AVX Intrinsics - SPeicherzugriffsfehler



  • Ich versuche momentan eine einfache Vektoraddition mit AVX zu relaisieren und nutze hierfür die GCC-Intrinsics

    Der nachfolgende Code produziet bei der Ausführung unregelmäßig Speicherzugriffsfehler. Ich habe keine Idee, warum dass so ist. Vor allem da ich fast exakt den selben COde schon mit SSE2 geschrieben habe und da keine Fehler auftreten.

    Hier richte ich den SPeicher sogar noch an 32-Byte Grenzen aus, was für AVX ja nicht nötig sein sollte.

    Wenn einer einen Fehler sieht, bitte korrigieren,
    danke

    #include <iostream>
    #include <malloc.h>
    #include "Timer.h"
    
    #include <immintrin.h>
    
    using namespace std;
    
    void add_vector(double *a, double *b, double *c, const unsigned int& N)
    {
    	__m256d av, bv, cv;
    
    	for (unsigned int i = 0; i < N; i= i+4)
    	{
    		bv = _mm256_loadu_pd(&b[i]);
    		cv = _mm256_loadu_pd(&c[i]);
    
    		av = _mm256_add_pd(bv, cv);
    
    		_mm256_store_pd(&a[i], av);
    	}
    
    }
    
    int main()
    {
    	Timer timer;
    
    	const unsigned int N = 40000;
    	double *a, *b, *c;
    	a = (double*) memalign(N * sizeof(double), 32);
    	b = (double*) memalign(N * sizeof(double), 32);
    	c = (double*) memalign(N * sizeof(double), 32);
    
    	for (unsigned int i = 0; i < N; i++)
    	{
    		c[i] = b[i] = i;
    	}
    
    	const unsigned int iterations = 100;
    
    	timer.start();
    	for (unsigned int i = 0; i < iterations; i++)
    	{
    		add_vector(a, b, c, N);
    	}
    	timer.stop();
    
    	for (unsigned int i = N-5; i < N; i++)
    	{
    		cout << a[i] << endl;
    	}
    
    	timer.printStoppedTime();
    
    	return 0;
    }
    


  • Ich nehme an du möchtes es schnell haben... warum nutzt du dann unsigned int statt int für eine Schleifenvariable? Das unsigned muss zur Laufzeit sichergestellt werden. Da du auch ohnehin nur vorwärts iterierst ist das unsigned wiederrum unnötig. Genau so wie pass by Reference und das const[/c. Damit gewinnst du nichts. Kostet mehr Aufwand und sieht viel hässlicher aus als einfach einen [c]int zu passen.

    Nur so nebenbei 🙂


  • Mod



  • aasdsad schrieb:

    ... int für eine Schleifenvariable? Das unsigned muss zur Laufzeit sichergestellt werden...

    Häh?



  • Tachyon schrieb:

    aasdsad schrieb:

    ... int für eine Schleifenvariable? Das unsigned muss zur Laufzeit sichergestellt werden...

    Häh?

    Soweit ich wei unterscheidet der Compiler an dieser Stelle ; i < N; zwischen signed und unsigned, wobei unsigned schneller ist, da er sich nicht weiter darum kümmern muss. Overflow ist sowieso ein undefiniertes verhalten, anders als bei unsigned. Es gibt Operationen in dennen unsigned schneller ist, weil er optimieren kann (*4 = Bitshift). Korrigiere mich bitte wenn ich daneben liege.


  • Mod

    aasdsad schrieb:

    Soweit ich wei unterscheidet der Compiler an dieser Stelle ; i < N; zwischen signed und unsigned, wobei unsigned schneller ist, da er sich nicht weiter darum kümmern muss. Overflow ist sowieso ein undefiniertes verhalten, anders als bei unsigned. Es gibt Operationen in dennen unsigned schneller ist, weil er optimieren kann (*4 = Bitshift). Korrigiere mich bitte wenn ich daneben liege.

    Wo soll denn hier ein Unterschied sein? Bei unsigned muss er sowieso nix machen (wegen der Darstellung), bei signed braucht er auch nix machen (da undefiniert), also macht er auch nix.

    P.S.: @Threadersteller: Den Fehler hast du durch meinen Tipp gefunden, oder?



  • SeppJ schrieb:

    ... nix machen (wegen der Darstellung) ...

    Ich weiß nicht ob ich das hier so unterschreiben würde. Die Darstellung ändert sich nicht von allein sobald ein overlow vorliegt. Ein Ring der wieder das erste Bit kippt ist ja etwas anderes als eine Addition. Im Prinzip weiß ich es nicht sooo genau. Alles reine Vermutung. Vielleicht weiß ja jemand was etwas genauer was die Laufzeitumgebung an diesem Punkt treibt.



  • aasdsad schrieb:

    Soweit ich wei unterscheidet der Compiler an dieser Stelle ; i < N; zwischen signed und unsigned, wobei unsigned schneller ist, da er sich nicht weiter darum kümmern muss.

    Nein.

    Overflow ist sowieso ein undefiniertes verhalten, anders als bei unsigned.

    Hä? Anders als unsigned? Was meinst du damit?

    Es gibt Operationen in dennen unsigned schneller ist, weil er optimieren kann (*4 = Bitshift).

    http://ideone.com/ACqJgw





  • Sone schrieb:

    Es gibt Operationen in dennen unsigned schneller ist, weil er optimieren kann (*4 = Bitshift).

    http://ideone.com/ACqJgw[/quote]

    Jetzt ersetze das Zweite durch ein Bitshift. Ich würde auch definitiv ein int verwenden als ein char, weil int mehr zur Hardware passt.



  • aasdsad schrieb:

    Hab auf die Schnelle folgendes gefunden:
    http://stackoverflow.com/questions/410982/is-there-a-difference-in-term-of-performance-between-unsigned-int-and-int-on

    Die erste Antwort ist ARM spezifisch.


  • Mod

    aasdsad schrieb:

    SeppJ schrieb:

    ... nix machen (wegen der Darstellung) ...

    Ich weiß nicht ob ich das hier so unterschreiben würde. Die Darstellung ändert sich nicht von allein sobald ein overlow vorliegt. Ein Ring der wieder das erste Bit kippt ist ja etwas anderes als eine Addition. Im Prinzip weiß ich es nicht sooo genau. Alles reine Vermutung. Vielleicht weiß ja jemand was etwas genauer was die Laufzeitumgebung an diesem Punkt treibt.

    Mit Darstellung meine ich so etwas wie Zweierkomplement. Egal, ob signed oder unsigned, Addition im Zweierkomplement kommt ganz ohne Fallunterscheidung aus. Die simplen Rechenregeln sind fest verdrahtet im Rechenwerk und können genau gleich für jede Zahlenkombination benutzt werden, egal ob Überlauf oder kein Überlauf. Und als Bonus läuft der signed Datentyp auch noch wie intuitiv erwartet über. Toll, oder?



  • Sone schrieb:

    ...

    Was mich jetzt noch total wundert ist, dass er überhaupt Zeit braucht. Ich meine, dass dein Code nichts anderes macht als die Zeit auszugeben. Wenn die Optimierungen an sind sollten auch nur diese Zeilen übrig bleiben:

    #include <iostream>
    #include <ctime>
    
    int main()
    {
        clock_t first = clock();
        std::cout << "unsigned braucht " << clock() - first << '\n';
    
        first = clock();
        std::cout << "signed braucht " << clock() - first << '\n';
    }
    

    Eigenartig... ich würde erwarten, dass er im Release zwei mal 0 bringt. tmp wird nirgendwo benutzt,wozu soll der Compiler diese Variable nach der Optimierung behalten??


  • Mod

    Änderungen an volatile-Variablen dürfen nicht einfach wegoptimiert werden.



  • SeppJ schrieb:

    Änderungen an volatile-Variablen dürfen nicht einfach wegoptimiert werden.

    Auch nicht vom Linker, er sieht ja, dass das vollständige Programm diese Variable an keiner Stelle nutzt.



  • aasdsad schrieb:

    Auch nicht vom Linker, er sieht ja, dass das vollständige Programm diese Variable an keiner Stelle nutzt**?**

    Sollte eine Frage sein..



  • Falls es jemanden interessiert: Ich kann das Verhalten hier mit gcc 4.7 auf keiner Optimierungsstufe nachvollziehen. Beide Varianten brauchen in allen Fällen genau gleich lang, was daran liegt, dass genau der gleiche Maschinencode erzeugt wird.

    Ich weiß nicht, was die ideone-Leute da in ihren Compileroptionen haben.

    @aasdsad: Der Compiler weiß nicht, dass es sich dabei nicht um ein magisches Outputregister für irgendwas handelt. Soweit der das beurteilen kann (darf), hängt da ne IR-LED hinter, die genau im richtigen Rhythmus betrieben werden soll.



  • Um wieder zu mir zu führen, ich hab tatsächlich nur die Parameter von

    void *memalign(size_t boundary, size_t size);

    durcheinandergebracht?

    ich geh in die ecke und schäm mich. Danke an Seppj.

    Zu der Anwendung:
    Das hier ist gerade nur ein wenig Spielerei um mich auf die eigentliche Anwendung
    vorzubereiten. Deshalb spielt die Performance hier in diesem Beispiel noch keine Rolle.

    ABer vielen Dank für die rege Diskussion


Anmelden zum Antworten