Autor |
Beitrag |
2cHH
      
Beiträge: 46
|
Verfasst: Do 31.08.06 17:38
Hi,
ich habe mal versucht, eine For-Schleife auszuprogrammieren.
Es geht um die DLL, die ich in meine Delphi-Anwednung einbinde.
Daher ist der Code noch im gcc Syntax,
hinterher soll er auch auf Delphi übertragen werden.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
| void strPaste(char** ppBuff, char* sCont, int nCont) { char *pBuff = *ppBuff; int nPos = 0; //for(; nPos < nCont; nPos++) *pBuff++ = *sCont++; ; //compilerswitch: -masm=intel asm(".intel_syntax noprefix\n") ; asm("LOOP: \n\t"); ; asm("mov esi, %0 \n\t"::"r"(sCont)); //quelladr laden asm("mov edi, %0 \n\t"::"r"(pBuff)); //zieladdr laden asm("mov al, [esi] \n\t"); //zeichen lesen asm("mov [edi], al \n\t"); //zeichen übertragen ; asm("mov eax, %0 \n\t"::"r"(nPos)); //nPos laden asm("inc eax \n\t"); //nPos erhöhen asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreiben ; asm("mov edx, %0 \n\t"::"r"(nCont)); //nCount laden asm("xor eax, edx \n\t"); //mit nPos vergleichen asm("jz END \n\t"); //wenn gleich, dann abbruch ; asm("mov eax, %0 \n\t"::"r"(sCont)); //sCont erhöhen asm("inc eax \n\t"); asm("mov %0, eax \n\t"::"r"(sCont)); ; asm("mov eax, %0 \n\t"::"r"(pBuff)); //pBuff erhöhen asm("inc eax \n\t"); asm("mov %0, eax \n\t"::"r"(pBuff)); asm("jmp LOOP \n\t"); //neuer durchlauf ; asm("END: \n\t"); ; *pBuff = '\0'; *ppBuff = pBuff; } |
Ich habe versucht, die auskommentierte For-Schleife 1:1 zu übersetzen,
daher auch die Einteillung. Leider gibt es eine Meldung:
"Invalid Pointer Operation"
Bitte nicht über den Assembler-Code lachen.....
Gruss aus Hamburg
|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: Do 31.08.06 19:16
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
| thomas@Majestix:/tmp$ cat test.c void strPaste(char** ppBuff, char* sCont, int nCont) { char *pBuff = *ppBuff; int nPos = 0; //for(; nPos < nCont; nPos++) *pBuff++ = *sCont++; ; //compilerswitch: -masm=intel asm(".intel_syntax noprefix\n") ; asm("LOOP: \n\t"); ; asm("mov esi, %0 \n\t"::"r"(sCont)); //quelladr laden asm("mov edi, %0 \n\t"::"r"(pBuff)); //zieladdr laden asm("mov al, [esi] \n\t"); //zeichen lesen asm("mov [edi], al \n\t"); //zeichen übertragen ; asm("mov eax, %0 \n\t"::"r"(nPos)); //nPos laden asm("inc eax \n\t"); //nPos erhöhen asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreiben ; asm("mov edx, %0 \n\t"::"r"(nCont)); //nCount laden asm("xor eax, edx \n\t"); //mit nPos vergleichen asm("jz END \n\t"); //wenn gleich, dann abbruch ; asm("mov eax, %0 \n\t"::"r"(sCont)); //sCont erhöhen asm("inc eax \n\t"); asm("mov %0, eax \n\t"::"r"(sCont)); ; asm("mov eax, %0 \n\t"::"r"(pBuff)); //pBuff erhöhen asm("inc eax \n\t"); asm("mov %0, eax \n\t"::"r"(pBuff)); asm("jmp LOOP \n\t"); //neuer durchlauf ; asm("END: \n\t"); ; *pBuff = '\0'; *ppBuff = pBuff; }
int main(int argc, char **argv) { return 0; } thomas@Majestix:/tmp$ gcc -masm=intel test.c -Wall thomas@Majestix:/tmp$ gcc --version gcc (GCC) 4.0.3 (Ubuntu 4.0.3-1ubuntu5) Copyright (C) 2006 Free Software Foundation, Inc. Dies ist freie Software; die Kopierbedingungen stehen in den Quellen. Es gibt KEINE Garantie; auch nicht für VERKAUFBARKEIT oder FÜR SPEZIELLE ZWECKE.
thomas@Majestix:/tmp$ | Funktioniert, wie du siehst, ohne Probleme. Welcher Compiler und was gibt er sonst noch für Informationen aus (Zeile oder Mnemonics)?
Ansonsten gibt es allerdings zur Laufzeit Probleme mit deinem Code. Dein Output Buffer ist ein char **, du lädtst daher die Adresse auf die Adresse des ersten Zeichens und behandelst sie anschließend im Code genauso wie deinen Input Buffer, der nur ein char * ist und bei dem du die Adresse des ersten Zeichens lädtst. Das kann so nicht richtig sein
Ansonsten möchte ich dich aber auf die Mnemonics LOOP und MOVS aufmerksam machen.
Intel-Syntax:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| XOR EDX, EDX ;EDX löschen MOV EAX, nCont ;Anzahl an zu kopierenden Zeichen nach EAX laden DIV 4 ;nCont durch 4 teilen, Ergebnis in EAX, Rest in EDX PUSH EDX ;Rest sichern MOV EAX, ECX ;Anzahl der Durchlaufe (nCont % 4) in ECX speichern
MOV EAX, sCont ;Quelladresse laden MOV EDX, ppBuff ;Pointer auf Zieladresse laden MOV EDX, [EDX] ;Zieladresse laden
loop1: MOVS dword_ptr [EDX], dword_ptr [EAX] ;DWORD kopieren LOOP loophead ;zurückspringen
POP ECX ;der Rest lag noch auf dem Stack loop2: MOVS byte_ptr [EDX], byte_ptr [EAX] ;BYTE kopieren LOOP loop2 ;zurückspringen | Ungetestet, ich hoffe das läuft so.
Welche Register du sichern musst und welche nicht und wie du korrekt die Pointergröße und Variablen im Assembler-Code angibst, entnimmst du bitte dem Handbuch deines Inline-Assemblers, mit dem GCC habe ich sowas noch nicht gemacht, ich glaub' ich bin zu alt für sowas
Verfahren: Bestimme, wie oft 4 Byte auf einmal kopiert werden können und kopiere sie auch auf einmal (weniger Pipeline-Durchläufe und weniger Transfers aus dem und in den Cache). Wenn man eine Stringlänge !=4 hat, werden die verbleibenden Byte (maximal 3) einzeln kopiert. Der Overhead um zu prüfen, ob man hier noch zwei Byte auf einmal kopiert werden können, ist wahrscheinlich größer als das dreimalige Kopieren einzelner Bytes (worst case).
2cHH hat folgendes geschrieben: | Bitte nicht über den Assembler-Code lachen..... |
[nelson]Haha[/nelson].
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
2cHH 
      
Beiträge: 46
|
Verfasst: Do 31.08.06 20:14
tommie-lie hat folgendes geschrieben: | Ansonsten gibt es allerdings zur Laufzeit Probleme mit deinem Code. |
Ja, das meinte ich auch, kompiliert bekomme ich ihn schon, aber der Code hat wohl Fehler.
Die Meldung kommt, wenn ich die DLL von dem Delphiprogramm benutzen lasse.
tommie-lie hat folgendes geschrieben: | Dein Output Buffer ist ein char **, du lädtst daher die Adresse auf die Adresse des ersten Zeichens und behandelst sie anschließend im Code genauso wie deinen Input Buffer, der nur ein char * ist und bei dem du die Adresse des ersten Zeichens lädtst. |
Reicht es nicht, das der Pointer vorher dereferenziert wird?
Quelltext 1:
| char *pBuff = *ppBuff; |
Ich habe aber, glaube ich, generell Probleme mit gcc Inline-Asm:
Das klappt auch schon nicht:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| int main(int argc, char *argv[]) { char* sSource = "Banana"; char* sTarget = "xxxxxx"; int nCont = 6; int nPos = 0; //compilerswitch: -masm=intel asm(".intel_syntax noprefix\n"); ; asm("mov esi, %0 \n\t"::"r"(sSource)); asm("mov edi, %0 \n\t"::"r"(sTarget)); asm("mov al, [esi] \n\t"); asm("mov [edi], al \n\t"); <-----------hier kommt schon der erste Fehler ; //printf("%s\n", sTarget); fflush(stdin); getchar(); return 0; } |
Es kommt eine Meldung, das auf eine Speicheradresse zugrgriffen werden sollte,
die bei 00403007 liegt, von 004012e2 aus, also nicht weit entfernt.
Irgendwas stimmt da noch nicht.
Ist Inline-Asm mit Delphi eigentlich einfacher?

|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: Fr 01.09.06 13:54
Gnnnnaaaarl, warum hat dieser Drecksbrowser gestern abend den Beitrag nicht abgeschickt? Und überhaupt, warum achte ich immer dann nicht drauf und schließe geistesabwesend den Tab, wenn mal etwas *nicht* klappt?
Nun denn, unvollständige Gedächtnisrekonstruktion von dem, was ich gestern abend getippt habe, von dem ich mir aber nur eingebildet habe, ich hätte es auch abgeschickt:
2cHH hat folgendes geschrieben: | Ja, das meinte ich auch, kompiliert bekomme ich ihn schon, aber der Code hat wohl Fehler. |
Ach so, ist mir entgangen.
2cHH hat folgendes geschrieben: | Die Meldung kommt, wenn ich die DLL von dem Delphiprogramm benutzen lasse. |
Zielpuffer ordentlich allokiert? Passendes ABI?
2cHH hat folgendes geschrieben: | tommie-lie hat folgendes geschrieben: | Dein Output Buffer ist ein char **, du lädtst daher die Adresse auf die Adresse des ersten Zeichens und behandelst sie anschließend im Code genauso wie deinen Input Buffer, der nur ein char * ist und bei dem du die Adresse des ersten Zeichens lädtst. | Reicht es nicht, das der Pointer vorher dereferenziert wird? |
Doch, eigentlich schon, habe übersehen, daß es noch ein pBuff gibt.
2cHH hat folgendes geschrieben: | Das klappt auch schon nicht:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| int main(int argc, char *argv[]) { char* sSource = "Banana"; char* sTarget = "xxxxxx"; // ... asm("mov edi, %0 \n\t"::"r"(sTarget)); asm("mov al, [esi] \n\t"); asm("mov [edi], al \n\t"); <-----------hier kommt schon der erste Fehler // ... | Es kommt eine Meldung, das auf eine Speicheradresse zugrgriffen werden sollte,
die bei 00403007 liegt, von 004012e2 aus, also nicht weit entfernt. |
Unter Linux kann man nicht schreibend in den Speicher des Images schreiben. sTarget zeigt auf den String "xxxxxx", der als Literal übergeben wurde. Das bedeutet, daß im Executable bereits dieser String auftaucht und nach dem Laden des Programmes und Ausführen der Zuweisung sTarget auf einen Speicherbereich innerhalb des Images des Executables zeigt. Ich bin nicht mehr so in den Windows-Interna, aber vielleicht ist das ja der Grund. Jedenfalls geht dein Code, wenn ich als Zielpuffer einen mit malloc() allokierten und somit schreibbaren Speicherbereich nehme.
2cHH hat folgendes geschrieben: | Ist Inline-Asm mit Delphi eigentlich einfacher? |
Du erkennst bereits an den Constraints, daß der Inline-Assembler vom GCC mehr drauf hat als der Delphi-Inline-Assembler, denn diese gibt es in Delphi nicht. Mit der zusätzlichen Macht, die dem Programmierer gegeben wird, steigt ganz automatisch auch die Komplexität. Prinzipiell ist aber der Inline-Assembler von Delphi "anders", genauso wie C nicht schwerer als Pascal ist.
Eigentlich hatte ich noch geschrieben, daß ich gestern abend zu müde sei, um um deinen restlichen Code ein Testbed zu schreiben und ihn zu debuggen und dies heute tun wolle, aber das hat sich ja nun erledigt
So, deinen Code aus dem ersten Post habe ich mit einem Zielpuffer, der von malloc() allokiert wurde, durch ddd gejagt. Dabei ist herausgekommen, daß es keinen Segfault (Speicherzugriffsverletzung, Absturz, Bluescreen,EAccessViolation, wie auch immer du das nennen willst) gibt.
Allerdings stoße ich auf ein anderes Problem, daß durch deine Verwendung von Contraints entsteht.
Mein GCC (ohne Optimierung) macht nämlich aus den Contraints immer ein "mov (addr), %eax" und "mov %eax, %zielregister" (AT&T-Assembler!). Daraus ergibt sich für deine sechs Zeilen:
Quelltext 1: 2: 3: 4: 5: 6: 7:
| asm("mov eax, %0 \n\t"::"r"(nPos)); //nPos laden asm("inc eax \n\t"); //nPos erhöhen asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreiben ; asm("mov edx, %0 \n\t"::"r"(nCont)); //nCount laden asm("xor eax, edx \n\t"); //mit nPos vergleichen asm("jz END \n\t"); //wenn gleich, dann abbruch | folgender Code (ebenfalls AT&T-Syntax):
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| 0x08048383 <LOOP+14>: mov 0xfffffffc(%ebp),%eax 0x08048386 <LOOP+17>: mov %eax,%eax 0x08048388 <LOOP+19>: inc %eax 0x08048389 <LOOP+20>: mov 0xfffffffc(%ebp),%eax 0x0804838c <LOOP+23>: mov %eax,%eax 0x0804838e <LOOP+25>: mov 0x10(%ebp),%eax 0x08048391 <LOOP+28>: mov %eax,%edx 0x08048393 <LOOP+30>: xor %edx,%eax 0x08048395 <LOOP+32>: je 0x80483af <END> | Hier wird also zunächst nPos um ein erhöht und (vermeintlich, später mehr) in die Variable zurückgeschrieben (ersten drei Zeilen), anschließend nCont über EAX in EDX geladen, wobei der alte Inhalt von EAX verloren geht. Beim anschließenden Vergleich von EDX mit EAX ergibt sich natürlich, daß beide Register den gleichen Inhalt haben, das XOR somit 0 ergibt und durch deinen jz die Schleife verlassen wird. Du gehst in deinem Code davon aus, daß EAX seinen Wert behält. Eine Korrektur würde darin bestehen, als Constriant nicht r zu verwenden, sondern einen Constraint für das richtige Register. Das r veranlasst den Compiler nämlich, selbst eines der General Purpose Registers zu bestimmen, in dem es den Wert speichern soll. Das klappt so aber nicht, weil die Anweisung nur das Zielregister angibt, nicht aber eines der Register, die auf keinen Fall benutzt werden sollen (in diesem Fall EAX).
Eine mögliche Lösung:
Quelltext 1: 2: 3: 4: 5:
| asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreiben ; // asm("mov edx, %0 \n\t"::"d"(nCont)); //nCount laden asm("xor eax, edx \n\t":: "d"(nCont)); //mit nPos vergleichen asm("jz END \n\t"); //wenn gleich, dann abbruch | Das "d"-Constraint lädt nCont gleich nach EDI, ein "MOV EDI, %0" ist nicht notwendig.
Ein weiteres Problem (ebenfalls im Zusammenhang mit Constraints) taucht auf, wenn man sich anschaut, wie du die Werte zurück in die Variablen schreiben willst:
asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreibenHier setzt du nach dem (korrekten) Erhöhen von nPos nicht den Inhalt der Variable auf den neuen Wert. Stattdessen wird, wie du durch den Constraint veranlasst, nPos in ein GPR geladen und anschließend der Wert von EAX in dieses Register geschrieben (auf diese Art und Weise kommt in meinem Debug-Code oben das "mov %eax, %eax" in Zeile 5 zustande).
Überhaupt halte ich dein Verfahren nicht für sonderlich effektiv. Du bekommst nCont als Wert (und nicht als Referenz) übergeben, benutzt aber trotzdem eine zusätzliche Variable und somit zusätzlichen Speicher, um zu zählen, wie weit du schon bist. Stattdessen könntest du doch einfach nCont herunterzählen und wenn du bei 0 ankommst, bist du fertig.
Hier mal mein Verfahren aus dem zweiten Post komplett getestet und funktionsfähig:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| { asm(".intel_syntax noprefix"); asm(""::"c"(nCont)); // nCont in ECX asm(""::"S"(sCont)); // sCont in ESI asm(""::"D"(buff)); // buff in EDI asm( "CLD\n\t"
"REP MOVSB byte ptr [EDI], byte ptr [ESI]\n\t" ); }
int main (int argc, char *argv[]) { char *buff = malloc(42); strPaste2(buff, "hello, world", 13); printf("%s", buff); } | Wie du siehst, geht das alles mit nur zwei Zeilen eigenem Code
Als nCont wird 13 statt 12 übergeben, um denn Nullterminator des C-Strings mitzukopieren. Das setzt natürlich vorraus, daß buff mindestens 13 Zeichen (String plus \0) lang ist.
Mit meiner obigen Optimierung durch Kopieren von vier Byte auf einmal sieht's dann nur wenig anders aus:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| void strPaste2(char *buff, char *sCont, int nCont) { asm(".intel_syntax noprefix"); asm(""::"a"(nCont)); // nCont in EAX asm(""::"S"(sCont)); // sCont in ESI asm(""::"D"(buff)); // buff in EDI asm( "XOR EDX, EDX\n\t" "MOV ECX, 4\n\t" "DIV ECX\n\t" "MOV ECX, EAX\n\t" "CLD\n\t"
"REP MOVSD dword ptr [EDI], dword ptr [ESI]\n\t"
"MOV ECX, EDX\n\t" "REP MOVSB byte ptr [EDI], byte ptr [ESI]\n\t" ); } | Da DIV keinen immed*-Operand erlaubt, muss man den Umweg über das Register ECX nehmen, zahlt sich aber bei längeren Strings aus. Wahlweise kann man den Width Postfix (B, W oder D) am Mnemonic oder den With Specifier "[byte|word|dword] ptr" weglassen, weil sich diese beiden Hinweise für den Assembler gegenseitig implizieren.
Bei langen Strings noch schneller ginge es mit längeren Registern, wie sie x86-64, die x87-Coprozessoreinheit oder SSE bieten, aber bei deren Benutzung kenne ich mich selbst zu wenig aus.
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
2cHH 
      
Beiträge: 46
|
Verfasst: Fr 01.09.06 16:56
Hi,
soweit war ich bisher gekommen:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| void strPaste(char** ppBuff, char* sCont, int nCont) { //compilerswitch: -masm=intel asm volatile(".intel_syntax noprefix\n"); ; asm volatile("mov ebx, %0 \n"::"r"(sCont)); asm volatile("mov edx, [%0] \n"::"r"(ppBuff)); asm volatile("mov ecx, %0 \n"::"r"(nCont)); asm volatile("xor ecx, 0 \n"); asm volatile("jz MARK_02 \n"); ; asm volatile("MARK_01: \n"); asm volatile("mov al, [ebx] \n"); asm volatile("mov [edx], al \n"); asm volatile("inc ebx \n"); asm volatile("inc edx \n"); asm volatile("loop MARK_01 \n"); ; asm volatile("MARK_02: \n"); asm volatile("mov eax, %0 \n"::"r"(ppBuff)); asm volatile("mov [eax], edx \n"); } |
tommie-lie hat folgendes geschrieben: | Allerdings stoße ich auf ein anderes Problem, daß durch deine Verwendung von Contraints entsteht.
Mein GCC (ohne Optimierung) macht nämlich aus den Contraints immer ein "mov (addr), %eax" und "mov %eax, %zielregister" (AT&T-Assembler!).
----
Das "d"-Constraint lädt nCont gleich nach EDI, ein "MOV EDI, %0" ist nicht notwendig.
Ein weiteres Problem (ebenfalls im Zusammenhang mit Constraints) taucht auf, wenn man sich anschaut, wie du die Werte zurück in die Variablen schreiben willst:
asm("mov %0, eax \n\t"::"r"(nPos)); //nPos zurückschreibenHier setzt du nach dem (korrekten) Erhöhen von nPos nicht den Inhalt der Variable auf den neuen Wert. Stattdessen wird, wie du durch den Constraint veranlasst, nPos in ein GPR geladen und anschließend der Wert von EAX in dieses Register geschrieben (auf diese Art und Weise kommt in meinem Debug-Code oben das "mov %eax, %eax" in Zeile 5 zustande). |
Den Loop-Befehl habe ich sozusagen autodidaktisch erarbeitet, thnx. Weil zu Inline-Asm mit gcc nur englische Literatur zu finden ist, war es leider mit den Constraints bisher auch so ähnlich.
Zum Glück hatte ich früher mal VC installiert, im Debugger konnte ich sehen, wie mein Code übersetzt wurde. Ich bin zwar Anfänger, aber gewundert hat mich das schon. Wozu die Constainsts genau da sind und welche es wofür gibt ist mir leider auch noch nicht 100% klar. Ich dachte "r", bzw "=r" steht für Register, weil der Wert der Variablen in oder aus einem Register übernommen werden soll, "m" "=m" für Speicher, "i" "=i" für Konsatnten.
Aber das habe ich mir wohl falsch zusammengereimt.....
Bist du so nett noch etwas zu den Constraints zu schreiben oder wenn du einen Link in deutsch hast, wäre auch nicht schlecht. Den Code von dir verstehe ich noch nicht ganz, warum hast du genau diese Constraints verwendet und was macht CLD REP (Schleife?) und MOVSB?
(ich dachte MOVE geht immer nur mit einem Register)
Gruss aus Hamburg
--
ps: ok, soweit habe ich es wohl kapiert:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| void strPaste(char** ppBuff, char* sCont, int nCont) { //compilerswitch: -masm=intel asm volatile(".intel_syntax noprefix\n"); ; asm volatile(" \n"::"b"(sCont)); asm volatile(" \n"::"d"(ppBuff)); asm volatile("mov edx, [edx] \n"); asm volatile(" \n"::"c"(nCont)); asm volatile("xor ecx, 0 \n"); asm volatile("jz MARK_02 \n"); ; asm volatile("MARK_01: \n"); asm volatile("mov al, [ebx] \n"); asm volatile("mov [edx], al \n"); asm volatile("inc ebx \n"); asm volatile("inc edx \n"); asm volatile("loop MARK_01 \n"); ; asm volatile("MARK_02: \n"); asm volatile(" \n"::"a"(ppBuff)); asm volatile("mov [eax], edx \n"); } |
wie ich Constraints bisher verstehe:
a,b,c,d für das Laden in oder aus dem jeweiligen Register (A-Register, B-Register...)
S,D analog für Source- und Destination-Index
r der Compiler bestimmt eines aus a,b,c,d (meistens wohl a)
m steht für Memory, soweit ich gelesen habe, was bedeutet, k.A.
I k.A.
i das gleiche wie I?
... es gibt bestimmt noch mehr... 
|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: Fr 01.09.06 18:56
2cHH hat folgendes geschrieben: | soweit war ich bisher gekommen: |
Sieht schon besser aus (vor allem mit dem Rückgabewert und den verwendeten Variablen), allerdings könnte (je nach Einstellung) EBX als Basisregister vom Compiler benutzt werden. Das sollte ihm eventuell durch den dritten Constraint ( asm("...":::"ebx")) mitgeteilt werden. Das volatile sollte hier auch nicht nötig sein und verwirrt unter Umständen nur den Optimizer unnötig.
2cHH hat folgendes geschrieben: | Den Loop-Befehl habe ich sozusagen autodidaktisch erarbeitet, thnx. |
Ich mir auch
2cHH hat folgendes geschrieben: | Wozu die Constainsts genau da sind und welche es wofür gibt ist mir leider auch noch nicht 100% klar. |
Um dem Compiler mitzuteilen, welche Register Eingabe- oder Ausgabeparameter enthalten und welche Register verändert werden. Theoretisch nicht notwendig, wenn man sich auf ein ABI festnagelt und beispielsweise die Parameter immer Right-To-Left auf dem Stack erwartet, aber praktisch hilfreich um genau zu definieren, welche Variablenwerte in welchem Register landen sollen und dem Optimizer mitzuteilen, welche Register man in seinem Assemblercode verändert. Auf Maschinen mit sehr wenigen Registern wie der x86-Architektur sind Register für den Optimizer eine heißbegehrte Ware. Ich meine mich erinnern zu können, daß der Optimizer von Delphi bei Inline-Assembler gar nicht optimiert, weil er annimmt, daß alle Register verwendet werden. Dem GCC-Optimizer kann man mit den Constraints mitteilen, was man tut.
2cHH hat folgendes geschrieben: | Ich dachte "r", bzw "=r" steht für Register, weil der Wert der Variablen in oder aus einem Register übernommen werden soll, "m" "=m" für Speicher, "i" "=i" für Konsatnten. |
Stimmt ja, auch, je nach Interpretation
"r" heißt, daß die Variable in einem Vielzweckregister gehalten wird, "m" bedeutet, daß die Variable in einer Speicherstelle verfügbar ist. Gemeint ist damit aber lediglich, was %0, %1 und so weiter intern darstellen. Ist das Constraint "m", so bedeutet %0 nur eine Adresse, also etwas, was in Intel-Syntax "[$12345678]" entspricht. "r" hingegen überträgt den Wert in ein Register beliebiger Art, wobei man davon ausgehen muss, daß möglicherweise andere Registerwerte überschrieben werden. Die genaue spezifizierung des Registers erfolgt über "a", "b" und so weiter.
2cHH hat folgendes geschrieben: | Bist du so nett noch etwas zu den Constraints zu schreiben oder wenn du einen Link in deutsch hast, wäre auch nicht schlecht. |
Ich habe nur dieses Howto, das vor allem für Umsteiger von anderen Inline-Assemblern gedacht ist, Brennan's Guide to Inline Assembly mit ähnlichem Inhalt und eben habe ich noch dieses Paper von IBM gefunden. Gezielt nach deutschen Texten suche ich generell nicht, das schränkt meine Ergebnisse zu sehr ein, und meine Erfahrungen mit deutschen Ressourcen sind relativ besch...eiden
2cHH hat folgendes geschrieben: | Den Code von dir verstehe ich noch nicht ganz, warum hast du genau diese Constraints verwendet und was macht CLD REP (Schleife?) und MOVSB?
(ich dachte MOVE geht immer nur mit einem Register) |
Die Constraints habe ich verwendet, weil sie naheliegend sind
Ich kann den Quell- und Zielpuffer durch einen Constraint direkt in ESI und EDI laden, also tue ich das doch anstatt ihn erst in EAX zu laden und anschließend selbst nach EDI oder ESI zu verschieben. Genauso beim ersten (undoptimierten) Code mit dem nCont. ECX ist das Zählregister, also nehme ich das als Zähler. Sämtliche Schleifenbefehle, die du findest, veerwenden das Zählregister, damit halte ich mir eigene Abfragen von anderen Registern vom Hals.
MOVS ist "Move String Element" und kopiert die angegebene Datenmenge (MOVSB für Byte, MOVSW für Word und so weiter) vom Quell- zum Zieloperand. Anschließend wird EDI und ESI jeweils um eins erhöht oder erniedrigt, abhängig vom Zustand des Direction Flag (DF). Damit es erhöht wird, ich also im String weiter nach vorne wandere, rufe ich vorher CLD auf, um das DF auf jeden Fall auf 0 zu setzen (was bedeutet, daß die Richtung "nach oben" ist (steigende Adressen)). Das alles macht ein einzelner Aufruf von MOVS. Nun will ich aber das ganze so lange machen, bis ich zum Ende des String komme. Das kann ich durch eine eigene Schleife machen. Ich kann aber auch den Prozessor dazu veranlassen, das selbst zu tun. Das macht der Prefix REP (das ist kein Befehl!). Er wiederholt den nachstehenden Befehl so lange, bis ECX == 0 ist und verringert gleichzeitig in jedem Durchlauf den Wert von ECX.
Und jetzt weißt du auch, warum ich nCont in ECX geladen habe: Die Anzahl der zu kopierenden Zeichen steht in ECX, wird bei jedem Schleifendurchlauf verringert, und sobald ECX 0 ist, habe ich keine Zeichen mehr zu kopieren und der Programmablauf geht hinter dem Befehl weiter, der mit REP versehen wurde. Damit spare ich mir eigenes Zählen, eigene Abfragen ob ich schon fertig bin und ich gebe einem modernen Prozessor die Möglichkeit, selbst zu optimieren, was er für richtig hält.
Beim zweiten Code kann ich aber nCont nicht in ECX gebrauchen, weil DIV auf das EAX:EDX-Registerpaar wirkt. Also lade ich nCont direkt nach EAX, lösche EDX und stecke den Divisor in ECX. Anschließend kopiere ich aber das Divisionergebnis trotzdem nach ECX, damit ich wieder den Prozessor optimieren lassen kann.
2cHH hat folgendes geschrieben: | [...]
... es gibt bestimmt noch mehr...  |
Ja, es gibt noch mehr. In meinem ersten Link sind weitere beschrieben. Was sie im Einzelnen für Auswirkungen auf den erzeugten Code haben, weiß ich auch nicht genau, müsste man sich bei zeiten mal im Debugger anschauen. Gute Standalone-Debugger (nicht in einer IDE integriert) gibt es auch für Windows.
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
2cHH 
      
Beiträge: 46
|
Verfasst: Sa 02.09.06 15:34
Hi,
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
| void strPaste(char** ppBuff, char* sCont, int nCont) { asm(".intel_syntax noprefix \n\t"); asm("# compiler: -masm=intel \n\t"); ; asm(" \n\t"::"S"(sCont)); asm(" \n\t"::"D"(ppBuff)); asm("mov eDI, [eDI] \n\t");//de-referring pointer asm(" \n\t"::"a"(nCont)); asm("xor eax, 0 \n\t");//set flags asm("jz MARK_END \n\t");//nCont == 0 ?+ jmp MARK_END ; asm("xor edx, edx \n\t");//edx = 0 asm("mov ecx, 4 \n\t");//div: value = 0xFFFFFFFFF * edx + eax asm("div ecx \n\t");//div: eax = value / ecx asm("mov ecx, eax \n\t");//div: edx = value % ecx ; asm("cld \n\t");//clear direction flag asm("rep movsd [eDI], [eSI] \n\t");//movsd: alias "movs dword ptr" asm("mov ecx, edx \n\t");//rep: edx ?* rep asm("rep movsb [eDI], [eSI] \n\t");//movsb: alias "movs byte ptr" ; asm("MARK_END: \n\t"); asm(" \n\t"::"a"(ppBuff)); asm("mov [eax], eDI \n\t");//store pBuff //asm("movb [eDI], 0 \n\t");//store NullChar } |
ohne deine Hilfe wäre ich immer noch am rätseln.  thnx.
Ich hab' mal versucht, den Code für später etwas zu kommentieren.
Jetzt habe ich noch versucht, das was ich bisher gelernt habe, anzuwenden.
Es geht wieder um eine Zeile C-Code (auskommentiert), der es in sich hat:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
| void strUpTo(char** ppCont, char** ppSearch) { //while((*++(*ppCont) == *++(*ppSearch)) && // (**ppCont) && (**ppSearch)); ; asm(".intel_syntax noprefix \n\t"); asm("# compiler: -masm=intel \n\t"); ; asm(" \n\t"::"b"(ppCont)); asm(" \n\t"::"d"(ppSearch)); asm("mov ebx, [ebx] \n\t");//de-referring pointer asm("mov edx, [edx] \n\t");//de-referring pointer ; asm("MARK_LOOP: \n\t");//start repeating asm("inc ebx \n\t");//increment pointer asm("inc edx \n\t");//increment pointer asm("movb al, [ebx] \n\t");//load cont-char asm("xor al, 0 \n\t");//test if null-char asm("jz MARK_SKIP \n\t");//jump if null-char asm("movb cl, [edx] \n\t":::"cl");//load search-char asm("xor al, 0 \n\t":::"cl");//test if null-char asm("jz MARK_SKIP \n\t":::"cl");//jump if null-char asm("xor al, cl \n\t":::"cl");//test if chars equal asm("jz MARK_LOOP \n\t");//repeat if chars equal ; asm("MARK_SKIP: \n\t"); asm(" \n\t"::"a"(ppCont)); asm("mov [eax], ebx \n\t");//store pCont asm(" \n\t"::"a"(ppSearch)); asm("mov [eax], edx \n\t");//store pSearch } |
Es funktioniert soweit ich sagen kann, recht gut.
Die Frage ist, ob ich die Constraints richtig verwendet habe.....
tommie-lie hat folgendes geschrieben: | In meinem ersten Link sind weitere beschrieben. Was sie im Einzelnen für Auswirkungen auf den erzeugten Code haben, weiß ich auch nicht genau, müsste man sich bei zeiten mal im Debugger anschauen. Gute Standalone-Debugger (nicht in einer IDE integriert) gibt es auch für Windows. |
Die lese ich seit gestern (mit Unterbrechung) und bin immer noch nicht ganz durchgestiegen.
Aber ich bin imho jetzt schon auf dem richtigen Weg.
Gruss aus Hamburg
btw: Welche guten Debugger gibt es eigentlich für Win32?
|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: Sa 02.09.06 17:40
[quote=" 2cHH"] Quelltext 1: 2: 3: 4:
| asm("MARK_END: \n\t"); asm(" \n\t"::"a"(ppBuff)); asm("mov [eax], eDI \n\t");//store pBuff //asm("movb [eDI], 0 \n\t");//store NullChar | Das ist in dieser Form gefährlich. MOVS (in allen Geschmacksirchtungen) erhöht sowohl EDI als auch ESI nach jedem Aufruf um den Wert, wie die Operandengröße von MOVS war. EDI würde demnach nach getaner Arbeit auf das Zeichen hinter dem zuletzt kopierten (also hinter dem Nullterminator) zeigen. Diese Adresse schreibst du nun nach *ppBuff. Ist das wirklich das, was du willst?
Im übrigen brauchst du auch gar keinen char ** um das zu erreichen, was du willst. In C sind Parameterübergaben "by value", insofern hast du recht, daß du, um einen char * "by reference" übergeben willst, char ** nehmen müsstest. Im Augenblick sieht es aber so aus, als müsse der Speicher, in den geschrieben werden soll, schon vom Caller allokiert werden. In dem Fall reicht ein char * aus, denn nur der Zeiger wird als Wert übergeben, den Zeiger auf das erste Zeichen im String willst du aber gar nicht ändern (tust du bei sehr spitzfindiger Lesart trotzdem, aber diese Änderung soll ja nicht auf den Caller zurückschlagen). Stattdessen interessierst du dich für den Speicherbereich, auf den der Zeiger zeigt, und den kann man nicht "by value" übergeben. Es reicht daher, einfach ein char * draus zu machen. Aus Delphi heraus kannst du es dann ebenfalls bei einem PChar belassen und brauchst keinen "PPChar".
Zitat: | Es geht wieder um eine Zeile C-Code (auskommentiert), der es in sich hat: |
Schon der C-Code scheint mir nicht das zu tun, was der Autor wahrscheinlich im Sinn hatte.
Pre-Increment hat einen hohen Vorrang und erhöht den Wert, bevor es ihn zurückgibt. Wenn *ppCont und *ppSearch jeweils auf das erste Zeichen zeigen, wird der Zeiger zuerst um ein erhöht und dann erst dereferenziert und verglichen. Das erste Zeichen in beiden Strings wird dabei ignoriert. Eine Abfrage *ppCont && *ppSearch erscheint mir beim Einstieg in die Funktion sinnvoll, um zu schauen, ob die beiden übergebenen Strings überhaupt existieren, aber nicht als Teil der Schleife, denn hier wird nur verglichen, ob der String nicht auf die Adresse null zeigt, nicht ob das Zeichen an der Position der Nullterminator ist.
Ich würde schon die C-Funktion eher so formulieren:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| void strUpTo(char **ppCont, char **ppSearch) { if ((!*ppCont) || (!*ppSearch)) return;
for (; **ppCont && **ppSearch && (**ppCont == **ppSearch); (*ppCont)++, (*ppSearch)++) ; } |
Diesmal schreibe ich meine Kommentare als C-Style Blockkommentar direkt in den Code.
Zitat: | 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
| void strUpTo(char** ppCont, char** ppSearch) { //while((*++(*ppCont) == *++(*ppSearch)) && // (*ppCont) && (*ppSearch)); ; asm(".intel_syntax noprefix \n\t"); asm("# compiler: -masm=intel \n\t"); ; asm(" \n\t"::"b"(ppCont)); asm(" \n\t"::"d"(ppSearch)); asm("mov ebx, [ebx] \n\t");//de-referring pointer asm("mov edx, [edx] \n\t");//de-referring pointer ; asm("MARK_LOOP: \n\t");//start repeating
/* Die nächsten beiden Zeilen überspringen das erste Zeichen, siehe Text oben. */ asm("inc ebx \n\t");//increment pointer asm("inc edx \n\t");//increment pointer
/* Das ist eigentlich überflissig. "xor byte ptr [EBX], 0" würde auch reichen */ asm("movb al, [ebx] \n\t");//load cont-char asm("xor al, 0 \n\t");//test if null-char asm("jz MARK_SKIP \n\t");//jump if null-char asm("movb cl, [edx] \n\t":::"cl");//load search-char
/* Schon wieder? AL hat sich seit dem letzten Vergleich nicht geändert */ asm("xor al, 0 \n\t":::"cl");//test if null-char asm("jz MARK_SKIP \n\t":::"cl");//jump if null-char asm("xor al, cl \n\t":::"cl");//test if chars equal asm("jz MARK_LOOP \n\t");//repeat if chars equal ; asm("MARK_SKIP: \n\t"); asm(" \n\t"::"a"(ppCont)); asm("mov [eax], ebx \n\t");//store pCont asm(" \n\t"::"a"(ppSearch)); asm("mov [eax], edx \n\t");//store pSearch } | |
Zitat: | Die Frage ist, ob ich die Constraints richtig verwendet habe..... |
Soweit schon. Nur die clobber list ist ein wenig eigenartig. So oft brauchst du cl dort nicht zu erwähnen. Stattdessen sollten vielleicht die anderen geänderten Register erwähnt werden, wie zum Beispiel EBX und EAX/AL.
Außerdem ist dein Code keine vollständige Nachbildung des C-Codes. Zum einen prüfst du nun tatsächlich die Zeichen, ob sie der Nullterminator sind, oder nicht (und nicht nur den Pointer auf den String), und zum anderen ist die Bearbeitung bei operator&& left-to-right, es werden also zuerst die beiden Zeichen verglichen und anschließend erst geprüft, ob sie Null sind.
Meine Version des Ganzen:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
| void strUpTo2(char **ppCont, char **ppSearch) { // if ((!*ppCont) || (!*ppSearch)) // return;
// for (; **ppCont && **ppSearch && (**ppCont == **ppSearch); //(*ppCont)++, (*ppSearch)++) // ; asm(".intel_syntax noprefix"); asm(""::"D"(ppCont)); asm(""::"S"(ppSearch)); asm("XOR EDI, 0"); asm("JZ END"); asm("XOR ESI, 0"); asm("JZ END");
asm("MOV EDI, [EDI]":::"edi"); asm("MOV ESI, [ESI]":::"esi");
asm("LOOP1:"); asm("MOV AL, [EDI]":::"al"); asm("XOR AL, 0"); asm("JZ END"); asm("MOV AH, [ESI]":::"ah"); asm("XOR AH, 0"); asm("JZ END"); asm("CMP AL, AH"); asm("JNE END"); asm("INC EDI":::"edi"); asm("INC ESI":::"esi"); asm("JMP LOOP1");
asm("END:"); asm(""::"d"(ppCont)); asm("MOV [EDX], EDI"); } |
2cHH hat folgendes geschrieben: | Welche Debugger gibt es denn für Win32? |
Viele
Ich denke IDA ist ganz brauchbar als Disassembler, um sich den erzeugten Code anzuschauen. Du könntest noch hier im Abschnitt "Reverse Engineering" schauen. Ansonsten habe ich wenig Erfahrung mit "ausgewachsenen" Windows-Debuggern, aber ich bin mir sicher, daß es sie gibt, schließlich sollte man auch unter Windows meinen, daß jemand mal in die Verlegenheit kommt, ein Programm zu debuggen, ohne es gleich selbst komplett neu zu bauen. Und wenn gar nichts hilft kannst du ja immer noch Linux in einer VM installieren, da gibt es den GDB und die grafischen Frontends ddd und Insight.
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
2cHH 
      
Beiträge: 46
|
Verfasst: So 03.09.06 09:00
Sorry,
den Code, den du gesehen hast, hatte ich anschliessend verbessert:
Quelltext 1: 2:
| //while((*++(*ppCont) == *++(*ppSearch)) && // (**ppCont) && (**ppSearch)); |
Natürlich soll nicht der Zeiger geprüft werden, sondern das, was drunter ist.
tommie-lie hat folgendes geschrieben: | Im übrigen brauchst du auch gar keinen char ** um das zu erreichen, was du willst. In C sind Parameterübergaben "by value", insofern hast du recht, daß du, um einen char * "by reference" übergeben willst, char ** nehmen müsstest. |
Doch, ich glaube schon. Die Funktionen, die ich mit Assembler zu optimieren versuche,
sind Stellen aus einer anderen Funktion. In der ursprünglichen Funktion
wird ein Zeiger nach Überprüfung des referrierten Inhaltes incrementiert.
Weil das jetzt extern geschehen soll, übergeben ich den Zeiger wiederum als Zeiger.
"Richtige" Referenzen gibt es ja bei C imho nicht,
daher muss de-referrenziert und am Ende wieder zurückgeschrieben werden,
auch das Pre-Increment ist beabsichtigt. Aus der ursprünglichen Funktion:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| //...pBuff allocated... int nToCopy = 0; while(*sCont) { if(*sCont == *sSearch) { char* pCont = sCont; char* pSearch = sSearch; //while((*++pCont == *++pSearch) && // (*pCont) && (*pSearch)); ; strUpTo(&pCont, &pSearch); ; if(!*pSearch) { strPaste(&pBuff, (sCont - nToCopy), nToCopy); nToCopy = 0; strPaste(&pBuff, sRepl, nRepl); sCont += nSearch; continue; } } sCont++; nToCopy++; } strPaste(&pBuff, (sCont - nToCopy), nToCopy); *pBuff = '\0'; return sBuff; } |
Der Speicher wird von Delphi aus alloziert.
Die auskommentierte(n) While-Schleife(n) soll durch "strUpTo()" ersetzt werden:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| void strUpTo(char** ppCont, char** ppSearch) { //while( ((*++(*ppCont) == *++(*ppSearch)) // (**ppCont) && (**ppSearch)); ; asm("# compiler: -masm=intel \n\t"); asm(".intel_syntax noprefix \n\t"); ; asm("mov ebx, [%0] \n\t"::"b"(ppCont));//load [pCont] asm("mov edx, [%0] \n\t"::"d"(ppSearch));//load [pSearch] ; asm("MARK_LOOP: \n\t");//start repeating asm("inc ebx \n\t");//increment pointer asm("inc edx \n\t");//increment pointer asm("movb al, [edx] \n\t");//load search-char asm("xor al, 0 \n\t");//test if null-char asm("jz MARK_SKIP \n\t");//jump if null-char asm("movb cl, [ebx] \n\t":::"cl");//load cont-char asm("xor cl, 0 \n\t");//test if null-char asm("jz MARK_SKIP \n\t");//jump if null-char asm("xor cl, al \n\t");//test if chars equal asm("jz MARK_LOOP \n\t");//repeat if chars equal ; asm("MARK_SKIP: \n\t");//stop repeating asm("mov [%0], ebx \n\t"::"a"(ppCont));//store [pCont] asm("mov [%0], edx \n\t"::"a"(ppSearch));//store [pSearch] ; //asm("mov eax, [0] \n\t");//start debugger ('_') } |
tommie-lie hat folgendes geschrieben: | Ich würde schon die C-Funktion eher so formulieren:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| void strUpTo(char **ppCont, char **ppSearch) { if ((!*ppCont) || (!*ppSearch)) return;
for (; **ppCont && **ppSearch && (**ppCont == **ppSearch); (*ppCont)++, (*ppSearch)++) ; } | |
Die Variante mit For ist schneller als mit While, nicht?
Ich habe auch mal versucht bei der ersten Funktion "strPaste()"
das 32Bit-Register in C auszunutzen:
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| void strPaste(char** ppBuff, char* sCont, int nCont) { char* pBuff = *ppBuff; int nPos = nCont / 4; void** lpBuff = (void**) pBuff; void** lsCont = (void**) sCont; for(; nPos > 0; nPos--) *lpBuff++ = *lsCont++; ; nPos = nCont % 4; pBuff = (char*) lpBuff; sCont = (char*) lsCont; for(; nPos > 0; nPos--) *pBuff++ = *sCont++; ; *ppBuff = pBuff; } |
Ob das was bringen würde, konnte ich noch nicht herausfinden.
Im Prinzip müssten die Zeiger immer um 4 erhöht werden
und die Werte als 32Bit umkopiert.
tommie-lie hat folgendes geschrieben: | Zitat: | Die Frage ist, ob ich die Constraints richtig verwendet habe..... | Soweit schon. |
Juhu, *freu*
Das mit dem Clubbern von cl sehe ich ein, war wohl etwas zuviel des Guten,
aber wegen den anderen Registern, habe ich den Text falsch verstanden?
GCC-Inline-Assembler-HowTo 5.3
Zitat: | We shoudn’t list the input and output registers in this list. Because, gcc knows that "asm" uses them (because they are specified explicitly as constraints). |
Oder ist es sicherer, alle Register, die man überhaupt verwendet,
vorher in der Clobberlist zu erwähnen?
Ich frage mich auch, ob es eine Unterschied macht, z.B. bei eingeschalteter Optimierung,
alle ASM-Anweisungen in eine Klammer zu schreiben oder jede in eine eigene.
Die Übergabe wird einfacher, wenn man mehr Zeilen nimmt, dafür ist es auch mehr zu schreiben.
Macht es auch einen funktionellen Unterschied,
ich meine, könnte gcc die ASM-Statements "umsortieren"?
Gruss aus Hamburg
|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: So 03.09.06 12:58
2cHH hat folgendes geschrieben: | tommie-lie hat folgendes geschrieben: | Im übrigen brauchst du auch gar keinen char ** um das zu erreichen, was du willst. In C sind Parameterübergaben "by value", insofern hast du recht, daß du, um einen char * "by reference" übergeben willst, char ** nehmen müsstest. | Doch, ich glaube schon. Die Funktionen, die ich mit Assembler zu optimieren versuche, |
Whoa. Ich dachte du würdest das nur aus Spaß an der Sache machen, und nicht, um tatsächlich den Code zu beschleunigen.
Dann mach mal einen Performancetest mit dem strPaste() von mir und einem "memmove(pBuff, sCont, nCont)" und verwende die Optimierung des Compilers. Bei mir liegt da gerne mal der Faktor 10 dazwischen. Und selbst die reine C-Schleife (dein auskommentierter C-Code) ist noch schneller als Assembler mit "4-Byte-auf-einmal-Kopieren". Unterschätze nicht den Optimizer von richtigen[tm] Compilern. Wirklich schneller bist du mit handoptimiertem Code nur in Grenzfällen.
Bei strUpTo() würde ich mit strspn() experimentieren. Die dürfte wahrscheinlich auch schneller sein, als das, was wir hier machen.
2cHH hat folgendes geschrieben: | In der ursprünglichen Funktion wird ein Zeiger nach Überprüfung des referrierten Inhaltes incrementiert. |
Ja, bei strUpTo(), denn da wird der Eingabepuffer direkt verwendet, um auch das Ergebnis an den Caller zurückzugeben. Bei strPaste() ist es aber nicht nötig (und je nach Kontext kontraproduktiv). Im Prinzip musst du das vom restlichen Code abhängig machen, aber zumindest bei strPaste() benötigst du zum Schreiben in den Speicher während des Kopierens keinen char ** und wahrscheinlich willst du ja, daß beim Caller der Zeiger immer noch auf das erste Zeichen des Strings zeigt, und nicht auf das letzte. Deswegen nehmn die Funktionen strcpy(), memcpy() und memmove() aus der Standardbibliothek auch keinen Pointer auf einen Pointer an, sondern nur direkt den Pointer. Damit bleibt für den Caller der Pointer dort, wo er ist, aber der Speicher wird trotzdem kopiert.
Für Dinge wie strpos() und strstr() wäre ein solcher Pointer auf einen Pointer erforderlich, aber die Standardbibliothek geht hier den Weg über den Rückgabewert, damit der Originalzeiger nicht manuell gesichert werden muss (um später den Speicher wieder freizugeben).
2cHH hat folgendes geschrieben: | auch das Pre-Increment ist beabsichtigt. |
Hm, eigenartig. Nun denn, nicht mein Code
2cHH hat folgendes geschrieben: | Die Variante mit For ist schneller als mit While, nicht? |
Nö, aber while-Schleifen sind mir unsympathisch.  In C sind for-Schleifen und while-Schleifen prinzipiell gleichwertig und sie lassen sich gegenseitig ineinander überführen (das geht nicht in jeder Sprache, in Delphi gibt es Fälle, in denen for tatsächlich schneller ist). Mit for-Schleifen arbeite ich lieber, weil man relativ kompakten Code schreiben kann, der aber sehr genau zeigt, was die einzelnen Teile der Schleife sind (Initialisierung, Abbruchbedingung und Schleifenkörper).
Deswegen schreibe ich lieber Quelltext 1: 2:
| for (; **ppCont && **ppSearch && (**ppCont == **ppSearch); (*ppCont)++, (*ppSearch)++) ; | als
Quelltext 1: 2: 3: 4: 5:
| while (**ppCont && **Search && (**ppCont == **ppSearch)) { (*ppCont)++; (*ppSearch)++; } | Alles nur der Übersichtlichkeit wegen
2cHH hat folgendes geschrieben: | Ich habe auch mal versucht bei der ersten Funktion "strPaste()"
das 32Bit-Register in C auszunutzen: Quelltext Ob das was bringen würde, konnte ich noch nicht herausfinden. |
Kommt darauf an. Die Idee, mehrere Bytes auf einmal zu kopieren, stammt nicht von mir, die hatten schon viele Leute vorher. Wenn du den Optimizer des Compilers anwirfst, findet der in der Regel derartige Konstrukte selbst und macht ein teilweises Loop-Unrolling: Die Schleife wird "aufgerollt" und in Vierer-Päckchen neu zusammengesetzt, wodurch 4 alte Schleifendurchläufe ein neuer Schleifendurchlauf werden. Je nach Qualität des Optimizers findet weiteres Loop-Unrolling statt, um beispielsweise architekturbedingte Eigenheiten von PRozessoren auszunutzen. Dann kann es auch gerne mal passieren, daß in einem Schleifendurchlauf zweimal jeweils 4 Byte kopiert werden, also pro Schleifendurchlauf 8 Byte in zwei Schritten, weil das einen höheren Durchsatz in der Pipeline des Prozessors bringt. Ich habe auch schon das Kopieren von 8 Byte in einem Schritt gesehen, indem die 8 Byte zunächst in ein Fließkommaregister der Coprozessoreinheit geladen wurden und anschließend wieder aus dem Coprozessor heraus, obwohl man im Allgemeinen überall erzählt bekommt, daß Fließkommaoperationen langsam seien. In diesem Fall war der Optimizer wohl davon überzeugt, daß dieses Vorgehen nicht langsamer war als ein viermaliges MOV, was bei der heutigen Integration von Coprozessor in den Hauptprozessor auch wenig verwunderlich ist.
Wie gesagt, unterschätze nie deinen Compiler.
2cHH hat folgendes geschrieben: | Das mit dem Clubbern von cl sehe ich ein, war wohl etwas zuviel des Guten, |
Sollte keine Nachteile (performancemäßig) bringen, aber verwirrt einen potentiellen Leser des Codes irgendwie
2cHH hat folgendes geschrieben: | Oder ist es sicherer, alle Register, die man überhaupt verwendet, vorher in der Clobberlist zu erwähnen? |
Weiß ich nicht genau, den Abschnitt habe ich vorher überlesen. Aber ich meinte eigentlich Register, die vorher verwendet werden, beispielsweise asm("movb al, [ebx] \n\t");//load cont-char aus Zeile 17. Der Compiler könnte ja auf die Idee kommen, das EAX Register schon früh in der Funktion (bzw durch die Parameterübergabe) mit ppCont zu füllen, aber hier wird es überschrieben. Aber um ehrlich zu sein, weiß ich es nicht genau und ich bin mir auch nicht sicher, daß dieses Howto in allen Fällen recht hat. Ich kann einen Input-Parameter auch auslesen, ohne seinen Wert zu verändern, und in diesem Fall könnte der Compiler den Wert die ganze Zeit über im Register halten und nicht im (langsameren) Speicher. Allerdings ist IBM der gleichen Meinung: Zitat: | Note here that %esi and %edi are used by "asm", and are not in the clobbered list. This is because it has been declared that "asm" will use them in the input operand list. The bottom line here is that if a register is used inside "asm" (implicitly or explicitly), and it is not present in either the input or output operand list, you must list it as a clobbered register. |
2cHH hat folgendes geschrieben: | Ich frage mich auch, ob es eine Unterschied macht, z.B. bei eingeschalteter Optimierung, alle ASM-Anweisungen in eine Klammer zu schreiben oder jede in eine eigene. |
Ich weiß es nicht (würde mich aber auch interessieren).
Der gcc-Parameter -S kompiliert nur und assembliert oder linkt nicht. Damit kannst du dir den erzeugten Code anschauen. Bei aktivierter Optimierung musst du aber aufpassen, möglicherweise hat der Compiler deinen Code geinlined um einen Funktionsaufruf zu sparen, dann findest du deinen Code woanders wieder. Kommentare bleiben aber erhalten.
Die Übergabe wird einfacher, wenn man mehr Zeilen nimmt, dafür ist es auch mehr zu schreiben.
2cHH hat folgendes geschrieben: | Macht es auch einen funktionellen Unterschied, ich meine, könnte gcc die ASM-Statements "umsortieren"? |
Umsortieren nicht, aber ich mache mir eher über die Belegung und Verwendung der Register Sorgen, wenn man ein Register zweimal verändert, es aber nur einmal in einer Clobber List hat, man aber zwischendurch über Constraints andere Variablen in Register lädt.
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
rpatsch
Hält's aus hier
Beiträge: 3
|
Verfasst: Fr 17.11.06 14:34
Herzlichen Dank für die schnelle Hilfe. Ich werde die Sache mal testen. Falls weitere Fragen auftauchen wende ich mich halt wieder an euch.
Nochmals vielen Dank
Rolf
|
|
|