Guten Abend!
Ich besitze einen NCR-DMV, ein 8-Bit-Bus System mit NEC V20 Cpu. Die modernste Software darauf ist TurboPascal 3 oder zB auch Wordstar 3.4 (mit Patch und zusätzlicher Hilfssoftware auch Wordstar 4). Das ganze unter DOS 2.11, 512KB RAM, 20MB Harddisk. Das Gerät ist so gut wie nicht "IBM-kompatibel" (andere Portnummern, andere BIOSdatenadressen, etc.).
Der DMV ist seit Jahrzehnten gut verstaut und ich hoffe, er läßt sich problemlos wieder in Betrieb nehmen, wenn ich erstmal einen genügend großen Arbeitsplatz dafür gefunden habe.
Jedenfalls habe ich vor, einige meiner Programme von damals (Ende 80er/Anfang 90er Jahre) nochmals zu überarbeiten, vor allem mit dem Ziel aus der Maschine mit deutlich mehr Wissen heute als damals rauszuholen was nur geht.
Nach Durchsicht einiger der sourcecodes von damals sehe ich enormes Potential zur Verbesserung in vielerlei Hinsicht und ich habe in den letzten Monaten im "Trockenen", also ohne irgendwie testen zu können, begonnen, die Programme zu überarbeiten.
Im Netz finden sich auch heute noch Tonnen an Sourcecodes (Pascal, gemischt mit Assembler), um die Turbo-eigenen Routinen durch schnellere Varianten zu ersetzen. Dabei dreht sich alles um schnelle Videoausgabe oder Ersatz der "string"-routinen wie pos(), insert(), delete(), upcase(), copy(), move() oder auch Vergleichsoperationen von Speicherblöcken, min() max() etc.
Nicht gefunden habe ich auch nur ein einziges Stück sourcecode, das sich mit den FILE procedures beschäftigt, also zB BLOCKREAD/WRITE(). Tips wie man sie optimal parametrisiert gibt es schon, aber keinen einzigen Versuch, die Routinen TurboPascals durch eigene zu ersetzen.
Die blockread-routine, der ein file-handle (nach assign(), reset()/rewrite()) übergeben wird, liest in einen vorbereiteten Speicherbereich X eine vorgegebene Menge Y Bytes einer Datei ein. Das ist natürlich in den meisten Anwendungsfällen notwendig und unumgänglich, nicht aber, wenn eigentlich mit den bytes/words (einzeln) gar nicht viel geschehen soll, sie also nicht wie zB für einen Editor lange Zeit im Speicher gehalten und im Verbund bearbeitet werden.
Wenn also wie in meiner Aufgabenstellung die Bytes/Words der Dabei nur als solche Einheiten von Interesse sind, also nicht ihre Zusammensetzung zu größeren Strukturen wie records/strings und ihre Be-/Verarbeitung minimal ist und sie sofort an ein anderes device (ser/par/vga/disk) weitergegeben werden, ist der Einsatz der von turbopascal zur Verfügung stehenden readfile-routinen in Bezug auf möglichst schenllen Programmablauf stark verbesserungsfähig.
Ein Beispiel:
Der NCR-DMV hat eine Grafiklösung (NEC yGD7220), die anders als die "IBM"kompatiblen den Videospeicher nicht an bestimmten Stellen (B000/B800) im Hauptspeicher einblendet und dort zur schnellen Bearbeitung zur Verfügung stellt, sondern nur über eine 8bit-Port ein- und ausgelesen werden können. Wie alle anderen devices auch.
Wenn nun zB auf Disk abgespeicherte Screenshots (4000 bytes, 25x80*2) eingelesen, das einzelne Attributbyte abgeändert und das Paar Zeichen/Attribut am Bildschirm ausgegeben werden sollen, so wäre die "natürliche" Programmierung in Kurzform etwa so (Pseudocode):
var buffer:[0..3999] of byte
f:file
bytesgelesen:integer;
fname = "C:\scrdata\scrshot.001"
assign(f,fname)
reset(f)
blockread(f, buffer, 4000, bytesgelesen)
show_screenshot(buffer)
Das sieht völlig normal aus und ich glaube jeder würde im Grunde die Aufgabe so lösen. Auf der Suche das letzte Quentchen Geschwindigkeit rauszuholen bin ich zum Schluß gekommen, daß das in Wahrheit hochgradig ineffizient und langsam ist.
Bei 4 involvierten 8-bit-Devices, CPU (intern 16 bit), DISK, RAM, VGA), die über einen 8bit-Bus miteinander kommunizieren, tauschen alle Geräte die Daten seriell-byteweise miteinander aus. Das geht gar nicht anders.
Was passiert auf TurboPascal-Ebene bei obigem Programm eigentlich (grob):
TPs blockread: Aufgabe: 4000 bytes von DISK in den RAM Speicherbuffer einlesen
Annahme: turbos blockread bedient sich des interrupts 21H, Funktion 3FH, und übergibt diesem dazu den file-handle (erhalten von assign()), die Anzahl der zu lesenden Bytes und die Adresse des Pufferspeichers)
Interrupt-routine:
* DISK -> BUS -> CPU (Einlesen in die CPU)
* CPU -> BUS -> RAM (Schreiben in den buffer[i])
show_screenshot: 2000 zeichen einlesen, 2000 attribute einlesen, 2000 attr nochmals einlesen, 2000 veränderte Attribute schreiben (RAM), 2000 zurück in die CPU, 2000 in die VGA
* RAM -> BUS -> CPU (einlesen Zeichen ch:=buffer[i])
* CPU -> BUS -> VGA (an den VGA schicken )
* RAM -> BUS -> CPU (attr einlesen und verändern des Attr zB: buffer[i+1]:=buffer[i+1] AND 15
* CPU -> BUS -> RAM (schreiben des Attributs nach buffer[i+1])
* RAM -> BUS -> CPU (wieder einlesen für ausgabe attribut nachg vga)
* CPU -> BUS -> VGA (an den VGA port schicken)
Wie man sieht ist verdammt viel los am BUS und im RAM. Für cycles-Fuchser viel zu viel, denn das ließe sich zb mit einem Assembler-Unterprogramm (Kernroutine) jedenfalls einfacher lösen:
2000x:
* disk -> bus -> CPU (Zeichen in CPU einlesen)
* disk -> bus -> CPU (attr in CPU einlesen)
---------------- CPU (in der CPU attribut direkt ändern)
2000x:
* CPU -> bus -> VGA (zeichen in den vga-port schreiben)
* CPU -> bus -> VGA (attr in den vga-port schreiben)
Mit vergleichsweise deutlich weniger Programmcodebytes ohne auch nur ein Datenbyte im RAM zwischenzuspeichern.
Im letzten Fall würden nur 8000 Datenbytes über den Bus geschickt werden.
Ganz anders im ersten Fall. Hier werden 4000 + 4000 + 6x2000 = 20000 Bytes über den Bus geschickt, gemeinsam mit einem x-fachen Aufwand an Programmcodebytes und der Dauer der Ausführung.
Es sollte sich hier also eine deutliche Verringerung der Gesamtablaufsdauer durch entsprechende direkte Programmierung einer "blockread"-Ersatzprozedur in einem external-Assemlberprogramm herbeizaubern lassen.
Es müßte also eigentlich eine Routine geschrieben werden, die im Prinzip das gleiche tut wie die TP-Blockread / Dos-Interruptroutine (aber keine ist), bis auf daß die Daten nicht in einen RAMbuffer geschrieben werden, sondern gleich wie hier zB an den VGA-port weitergeleitet werden.
Jetzt könnte der Einwand gebracht werden, daß es doch schneller sein müßte, einen großen Block in einem Rutsch zu lesen. Und einen großen Block in eiem zurüchzuschreiben (wohin auch immer). Das stimmt wohl für den allergrößten Teil der Anwendungen, nicht aber in einem Fall wie geschildert, wo die Daten mit minimaler Änderung, welche gleich direkt in der CPU vorgenommen werden kann, an ein Zielgerät geschickt werden sollen. Die Disk wie jedes andere Device hat einen 8 Bit Port. Alle Daten müssen stets durch dieses Nadelöhr, hintereinander. Blockzugriff hin oder her.
Ein weiterer Anwendungsfall fällt mir dazu ein, eine Datei von einem LW (A) auf ein anderes (B) zu kopieren. Auch dazu wäre ein Auslagern der Daten ins RAM unnötig und kostet enorm Zeit. Das gleiche LW darf es zwar technisch sein, aber die zehntausendfachen Lesekopfausrichtungen auf andere Sektoren wäre überaus zeitaufwändig und würde die LWsmechanik extrem belasten. Außerdem werden Sektoren immer als ganzes von der LWsintelligenz in interne Buffer gelesen, die jedesmal verworfen werden müßten, weil nach jedem Schreibvorgang neuerlich derselbe vorherige Lesesektor eingelesen werden müßte um innerhalb dessen das nächste Byte am Port auszugeben.
Ich weiß zwar, wie man mit den Lese/Schreibvorgängen über die Ports von VGA/ser/par Schnittstelle umgeht, nicht aber bei Laufwerken.
Wenn sich hier im Forum jemand damit auskennt, würde ich mich über entsprechende code-Schnippsel sehr freuen.