Ideen zur Beschleunigung des Codes (Camera Bild einlesen)



  • 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;
    }
    


  • Jetzt wird es interessant. Zieht man das OpenMP Pragma in die Kopierroutine, wird auf einmal die non-memcpy Version schneller (1000 Durchläufe):

    `

    Copying with Add based address calculation: Time elapsed 2.984s

    Copying with memcpy based address calculation: Time elapsed 4.641s

    Drücken Sie eine beliebige Taste . . .

    `

    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;
    
    #pragma omp parallel for
        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++;
        } 
    }
    
    void checkPerfAdd(void)
    {
        // jetzt der Zugriff
        RGBPixel32* imageIterator = reinterpret_cast<RGBPixel32*>(getImageStart()); // Hier den Start des Bildes im Speicher bestimmen
        int targetIterator = 0;
    #pragma omp parallel for
        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++;
            }
        } 
    }
    

    Dafür habe ich jetzt keine Erklärung, höchstens eine Vermutung: OpenMP packt Schleifendurchläufe in parallele Threads. Dabei könnten sich bei der memcpy-Version die Cores beim Zugriff auf die Caches in die Quere kommen. Das ist mit Sicherheit aber sehr abhängig davon, auf welchem Zielsystem das Programm läuft.

    Dennoch, von ursprünglich 100ms Laufzeit/Bild sind wir jetzt bei unter 5ms/Bild. Das ist doch schon was 😃

    EDIT:
    Fazit: Die nicht parallelisierte memcpy Version ist die Beste.



  • ogni42 schrieb:

    [/cpp]

    Dafür habe ich jetzt keine Erklärung, höchstens eine Vermutung: OpenMP packt Schleifendurchläufe in parallele Threads. Dabei könnten sich bei der memcpy-Version die Cores beim Zugriff auf die Caches in die Quere kommen. Das ist mit Sicherheit aber sehr abhängig davon, auf welchem Zielsystem das Programm läuft.

    Dennoch, von ursprünglich 100ms Laufzeit/Bild sind wir jetzt bei unter 5ms/Bild. Das ist doch schon was 😃

    Warte mal... in der Funktion musst du ein wenig aufpassen dass zwei Threads nicht gleichzeitig auf eine Adresse zugreifen.. das geht ein wenig anders. Es ist langsamer weil OpenMP die Zugriffe koordinieren muss.

    Ich dachte du rufst X-Mal diese Funktion auf und holst dir damit mehrere Bilder heraus. Deswegen habe ich das omp vor die funktion gemacht.

    Wie genau läuft jetzt der Vorgang ab??

    Wenn du es wie an der Stelle 13 und 29 (dein Codeposting) brauchst, dann muss es ganz anders angegangen werden.

    Dann wird das Bild als RAM-Block in n-Blöcke aufgeteilt wobei jeder Block von einem Thread abgegrast wird.



  • Der Aufruf (x-Mal, x=100 oder 1000) ist nur, damit man mit clock() vernünftig messen kann. Um die Funktion auszuführen muss die Funktion nur einmal aufgerufen werden.

    Aber ich verstehe, was Du meinst, Die Zugriffe müssen synchronisiert werden, weil sonst das Inkrementieren der Zähler zu indizierungsfehlern führen kann. Ich schlage mal in der Hilfe nach und baue das ein.

    Oder muss ich sonst noch was beachten?

    EDIT: Ich fürchte, dass ich hier etwas Hilfe von einem erfahreneren OpenMP-Nutzer brauche: Die Indexvariablen werden ja von den Threads geteilt und somit ist das in der Form IMHO nicht OpenMParallelisierbar. Meine Idee wäre, für eine feste Anzahl Threads (das geht mit OpenMP) den Code umzuschreibe, so dass bei n Thredas je Thread 1/n-tel des Bildes derart bearbeitet wird, dass (n=2) Thread0 die obere Hälfte und Thread1 die untere Hälfte bearbeitet.

    Wäre das so praktikabel?



  • Jetzt muss ich mal was ganz doofes in die Runde fragen. Ist in diesem Fall:

    memcpy(redmemory  + ColorIndex,((char*)RGBXMemory + RGBXIndex) + 2,1);
    memcpy(bluememory + ColorIndex,((char*)RGBXMemory + RGBXIndex) + 1,1);
    memcpy(greenmemory+ ColorIndex,((char*)RGBXMemory + RGBXIndex) + 0,1);
    

    die Werte

    sizeof(i)==sizeof(RGBXIndex)==sizeof(ColorIndex)
    

    size_t ist doch wie int eine architekturabhängige Größe oder nicht?
    so dass man im Prinzip:

    memcpy(redmemory  + i,((char*)RGBXMemory + i) + 2,1);
    memcpy(bluememory + i,((char*)RGBXMemory + i) + 1,1);
    memcpy(greenmemory+ i,((char*)RGBXMemory + i) + 0,1);
    

    schreiben kann?
    Da char ja immer 1 sein muss kann man sich die zweite Klammer auch sparen.

    memcpy(redmemory  + i,(char*)RGBXMemory+i+2,1);
    memcpy(bluememory + i,(char*)RGBXMemory+i+1,1);
    memcpy(greenmemory+ i,(char*)RGBXMemory+i,1);
    

    So dass man im Endeffekt das gleich erreicht wenn man folgendes hat:

    for( int i=0; i<number_of_pixels; i++){
    	memcpy(redmemory  + i,(char*)RGBXMemory+i+2,1);
    	memcpy(bluememory + i,(char*)RGBXMemory+i+1,1);
    	memcpy(greenmemory+ i,(char*)RGBXMemory+i+0,1);
    }
    // xxxmemory++==xxxmemory+i
    for( int i=0; i<number_of_pixels; i++){
    	memcpy(redmemory++   ,(char*)RGBXMemory++ +2,1);
    	memcpy(bluememory++  ,(char*)RGBXMemory++ +1,1);
    	memcpy(greenmemory++ ,(char*)RGBXMemory++ +0,1);
    // Dieses hier müsste eigentlich langsamer sein wegen den ++
    }
    

    oder nicht?
    Ohne das Ausgabebild ist es ein wenig schwer zu beurteilen.



  • Ja, ich denke auch, dass das mit boost::thread o.Ä. einfacher zu realisieren ist. Allerdings, bevor man damit loslegt, sollte man prüfen, ob das der gesamten Bildverarbeitung zuträglich ist.



  • ogni42 schrieb:

    Ja, ich denke auch, dass das mit boost::thread o.Ä. einfacher zu realisieren ist. Allerdings, bevor man damit loslegt, sollte man prüfen, ob das der gesamten Bildverarbeitung zuträglich ist.

    Machst du das nicht nebenbei? 🙂



  • EDIT: Ich war dumm und habe die falsche Methode aufgerufen. Hier die korrigierten Zahlen:

    So jetzt mit einer "vernünftig" mit OpenMP parallelisierten Version - per omp_set_num_threads(maxThreads); ist die Gesamtzahl an Threads auf 8 gesetzt:

    `

    Copying with Add based address calculation: Time elapsed 3.219s

    Copying with memcpy based address calculation: Time elapsed 1.92s

    Copying with memcpy_parallel based address calculation: Time elapsed 4.107s

    `

    Unterm Stricht bringt's auf meinem Rechner nichts. Hier noch der Code für die parallele Version. Die beiden anderen Funktionen sind sequentiell.

    void checkPerMemCpyParallel()
    {
        char* redmemory  = getStartOfRedMemory();
        char* bluememory = getStartOfBlueMemory();
        char* greenmemory = getStartOfGreenMemory();  
    
        RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    
        int  number_of_pixels = m_nSizeX*m_nSizeY;
    
    #pragma omp parallel
        {
            size_t ColorIndex=0;
            size_t RGBXIndex=0;
            int offset = m_nSizeY/maxThreads*omp_get_thread_num();
            for( int i=0; i<number_of_pixels/maxThreads; i++)
            {
                memcpy(redmemory  + ColorIndex + offset,((char*)RGBXMemory + RGBXIndex + offset) + 2,1);
                memcpy(bluememory + ColorIndex + offset,((char*)RGBXMemory + RGBXIndex + offset) + 1,1);
                memcpy(greenmemory+ ColorIndex + offset,((char*)RGBXMemory + RGBXIndex + offset) + 0,1);
    
                RGBXIndex++;
                ColorIndex++;
            }
        }
    }
    


  • ogni42 schrieb:

    So jetzt mit einer "vernünftig" mit OpenMP parallelisierten Version - per omp_set_num_threads(maxThreads); ist die Gesamtzahl an Threads auf 8 gesetzt:

    Du hast doch gesagt das du nur zwei Cores hast.

    Hast du es mit zwei Threads probiert? Es nutzt nämlich nichts wenn du mehr Threads rechnen lässt als du Kerne zur Verfügung hast.



  • Hier die Ergebnisse mit 2 Threads:
    `

    Copying with Add based address calculation: Time elapsed 3.156s

    Copying with memcpy based address calculation: Time elapsed 1.937s

    Copying with memcpy_parallel based address calculation: Time elapsed 4.907s

    `

    Also auch nicht besser.

    Ich schätze, dass es an Problemen beim Cache-Zugriff liegt. Ein Thread will auf eine Cache-Line schreiben auf die auch ein anderer geschrieben hat. Dann muss erst alle in den nächste Cache oder Hauptspeicher ent- und wieder geladen werden.

    Ich probiere das jetzt noch auf einem anderen Rechner aus....

    EDIT: Getan! Das sind die Zahlen etwas anders aber die Verhältnisse gleich.



  • Doppelpost..



  • Man muss hier anmerken dass:
    Bisher jeder Thread auf identische Speicherbereiche zugreifen muss.
    Dieser ist hat auch noch weder die optimale Größe noch ein super Alingment.

    Besserer Win32 Thread Aufbau:
    -128bit Alignment (SSE2)
    -Drei Funktionen
    -Drei Threads

    #include <ctime>
    #include <cstdlib>
    #include <iostream>
    #include <windows.h>
    
    #define NUM_THREADS 3
    CRITICAL_SECTION hUpdateMutex;
    HANDLE thread_handles[NUM_THREADS];
    
    const int m_size_x = 2048;
    const int m_nSizeX = 2048;
    const int m_nSizeY = 2048;
    const int number_of_pixels = m_size_x*m_nSizeY;
    
    const int chunkSz = 5*m_nSizeX*m_nSizeY;
    int largeChunk[chunkSz];
    
    char red[chunkSz],green[chunkSz],blue[chunkSz];
    
    struct RGBPixel24{unsigned char r,g,b;};
    struct RGBPixel32{unsigned char r,g,b,x,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13;};
    
    char* getStartOfRedMemory(){return red;}
    char* getStartOfBlueMemory(){return blue;}
    char* getStartOfGreenMemory(){return green;}
    void* getImageStart(void){return largeChunk;}
    RGBPixel32* getStartOfRGBXMemory(){return reinterpret_cast<RGBPixel32*>(getImageStart());}
    
    void checkPerfMemCpyR(){
    	RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    	char* redmemory  = getStartOfRedMemory();
    	for(int i=0;i<number_of_pixels;i++) memcpy(redmemory+i,(char*)RGBXMemory+i+2,1);
    } 
    
    void checkPerfMemCpyG(){
    	RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    	char* greenmemory = getStartOfGreenMemory();  
    	for(int i=0;i<number_of_pixels;i++) memcpy(greenmemory+i,(char*)RGBXMemory+i,1);
    } 
    
    void checkPerfMemCpyB(){
    	RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    	char* bluememory = getStartOfBlueMemory();
    	for(int i=0;i<number_of_pixels;i++) memcpy(bluememory+i,(char*)RGBXMemory+i+1,1);
    } 
    
    void checkPerfMemCpy(){
    
    	char* redmemory  = getStartOfRedMemory();
    	char* bluememory = getStartOfBlueMemory();
    	char* greenmemory = getStartOfGreenMemory();  
    	RGBPixel32* RGBXMemory = getStartOfRGBXMemory();
    
    	for(int i=0;i<number_of_pixels;i++){
    		memcpy(redmemory+i,(char*)RGBXMemory+i+2,1);
    		memcpy(bluememory+i,(char*)RGBXMemory+i+1,1);
    		memcpy(greenmemory+i,(char*)RGBXMemory+i,1);
    	}
    } 
    
    int main( void ){
    
    	DWORD threadID;
    	clock_t start=0,stop=0;
    	for(int j=0;j<5;j++){
    		std::cout << "Benchmark Nr. " << j+1 << '\n';
    		start=clock();
    		for (int i=0;i<1000;i++) {
    			InitializeCriticalSection(&hUpdateMutex);
    			thread_handles[0] = CreateThread(0,0,(LPTHREAD_START_ROUTINE) checkPerfMemCpyR,0,0,&threadID);
    			thread_handles[1] = CreateThread(0,0,(LPTHREAD_START_ROUTINE) checkPerfMemCpyG,0,0,&threadID);
    			thread_handles[2] = CreateThread(0,0,(LPTHREAD_START_ROUTINE) checkPerfMemCpyB,0,0,&threadID);
    			WaitForMultipleObjects(NUM_THREADS,thread_handles,TRUE,INFINITE);
    		}
    		stop=clock();
    
    		std::cout << "Copying with Win32-Thread memcpy based address calculation: ";
    		std::cout << 1000*(stop-start)/CLOCKS_PER_SEC << '\n';
    
    		start=clock();
    		for (int i=0;i<1000;i++) checkPerfMemCpy();
    		stop=clock();
    
    		std::cout << "Copying with memcpy based address calculation: ";
    		std::cout << 1000*(stop-start)/CLOCKS_PER_SEC << "\n\n";
    	}
    }
    

    Unabhängig von den Optimierungen müsste die Struktur eines Win32 Programms wie oben angegeben aussehen.

    Da im ersten Post die Rede von 5MP war, sind die Werte m_nSizeX,m_nSizeY verdoppelt worden so dass man auf ~4MP kommt.

    Benchmarks:

    Benchmark Nr. 1
    Copying with Win32-Thread memcpy based address calculation: 1558
    Copying with memcpy based address calculation: 2582
    
    Benchmark Nr. 2
    Copying with Win32-Thread memcpy based address calculation: 1501
    Copying with memcpy based address calculation: 2568
    
    Benchmark Nr. 3
    Copying with Win32-Thread memcpy based address calculation: 1487
    Copying with memcpy based address calculation: 2577
    
    Benchmark Nr. 4
    Copying with Win32-Thread memcpy based address calculation: 1459
    Copying with memcpy based address calculation: 2569
    
    Benchmark Nr. 5
    Copying with Win32-Thread memcpy based address calculation: 1461
    Copying with memcpy based address calculation: 2569
    

Anmelden zum Antworten