Ideen zur Beschleunigung des Codes (Camera Bild einlesen)



  • Habs mal mit 100 Durchläufen nachgemessen:

    11,027s zu 0,313s. Das ist doch signifikant.



  • Hi,..

    Für so etwas gibt es die "Streaming Single Instruction Multible Data Extension" kurz SSE. Oder per GPGPU mittels VBO's.

    Damit kannst du Multiplikationen um etliches beschleunigen.
    Bei gängigen Graka's kommst Du damit bis auf 300 GFlops,... ( 300 * 10^9 floating point operations per second)

    Grüße



  • Das ist für Adressberechnungen aber mit Eulen auf Spatzen geschossen (oder Kanonen nach Athen getragen)

    Intelligente Algortihmen bringen mehr (s. mein Benchmark)



  • gastgast schrieb:

    Intelligente Algortihmen bringen mehr (s. mein Benchmark)

    nicht ganz weil:
    Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.



  • Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.



  • ogni42 schrieb:

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.

    Ich könnte wetten dass durch zuschalten von SSE sich doch mehr rausholen lässt.



  • ogni42 schrieb:

    Wenn du es kombinierst, erreichst du einen deutlich höheren Faktor mehr weil mit SSE(1,2,3,4.x) und insbesondere GPGPU deutlich mehr aus gleichbleibendem System und Code herauszuholen ist.

    Für allgemeine Verarbeitung gilt das ja, aber im Thread geht es nur um Adress- und Kopieroperationen.

    Ich erinnere mich an einen Artikel in der Ct wo über die Performance von GPU Algorithmen geredet wurde. Und ein maßgeblicher Faktor war dabei die Zeit die benötigt wird die Daten in die GPU zu bringen. Solange sich hier nicht die gesamte Arithmetik auf Matrizen und massive parallel Verarbeitung abbilden lässt lohnt es sich daher gar nicht GPU zu verwenden.

    Bei SSE weiß ich nichteinmal wie man das programmiert.

    Da ich aber in wesentlichen Datenstrukturen in andere Datenstrukturen abbilde und eigentlich gar nicht rechnen will, denke ich das Vektor/Matrizen Algorithmen der falsch Ansatz sind.



  • pospiech schrieb:

    Ich erinnere mich an einen Artikel in der Ct wo über die Performance von GPU Algorithmen geredet wurde. Und ein maßgeblicher Faktor war dabei die Zeit die benötigt wird die Daten in die GPU zu bringen. Solange sich hier nicht die gesamte Arithmetik auf Matrizen und massive parallel Verarbeitung abbilden lässt lohnt es sich daher gar nicht GPU zu verwenden.

    Matrizen und Vektoren sind ja nichts anderes als Schleifenkonstrukte.

    for( int y = 0; y < m_nSizeY; y++ )
        {
            for( int x = 0; x < m_nSizeX; x++ ) // hier wird imageStart immer um ein ganzes Pixel weiter geschoben
            {
                setPixel(x, y, imageIterator->r, imageIterator->g, imageIterator->b);
                imageIterator++;
            }
        }
    

    Dieser Teil lässt sich mMn sehr gut parallelisieren.



  • Dieser Teil lässt sich mMn sehr gut parallelisieren.

    Ja, aber nicht mit SSE und schon gar nicht mit GPGPU.

    Mit SEE macht es keinen Sinn, da in die SSE Register ein komplettes Wort gelegt werden muss und als Ergebnis aber kein weiteres vollständiges Datenwort heraus kommt, sondern auf drei unterschiedliche Worte verteilt wird.

    Mit GPGPU macht das keinen Sinn, weil es ja nur um das Umkopieren der Daten geht und die liegen nun mal im Hauptspeicher des Rechners und nicht im Speicher der GPU. GPGPU beschleunigt im Wesentlich Matrixoperationen wie MulAdd, Inversion etc.

    Alternative wäre, die Schleife zu entrollen (z.B. mit OpenMP). Das kann, muss aber nicht zu schnellerer Verarbeitung führen. Das hängt von der Organisation der Caches ab. Dazu gibt es ein gutes Paper "What every programmer should know about memory".



  • ogni42 schrieb:

    Dieser Teil lässt sich mMn sehr gut parallelisieren.

    ....

    Diese Antwort bezog sich nur auf die Schleifen nicht auf SSE oder GPGPU

    Ja muss man halt durchprobieren, gibt ja nicht nur openmp..



  • Hi,...

    Also die doppelten schleifen durchläufe brauchen Vieeeel zu viel zeit,...
    Die Zeilen liegen bei beiden doch so oder so hintereinander im Speicher
    Und wie ich das verstanden habe muss nur ein Byte, und zwar x, entfernt werden.

    struct RGBX
    { 
      u8 r;
      u8 g;
      u8 b;
      u8 x;
    };
    
    struct RGB
    {
      u8 r;
      u8 g;
      u8 b;
    };
    
    RGBX* startmemRGBX= GetStartOfRGBX();
    RGB*  startmemRGB = GetStartOfRGB();
    
    size_t RGBXIndex =0;
    size_t RGBIndex  =0;
    
    for(int i=0; i<pixelanzahl; i++)
    {
    memcpy_s(startmemRGB+RGBIndex,sizeof(RGB),startmemRGBX+RGBXIndex,sizeof(RGBX));
    
    RGBXIndex++;
    RGBIndex++;
    };
    

    grüüße



  • Nein, im Ursprungscode wird das Bild in drei separate Felder geschrieben. Die Daten sind auch 24 bit weise organisiert.



  • Ja mein gott,..
    dann halt:

    u8* redmemory  = getStartOfRedMemory();
    u8* bluememory = getStartOfBlueMemory();
    u8 greenmemory = getStartOfGreenMemory();  
    
    RGBX* RGBXMemory = getStartOfRGBXMemory();
    
    size_t ColorIndex=0;
    size_t RGBXIndex=0;
    
    for( int i=0; i<number_of_pixels; i++)
    {
      memcpy_s(redmemory  + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 2,1);
      memcpy_s(bluememory + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 1,1);
      memcpy_s(greenmemory+ ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 0,1);
    RGBXIndex++;
    ColorIndex++;
    }
    

    grüüße



  • zeusosc schrieb:

    Ja mein gott,..
    dann halt:

    memcpy_s(redmemory  + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 2,1);
      memcpy_s(bluememory + ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 1,1);
      memcpy_s(greenmemory+ ColorIndex,1,((char*)RGBXMemory + RGBIndex) + 0,1);
    

    memcpy_s braucht man nicht 🙂
    Dann läuft das ganze noch einen Tacken schneller.



  • Die memcpy_s Version ist bei mir deutlich langsamer, die mit memcpy ein Drittel schneller:

    `

    Copying with Mul based address calculation: Time elapsed 11.236s

    Copying with Add based address calculation: Time elapsed 0.313s

    Copying with memcpy_s based address calculation: Time elapsed 4.844s

    Copying with memcpy based address calculation: Time elapsed 0.203s

    `

    Also macht memcpy das Rennen. Hab' ich wieder was gelernt, ich hatte vermutet, dass bei wortweisem Kopieren da kein Unterschied ist. Danke für den Tipp!



  • ogni42 schrieb:

    ...

    Könntest du grad den Code nochmal posten, so langsam verliert man den Überblick wo welche Veränderungen enthalten sind.

    0.2s je Frame ist doch ein wenig langsam.
    uEye cams können doch wohl mehr als 5frames/s?
    Oder ist es die Messung im Debug Modus?



  • Hier nochmal mein Testcode:

    #include <iostream>
    #include <Timer.h>
    
    using std::cout;
    using std::endl;
    
    const int m_size_x = 1024;
    const int m_nSizeX = 1024;
    const int m_nSizeY = 1024;
    
    const int chunkSz = 5*m_nSizeX*m_nSizeY;
    int largeChunk[chunkSz];
    
    char red[chunkSz];
    char green[chunkSz];
    char blue[chunkSz];
    
    // Diese struct muss der organisation eines Pixels im Speicher entsprechen
    struct RGBPixel32
    {
        unsigned char r;
        unsigned char g;
        unsigned char b;
        unsigned char x; // unused, 32bit alignment
    };
    
    struct RGBPixel24
    {
        unsigned char r;
        unsigned char g;
        unsigned char b;
    };
    
    struct MonoPixel8
    {
        unsigned char g;
    };
    
    struct MonoPixel10Left // left aligned
    {
        unsigned short pad  :    6; // padding bits
        unsigned short g    :   10; // 10bit grey value
    };
    
    struct MonoPixel10Right
    {
        unsigned short g    :   10; // 10bit grey value
        unsigned short pad  :    6; // padding bits
    };
    
    void pixelToRGB(char* pPixelPointer, int  & r, int & g, int & b)
    {
        r = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+2)));
        g = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+1)));
        b = static_cast<int>(*(reinterpret_cast<char*>(pPixelPointer+0)));
    }
    
    inline int ArrPos(int x, int y)
    {
        // y + height * x
        return (x + m_size_x * y);
    } 
    
    inline void setPixel(int x, int y, int r, int g, int b)
    {
        int pos = ArrPos(x,y);
        red[pos] = r;
        green[pos] = g;
        blue[pos] = b;
    }
    
    void checkPerfMult(void)
    {
        int m_nBytesPerPixel = 4;
        int nPitch = 4;
        void* pMemVoid = (void*)largeChunk;
        char* pPixelPointer;
        for ( int x = 0; x < m_nSizeX; x++ ) 
        {
            for ( int y = 0; y < m_nSizeY; y++ ) 
            {
                // (x*bytes) + (y * sizex)
                int r;
                int g;
                int b;
                long lMemoryPixelOffset = ((long)x * m_nBytesPerPixel) + ((long)y * nPitch );
                pPixelPointer = (reinterpret_cast<char*> (pMemVoid) + (lMemoryPixelOffset)) ;
    
                pixelToRGB(pPixelPointer, r, g, b);
                setPixel(x,y,r,g,b);
            }
        } 
    }
    
    void* getImageStart(void)
    {
        return largeChunk;
    }
    
    void checkPerfAdd(void)
    {
        // jetzt der Zugriff
        RGBPixel32* imageIterator = reinterpret_cast<RGBPixel32*>(getImageStart()); // Hier den Start des Bildes im Speicher bestimmen
        int targetIterator = 0;
        for( int y = 0; y < m_nSizeY; y++ )
        {
            for( int x = 0; x < m_nSizeX; x++ ) // hier wird imageStart immer um ein ganzes Pixel weiter geschoben
            {
    //            setPixel(x, y, imageIterator->r, imageIterator->g, imageIterator->b);
                red[targetIterator] = imageIterator->r;
                green[targetIterator] = imageIterator->g;
                blue[targetIterator] = imageIterator->b;
                imageIterator++;
                targetIterator++;
            }
        } 
    }
    
    char* getStartOfRedMemory()
    {
        return red;
    }
    
    char* getStartOfGreenMemory()
    {
        return green;
    }
    
    char* getStartOfBlueMemory()
    {
        return blue;
    }
    
    RGBPixel32* getStartOfRGBXMemory()
    {
        return reinterpret_cast<RGBPixel32*>(getImageStart());
    }
    
    void checkPerfMemCpy_s()
    {
        char* redmemory  = getStartOfRedMemory();
        char* bluememory = getStartOfBlueMemory();
        char* greenmemory = getStartOfGreenMemory();  
    
        RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    
        size_t ColorIndex=0;
        size_t RGBXIndex=0;
        int  number_of_pixels = m_nSizeX*m_nSizeY;
    
        for( int i=0; i<number_of_pixels; i++)
        {
            memcpy_s(redmemory  + ColorIndex,1,((char*)RGBXMemory + RGBXIndex) + 2,1);
            memcpy_s(bluememory + ColorIndex,1,((char*)RGBXMemory + RGBXIndex) + 1,1);
            memcpy_s(greenmemory+ ColorIndex,1,((char*)RGBXMemory + RGBXIndex) + 0,1);
            RGBXIndex++;
            ColorIndex++;
        } 
    }
    
    void checkPerfMemCpy()
    {
        char* redmemory  = getStartOfRedMemory();
        char* bluememory = getStartOfBlueMemory();
        char* greenmemory = getStartOfGreenMemory();  
    
        RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    
        size_t ColorIndex=0;
        size_t RGBXIndex=0;
        int  number_of_pixels = m_nSizeX*m_nSizeY;
    
        for( int i=0; i<number_of_pixels; i++)
        {
            memcpy(redmemory  + ColorIndex,((char*)RGBXMemory + RGBXIndex) + 2,1);
            memcpy(bluememory + ColorIndex,((char*)RGBXMemory + RGBXIndex) + 1,1);
            memcpy(greenmemory+ ColorIndex,((char*)RGBXMemory + RGBXIndex) + 0,1);
            RGBXIndex++;
            ColorIndex++;
        } 
    }
    
    int main( void )
    {
        const int runs = 100;
        {
            cout << "Copying with Mul based address calculation: ";
            Timer t;
            for (int i = 0; i<runs; i++)
            {
                checkPerfMult();
            }
        }
        cout << endl;
        {
            cout << "Copying with Add based address calculation: ";
            Timer t;
            for (int i = 0; i<runs; i++)
            {
                checkPerfAdd();
            }
        }    
        cout << endl;
        {
            cout << "Copying with memcpy_s based address calculation: ";
            Timer t;
            for (int i = 0; i<runs; i++)
            {
                checkPerfMemCpy_s();
            }
        }
        cout << endl;
        {
            cout << "Copying with memcpy based address calculation: ";
            Timer t;
            for (int i = 0; i<runs; i++)
            {
                checkPerfMemCpy();
            }
        }
        cout << endl;
    }
    

    Und hier die Compileroptionen von VisualC++ 2008:
    /O2 /Ob2 /Oi /Ot /GL /I "C:\work\learning\playGroundForCpp\playGroundForCpp" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /Gy /Fo"Release\\" /Fd"Release\vc90.pdb" /W3 /nologo /c /Zi /TP /errorReport:prompt

    Bitte beachten: Ich teste nur auf einem Laptop mit lahmem Speicher und 1.6GHz CPU

    EDIT: Die Messungen sind mit dem Release Build gemacht.



  • ogni42 schrieb:

    Bitte beachten: Ich teste nur auf einem Laptop mit lahmem Speicher und 1.6GHz CPU

    Achso.. ist es vielleicht ein Mehrkernprozessor?



  • Ja, Core 2 Duo@1,6GHz.



  • 10000 Durchgänge:
    ohne parallel for in Zeile 14: ~4000
    mit pragma #omp parallel for in Zeile 45: ~1300

    Dazu OpenMP Support im Compiler anschalten.

    Skaliert um Faktor 3 bei 4 Prozessoren... ist ein wenig lausig.. aber OpenMP ist halt nicht so besonders.

    Man müsste das Alignment auf 128 bringen, da bietet der Intel Compiler ein wenig bessere Tools.

    Benche mal mit deinen Einstellungen.
    Ich habe timer.h nicht.

    #include <iostream>
    #include <ctime>
    #include <cstdlib>
    #include <cstdio>
    
    int main( void )
    {
    	clock_t start=0,stop=0;
    
    	const int runs = 10000;
    ....
    		cout << "Copying with memcpy based address calculation: ";
    		start=clock();
    		#pragma omp parallel for
    		for (int i = 0; i<runs; i++)
    		{
    			checkPerfMemCpy();
    		}
    		stop = clock();
    ....
    	cout << (float) 1000*(stop-start) / CLOCKS_PER_SEC <<endl;
    }
    

Anmelden zum Antworten