Frage zu MMX und Ladebefehlen (erledigt)



  • Hier stand ne Frage zu MMX.
    Damit habe ich mich wohl übernommen. (Schaffe ich eh nicht zu implementieren)

    Somit erledigt.



  • Dieser Thread wurde von Moderator/in rüdiger aus dem Forum Rund um die Programmierung in das Forum Assembler verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • Was hattest du denn vor? Die Community hätte dir schon den Rücken gestärkt!



  • Andreas MMX schrieb:

    Was hattest du denn vor? Die Community hätte dir schon den Rücken gestärkt!

    Ich wollte einen Filter für die Bildbearbeitung programmieren.
    Ich versuche es erst mal in ner Hochsprache (C).
    Wenns klappt kann ich es ja wieder in Assembler (MMX) versuchen.

    Ich hatte mir wohl zu viel vorgenommen es sofort in Assembler mit MMX zu versuchen, da ich weder von Assembler noch von MMX eine Ahnung habe.
    (Erst ein Schritt nach dem anderen 😃 )



  • nur 119min57s um zur vernunft zu kommen, nicht schlecht 🤡

    aber ja, erstmal sollte es in einer hochsprache laufen
    danach empfehle ich dir loop zu unrollen, wenn du 4 oder 8mal das selbe machst, hast du schon ne gute basis.

    weiter ist es manchmal hilfreich sich ne kleine math lib mit primitiven funktionen zu schreiben die du brauchst, z.b.

    color4 c=AddSaturate(Pixel0,Pixel1);

    auch wenn alles dahinter erstmal nur in c ist, hilft es dir den code auf SIMD auszulegen (und gerade beim loop unrollen nicht massig Copy&Paste code zu haben der unuebersichtlich ist).

    neben MXX kannst du dir auch SSE mal anschauen, das ist mittlerweile recht gut geeignet fuer sowas, wobei der unalligned load und writethrough von mmx immer noch zucker sind 🙂



  • Also, auf der Hochsprache läufts jetzt.

    Soweit ich das verstanden habe kann ich bei MMX 8 Byte (Graustufenpixel des Bildes) gleichzeitig verarbeiten.

    Wenn ich also nur alle Pixel des Bildes mit einem gewissen wert z.B.
    0x41 addieren möchte, so würde ich jeweisl 8 Pixel (=8 Byte) des Bildes laden und mit einem Befehl die 0x41 dazuaddieren.

    Angenommen ich hätte schon die 8 Byte für die 8 Pixel des bildes geladen etwa so: (in Register MM1)

    01010101 01011000 01011100 11111000 11000011 10010011 00001111 01010101

    dann müste ich ja die 0x41 in jedes dieser 8 Byte laden (in Register MM2)
    01000001 01000001 01000001 01000001 01000001 01000001 01000001 01000001

    Nun könmnte ich einfach addieren (Dabei würde so wie ich verstanden habe jedes
    Byte[i] aus MM1 mit jedem Byte[i] aus MM2 addiert werden.)

    Meine Frage ist nun, wie ich diese 0x41 in jedes der 8 Byte laden kann.
    Geht das nur mit laden und nach links shiften, laden nach links shiften usw. oder geht das in weniger Befehlen?



  • 0x01010101 * 0x41 = 0x41414141



  • Ich hab leider so gut wie keine Ahnung von MMX im Speziellen (und eigentlich auch nicht von Assembler im Allgemeinen), aber nachdem ich mir den Wiki-Artikel angesehen habe weiß ich zumindest, dass dieser voller Fehler steckt. Also Intel-Manuals konsultiert und dieses Frickelwerk zusammengeschustert:

    #include <iostream>
    
    template<typename T>
    void printValues(T val, unsigned numVals)
    {
    	for(unsigned i=0; i!=numVals; ++i)
    		std::cout << (int)*(val+i) << " ";
    	std::cout << std::endl;
    }
    
    int main()
    {
    	unsigned char op1[8] = {8, 16, 32, 64, 128, 200, 255, 9};
    	unsigned char op2[8] = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41};
    	__declspec(align(16)) unsigned char fpuState[512] = {0};
    
    	std::cout << "Before: ";
    	printValues(op1, 8);
    
    	__asm
    	{
    		FXSAVE fpuState
    		MOVQ MM0, op1
    		MOVQ MM1, op2
    		PADDUSB MM0, MM1
    		MOVQ op1, MM0
    		FXRSTOR fpuState
    	}
    
    	std::cout << "After: ";
    	printValues(op1, 8);
    
    	return 0;
    }
    

    Ausgabe:

    Before: 8 16 32 64 128 200 255 9
    After: 73 81 97 129 193 255 255 74

    Entgegen dem Wikipedia-Artikel muss der Speicherbereich für FXSAVE 512 Byte groß sein, nicht 512 Bit. Außerdem muss dieser Speicherbereich an einer 16-byte boundary ausgerichtet sein, sonst fliegt eine Exception. Wie ich nach einiger Googelei herausgefunden habe, kann ich das mit MSVC wie im oben Code ersichtlich mit __declspec(align(16)) machen, das löst das Problem. Es gibt aber auch noch die Instructions FSAVE und FRSTOR, die nur die FPU-Daten sichern, was meines Erachtens für diesen Code ausreichen müsste. Dann muss der Speicher auch nicht an einer 16-Byte Grenze ausgerichtet sein und es ist auch schneller.

    Und op2 habe ich halt schon zuvor vorbereitet und dann in einem Schwupps ins Register geladen, eine andere Möglichkeit sehe ich nicht. Die MMX-MOVs setzen die oberen Bytes auf 0, wenn man einen Operanden hat der kleines als ein Quadword ist, deshalb kann man 0x41 wohl nicht "der Reihe nach" hineinkopieren, indem man immer wieder 1 Byte nach links shiftet. Aber im Endeffekt ist das auch egal, der Wert muss ja nur einmal vorbereitet werden und kann dann für die ganzen Additionen im Register verbleiben.

    Und ja, ich hab hier Inline-Assembler benutzt, aber wie ich im Intel-Manual gesehen habe, gibt es dafür wohl auch Compiler-Intrinsics, für das hier benötigte PADDUSB wäre das beim Intel C/C++ Compiler z.B. "__m64_mm_adds_pu8(__m64 m1, __m64 m2)".

    Für MSVC habe ich auch noch eine Liste mit den MMX-Intrinsics gefunden:
    http://msdn.microsoft.com/en-us/library/ccky3awe(VS.71).aspx
    ...den obigen Code lasse ich trotzdem mal so stehen. Ist zwar nicht schön, aber man versteht's zumindest.

    Und hier schließlich noch der Link zu den Intel-Manuals: http://www.intel.com/products/processor/manuals/index.htm
    Volume 2A und 2B sind wie dort auch ersichtlich eine gute Befehlsreferenz, Volume 1 gibt nen generellen Überblick über die Technologie, ein Kapitel zu MMX ist auch enthalten.



  • Dein Beispiel ist wirklich gut, weil ich jetzt einige Dinge wirklich verstehe!
    (Irgendwie findet man nur ne Menge Befehle aber kaum ein funktionieredes Beispiel)

    Eine Frage habe ich aber noch: Wie ich dies in ein MMX Register reinbekomme, wenn 0x41 nur einmal angegeben ist. (also nur in einem einzigen unsigned char).

    Wie mache ich aus:
    00000000 00000000 00000000 00000000 00000000 00000000 00000000 01000001

    Folgendes mit möglichst wenigen Befehlen:

    01000001 01000001 01000001 01000001 01000001 01000001 01000001 01000001



  • Du hast meinen Beitrag ja mal komplett ignoriert.

    0x01010101 * 0x41 = 0x41414141

    probier es doch mal aus.



  • imul schrieb:

    Du hast meinen Beitrag ja mal komplett ignoriert.

    0x01010101 * 0x41 = 0x41414141

    probier es doch mal aus.

    Ok das funktioniert! Aber wie macht man es, wenn das 0x41 nicht fest vorgegeben ist, sondern vom Benutzer eingegeben werden kann?



  • ???

    unsigned char in=0x41;
    unsigned int  out = 0x01010101 * in;
    

    in assembler wäre der imul befehlr interessant für dich.



  • Danke!

    Aber sehe ich das richtig, dass es keinen MMX Befehl gibt der das kann?



  • kann ich dir so nicht beantworten, da ich mit mmx eine weile nichts mehr gemacht habe. aber ich denke es wird einen multiplikationsbefehl geben im mmx befehlssatz. 2 imul befehle sind aber auch ausreichend flott zur not.



  • Danke habs jetzt so wie ich wollte!

    Ich habs so nach "imul"s Idee umgesetzt:

    unsigned char in=0x41;
    unsigned int out = 0x01010101 * in;



  • Ist so eine Multiplikation nicht langsamer als ein paar Bitshifts?

    unsigned char in = 0x41;
    unsigned int out = (in << 24) | (in << 16) | (in << 8) | in;
    


  • Finetuner schrieb:

    Ist so eine Multiplikation nicht langsamer als ein paar Bitshifts?

    unsigned char in = 0x41;
    unsigned int out = (in << 24) | (in << 16) | (in << 8) | in;
    

    Danke!
    Aber es geht mir nicht darum das super turbo schnelle Programm zu schreiben.
    Ich möchte einfach nur verstehen wie man MMX prinzipiell für die Verarbeitung von Multimedia Daten verwendet. (Und an einem Filter ausprobieren)
    Und das habe ich jetzt.



  • ich finde da sind die befehle gut erklaert.



  • Finetuner schrieb:

    Ist so eine Multiplikation nicht langsamer als ein paar Bitshifts?

    unsigned char in = 0x41;
    unsigned int out = (in << 24) | (in << 16) | (in << 8) | in;
    

    nein



  • Woran liegt denn das Problem die 8Bytes direkt in das Register zu laden? Ist doch klar, dass die MMX Befehle mit 8Bytes in einem Durchgang arbeiten, darauf sind sie doch ausgelegt.

    Und wie man schnell einen Wert in mehrere Bytes bekommt hat imul ja schon gezeigt.


Anmelden zum Antworten