Ideen zur Beschleunigung des Codes (Camera Bild einlesen)
-
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 mitmemcpy
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: ~1300Dazu 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