Performantes schreiben grosser Dateien gegenüber c/c++??



  • hellihjb schrieb:

    ansonsten haette der Code nichts getan und waere vom Compilter bei der Optimierung komplett entfernt worden
    [...]
    Laufzeit mit Optimierung (Release Build):

    asm:  5.018.325.130
    c:               66
    

    Bist du dir sicher, dass die Schleife da nicht auch entfernt wurde und gleich das Ergebnis zurückgegeben wurde?

    Bei Dateizugriffen ist das Problem ohnehin die Festplatte. Die ist extrem lahm, da machen die paar Schleifendurchläufe prozentual praktisch nichts aus.



  • verifiarisation schrieb:

    Bist du dir sicher, dass die Schleife da nicht auch entfernt wurde und gleich das Ergebnis zurückgegeben wurde?

    Ich bin mir sogar sicher, dass die Schleifen entfernt wurden und schliesse daraus, dass in diesem Fall die Annahme "Der Speedvorteil durch asm ist offensichtlich" auf einer unangemessenen Messung beruht.



  • Hallo,

    Danke schonmal für eure Antworten. Ich glaube ich sollte nochmal deutlich machen worauf ich hinaus wollte, als Background 😉

    Folgendes Problem ist zu lösen: Es sollen alle IPv4 Internetadressen ind eine Datenbank geschrieben werden. Also von 0.0.0.0 bis 255.255.255.255. Macht in Summe über 4.2Mrd Adressen. (Brute Force) Selbst nach weglassen aller von ARIN reservierten Adressbereiche (Ein paar hundert Mill.) bleiben knappe 4Mrd Adressen über.

    Der erste Lösungsansatz bestand darin über den (php Interpreter) durch 4 verschachtelte Schleifen jede IP Adresse als String ind die DB zu schreiben.
    Den ersten Versuch musste ich abbrechen das dies einfach zulange dauerte.

    Der zweite Versuch bestand darin das gleiche php Programm zu nutzen aber die IP in eine Datei zu schreiben (muss TXT Datei sein keine Binär Datei) um diese dann in die DB zu laden.

    Dar php Interpreter brauchte dazu über Nacht c.a 10Stunden!!

    Also meine Idee 1) Compiliertes Programm vs. Interpreter sollte schneller sein.

    Die Umsetztung also in c/c++

    ...
    
    for (int a=0; a<256; a++) {	
    		for (int b=0; b<256; b++) {
    			for (int c=0; c<256; c++) {
    				for (int d=0; d<256; d++) {
    					sprintf(buff,"%d.%d.%d.%d\n",a,b,c,d);
    					fputs (buff,file);
    				}//d
    
    			}//c
    		}//b
    	}//a
    ...
    

    Dies Programm braucht knapp über eine Stunde! Also schneller als der php Interpreter.

    Dies brachte mich auf Idee 2) Assembler vs. c/c++ schneller?
    Schafft es handgeschriebener asm code dieses Programm schneller abzuarbeiten?
    Daher wollte ich erstmal wissen ob es überhaupt einen speed vorteil bringt was die Schleifendurchlaüfe angeht.

    deswegen der test

    for (int a=0; a<256; a++) {	
    		for (int b=0; b<256; b++) {
    			for (int c=0; c<256; c++) {
    				for (int d=0; d<256; d++) {
    
    				}//d
    
    			}//c
    		}//b
    	}//a
    

    mit Visual C++ 2008Express Edition comp. In der Debug Version dauerte die halt c.a 15s. Klar ist das in der Release Version nichts ausgeführt wird durch die Optimierung.

    Der Test Dagegen:

    CODE_SEG	SEGMENT
    					ASSUME CS:CODE_SEG
    start:		mov ax,0ffh
    m0:       mov bx,0ffh
    m1:       mov cx,0ffh
    m2:       mov dx,0ffh
    m3:       dec dx
              jnz m3
              dec cx
              jnz m2
              dec bx
              jnz m1
              dec ax
              jnz m0
    					mov ah,4ch
    					int 21
    CODE_SEG	ENDS
    					end start
    

    comp mit TASM und TLINK. Dauer der ausführung ca. 3s

    ------------- Ok break -------------------- 😉
    ob jetzt asm gegen über c/c++ schneller ist was leere schleifendurchlaüfe angeht ist ja auch erstma egal.

    Die beide obigen c programme gegenübergestellt macht wohl deutlich das 2 Flaschenhälse 😉 hier liegen (ein paar Sekunden gegenüber 60Minuten)

    sprintf(buff,"%d.%d.%d.%d\n",a,b,c,d);
    					fputs (buff,file);
    

    also die eigentlich arbeit.

    Ich dachte mir das händisch geschriebener asm code das schreiben in die Datei evtl. beschleunigen könnte als das was die c Routinen leisten können.
    In dem man vill über die größe des schreibpuffers dem computer zuarbeitet. Also überspitzt gesagt erst 1GB Schreib Puffer füllt und dann schreibt, als jedesmal nach 128b.
    Wenn ich mir das so durch den kopf gehen lasse ist das aber müll. ich denke mal das kommt beides aufs gleiche raus (ob nu einmal große Datenmengen schreiben oder mehrmals kleine)
    D.h ich nehme an das sowohl in c als auch in asm beide schreibvorgange vom Speed her fast identisch sein werden.
    (Die Festplatte als Bremse mal aus den Betrachtungen ausgeschlossen ,da sowohl asm als auch c dort nichts dran ändern können)

    Bleibt der 2. Flaschenhals:

    sprintf(buff,"%d.%d.%d.%d\n",a,b,c,d);
    

    Gibt es eine möglichkeit in asm dies effizienter abzubilden als dies der c compiler macht?
    Ich bin mir sicher ein Mensch ist kreativer als ein Compiler. (Mir zum. fällt aber nix gescheites ein)

    In der innersten schleife ist also jedsmal auszuführen:
    - Werte alle 4 Schleifenregister in Dzimalzahl umzuwandeln
    - alle 4 Zahlen in den string "a.b.c.d\n" umwandeln (sprintf() nachbilden
    ODER
    a --->in Puffer schreiben
    . --->in Puffer schreiben
    b --->in Puffer schreiben
    ..usw

    - String in Puffer schreiben
    - String in Datei schreiben wenn Puffer voll

    und das 4Mrd mal.
    Den vom Vc++ Compiler generierten Code für sprintf() und fputs() einfachso zu übrnehmen dürfte keinen Speedvorteil bringen.

    Gibt es also überhaupt eine Lösung das obige C/C++ Programm in asm sehr viel schneller abzubilden?
    (Werde auch im C/C++ Forum nochmal nachfragen ob in C noch Optimierungsmöglichkeiten gibt)

    ... Ich denke jetz hab ich erstmal genug gelabert und verbleibe erstmal mfg

    gruss ronny

    P.S.

    unsigned int count= 0; 
    
       for (int a=0xff;a!=0;a--) 
          for (int b=0xff;b!=0;b--) 
             for (int c=0xff;c!=0;c--) 
                for (int d=0xff;d!=0;d--) 
                   count++; 
    
       return count;
    

    wird in der release version tatsächlich nicht ausgeführt das progr. terminiert sofort.



  • mal unabhängig von der Sinnhaftigkeit: sprintf(buff,"%d.%d.%d.%d\n",a,b,c,d) ließe sich wunderbar in ASM implementieren (denke da an 'magic number division').

    BTW: hat es einen speziellen Grund warum hier 16 Bit code verwendet wird?



  • Ich würde mal meinen bei derart riesigen Datenmengen (was ist eigentlich der Sinn davon sämlichte IPv4 Adressen in eine Datei zu schreiben!?) sollte, zumindest wenn es einigermaßen vernünftig implementiert ist, wohl die Schreibgeschwindigkeit der Festplatte und nicht irgendein Schleifenoverhead limitierender Faktor werden. Und daran kannst du auch mit asm nichts ändern.

    Selbst wenn wir mal annehmen dass deine CPU nichts anderes tut dann brauchst du um 80GB zu schreiben (angenommene Schreibgeschwindigkeit: 70MB/s) immer noch 80 * 1024 / (70 * 60) = 19,5 min. Und das ist nur die Zeit die allein die Festplatte unter Idealbedingungen mindestens braucht um überhaupt 80GB zu schreiben. Und eine einigermaßen brauchbare Implementierung sollte ohne Probleme mehr als 70MB/s an Text generieren können (auf einer 1GHz CPU entspräche das 13950 Takten pro Zeichen, ich denke das ist sogar mit PHP machbar). Meiner Meinung nach verschwendest du mit asm hier deine Zeit da dein Code sowieso die meiste Zeit damit verbringen wird auf die Platte zu warten...



  • :xmas1:

    ; in: eax=byte
    ; uses eax,edx,ecx,ebx,esi
    byte_to_sz macro
    LOCAL @1
    
    	mov ebx,2e00h		; 0,'.'
    	.if eax > 9
    		mov esi,3
    		mov edx,0CCCCCCCDh
    		mul edx
    		shrd eax,edx,3
    		lea eax,[eax+1]
    		mov ecx,edx
    		mov edx,10
    		mul edx
    		shr ecx,3
    		mov eax,ecx
    
    		lea ebx,[edx+ebx+'0']
    		shl ebx,8
    
    		mov edx,0CCCCCCCDh
    		mul edx
    		shrd eax,edx,3
    		lea eax,[eax+1]
    		mov ecx,edx
    		mov edx,10
    		mul edx
    		shr ecx,3
    
    		lea ebx,[edx+ebx+'0']
    		jz @1
    		mov esi,4
    		shl ebx,8
    		lea ebx,[ecx+ebx+'0']		
    	.else
    		mov esi,2
    		lea ebx,[eax+ebx+'0']
    	.endif
    @1:
    endm
    
    ipsz proc uses ebx esi edi pszBuffer: ptr BYTE,cA:DWORD,cB:DWORD,cC:DWORD,cD:DWORD
    
    	mov edi,pszBuffer
    
    	mov eax,cA
    	byte_to_sz
    	mov DWORD ptr [edi],ebx
    	lea edi,[edi+esi]
    
    	mov eax,cB
    	byte_to_sz
    	mov DWORD ptr [edi],ebx
    	lea edi,[edi+esi]
    
    	mov eax,cC
    	byte_to_sz
    	mov DWORD ptr [edi],ebx
    	lea edi,[edi+esi]
    
    	mov eax,cD
    	byte_to_sz
    	mov DWORD ptr [edi],ebx
    	mov BYTE ptr [edi+esi-1],0	
    
    	ret
    
    ipsz endp
    

    :xmas2:



  • Ich denke, am schnellsten geht es, wenn du die Datei, die du schon erstellt hast, einfach kopierst!



  • Ich bin auf die IPv6-Umstellung gespannt.



  • hellihjb schrieb:

    Ich bin auf die IPv6-Umstellung gespannt.

    😃



  • dot schrieb:

    Ich würde mal meinen bei derart riesigen Datenmengen (was ist eigentlich der Sinn davon sämlichte IPv4 Adressen in eine Datei zu schreiben!?) sollte, zumindest wenn es einigermaßen vernünftig implementiert ist, wohl die Schreibgeschwindigkeit der Festplatte und nicht irgendein Schleifenoverhead limitierender Faktor werden. Und daran kannst du auch mit asm nichts ändern.

    Selbst wenn wir mal annehmen dass deine CPU nichts anderes tut dann brauchst du um 80GB zu schreiben (angenommene Schreibgeschwindigkeit: 70MB/s) immer noch 80 * 1024 / (70 * 60) = 19,5 min. Und das ist nur die Zeit die allein die Festplatte unter Idealbedingungen mindestens braucht um überhaupt 80GB zu schreiben. Und eine einigermaßen brauchbare Implementierung sollte ohne Probleme mehr als 70MB/s an Text generieren können (auf einer 1GHz CPU entspräche das 13950 Takten pro Zeichen ...

    Da ist was drann.

    Hab mal die Datei geschrieben - 68GB in 13 Minuten mit Intel CPU U7300 1.30GHz Hilfsmittel - grosser Schreibpuffer, Tabellen und Inline-Assembler.

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    FILE* file;
    
    int main(void)
    {
    	file = fopen("c:/test.txt", "w+b");
    
    	char tab1[256*4+1]; memset(tab1, '0', 256*4);
    	char tab2[256*4+1]; memset(tab2, '0', 256*4);
    
    	for (int i = 0; i < 256; i++)
    	{
    		if (i < 10) 
    		{ 
    			sprintf(&tab1[i*4+2], "%d.", i); sprintf(&tab2[i*4+2], "%d", i); tab2[i*4+3] = 13;
    			tab1[i*4+4] = '0'; /*Null-Terminierung übrschreiben*/ tab2[i*4+4] = '0'; continue;
    		}
    		if (i < 100)
    		{ 
    			sprintf(&tab1[i*4+1], "%d.", i); sprintf(&tab2[i*4+1], "%d", i); tab2[i*4+3] = 13;
    			tab1[i*4+4] = '0'; tab2[i*4+4] = '0'; 
    		}
    		else 
    		{
    			sprintf(&tab1[i*4], "%d.", i); sprintf(&tab2[i*4], "%d", i); tab2[i*4+3] = 13;
    			tab1[i*4+4] = '0'; tab2[i*4+4] = '0';
    		}
    	}
    	char* buf = (char*)malloc(256*256*256*17); 
    	unsigned int raw_ip = 0;
    
    	_asm {
    		mov		edx, raw_ip
    L1:		
    		push    esi
            push    edi
    		push    ebx
    		lea     esi, tab1
    		lea     ebx, tab2
    		mov     edi, buf
    		mov     ecx, 256*256*256	
    L2:
    		shld	eax, edx, 8
    	    and     eax, 0ffh;
    		mov     eax, [esi+eax*4]
    		stosd
    	    shld	eax, edx, 16
    	    and     eax, 0ffh;
    		mov     eax, [esi+eax*4]
    	    stosd
    		movzx   eax, dh
    		mov     eax, [esi+eax*4]
    	    stosd
            movzx   eax, dl
    		mov     eax, [ebx+eax*4]
    		stosd
    		mov     al, 10
    		stosb
    		inc     edx
    		dec     ecx
    		jne L2
    		mov     raw_ip, edx
    		pop     ebx
    		pop     edi
    		pop     esi
            push    file
    		push    256*256*256*17
    		push    1
            push    buf
    		call    dword ptr [fwrite]
    		add     esp, 16
    		mov     edx, raw_ip
    		or      edx, edx
    		jne L1
    	}
    	fclose(file);
    	free(buf);
    	return 0;
    }
    

    :xmas1:


Anmelden zum Antworten