"calloc()
zero-initializes the buffer, while malloc()
leaves the memory uninitialized."
GetUniqueColors
-
Oscar -
14. November 2017 um 17:33 -
Erledigt
-
-
Ist es denn 100% gewährleistet, dass der Speicher für das Bool auch wirklich 0 ist? Ich meine es gab da was, dass man nicht gewährleisten kann, was im Speicher drinnen steht. Würde das dann nicht im Extremfall zum Ausbleiben von gefundenen Pixeln führen?
Dafür gibt es ja calloc. malloc stellt dir nur den Speicher zur Verfügung, zur Sicherheit sollte man diesen mit Nullen überschreiben.
-
Dafür gibt es ja calloc. malloc stellt dir nur den Speicher zur Verfügung, zur Sicherheit sollte man diesen mit Nullen überschreiben.
Danke, wieder was gelernt
-
Danke, wieder was gelernt
Ich hab ein kleines Beispielprogramm geschrieben und das zeigt das ganze auch schön:
C
Alles anzeigen#include <stdio.h> #include <stdlib.h> int main() { const int TEST_SIZE = 5000; void* pSpaceMalloc = malloc(TEST_SIZE); void* pSpaceCalloc = calloc(TEST_SIZE, 1); int counterMalloc = 0; char* pMalloc = (char*)(pSpaceMalloc); for (int i = 0; i < TEST_SIZE; i++) { if (*pMalloc != 0) counterMalloc++; pMalloc += 1; } printf("Non-Zeroes Malloc: %d\n", counterMalloc); int counterCalloc = 0; char* pCalloc = (char*)(pSpaceCalloc); for (int i = 0; i < TEST_SIZE; i++) { if (*pCalloc != 0) counterCalloc++; pCalloc += 1; } printf("Non-Zeroes Calloc: %d", counterCalloc); getchar(); return 0; }
-
Ich hab ein kleines Beispielprogramm geschrieben und das zeigt das ganze auch schön:
C
Alles anzeigen#include <stdio.h> #include <stdlib.h> int main() { const int TEST_SIZE = 5000; void* pSpaceMalloc = malloc(TEST_SIZE); void* pSpaceCalloc = calloc(TEST_SIZE, 1); int counterMalloc = 0; char* pMalloc = (char*)(pSpaceMalloc); for (int i = 0; i < TEST_SIZE; i++) { if (*pMalloc != 0) counterMalloc++; pMalloc += 1; } printf("Non-Zeroes Malloc: %d\n", counterMalloc); int counterCalloc = 0; char* pCalloc = (char*)(pSpaceCalloc); for (int i = 0; i < TEST_SIZE; i++) { if (*pCalloc != 0) counterCalloc++; pCalloc += 1; } printf("Non-Zeroes Calloc: %d", counterCalloc); getchar(); return 0; }
Danke, mir war ja bewusst, dass der Effekt existiert, sonst hätte ich nicht gefragt. War mir nur nicht bewusst, dass calloc() uns dieses unsauberen Effekts entledigt.
-
Hab eine primitive ASM Version geschrieben. (primitiv im Sinne von "kein SSE und sonstige Extras, sondern wirklich nur extrem einfach"). Da hierbei AutoIt für das calloc zuständig ist (das geht bestimmt auch in asm, aber ich habe das noch nie gemacht und hatte keine Zeit zu googeln :D) gibt es einen größeren Overhead. Daraus folgt, dass die ASM Variante für "kleine" Bilder langsamer als die DLL ist und für große Bilder schneller.
Code
Alles anzeigen#include <GDIPlus.au3> ;~ #include "C:\Users\...\OneDrive\AutoIt\UDF\EasyASM\EasyASM.au3" ;~ _ASM_Startup() _GDIPlus_Startup() Global $iTimer, $iCount Global $sImagefile = @ScriptDir & '\testbig.jpg' Global $hImage = _GDIPlus_BitmapCreateFromFile($sImagefile) Global $hDLL = DllOpen('cuc.dll') ;~ Global $iMyASM = _ASM_AddOP(_MyASM()) ; Irgendwie braucht der "erste" Aufruf beider Funktionen weeeesentlich länger als alle weiteren Aufrufe. $iCount = _GetUniqueColors($hImage) $iCount = _GetUniqueColorsASM($hImage) ConsoleWrite('##################################' & @CRLF) $iTimer = TimerInit() $iCount = _GetUniqueColors($hImage) ConsoleWrite('GesamtZeit: ' & Int(TimerDiff($iTimer)) & ' ms' & @CR) ConsoleWrite('Anzahl der Farben: ' & $iCount & @CR) ConsoleWrite('##################################' & @CRLF) $iTimer = TimerInit() $iCount = _GetUniqueColorsASM($hImage) ConsoleWrite('GesamtZeit: ' & Int(TimerDiff($iTimer)) & ' ms' & @CR) ConsoleWrite('Anzahl der Farben: ' & $iCount & @CR) ConsoleWrite('##################################' & @CRLF) _GDIPlus_BitmapDispose($hImage) _GDIPlus_Shutdown() ;~ _ASM_Shutdown() Func _GetUniqueColors(ByRef $hImage) Local $aSize = _GDIPlus_ImageGetDimension($hImage) ; Nur 1 Dllcall statt 2 Local $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $aSize[0], $aSize[1], $GDIP_ILMREAD, $GDIP_PXF32ARGB) Local $tPixel = DllStructCreate('uint[' & $aSize[0] * $aSize[1] & '];', DllStructGetData($tBitmapData, 'Scan0')) ;int DLL_EXPORT count_colors(unsigned int* pixel, int len) $aret = DllCall($hDll, "int:cdecl", "CountUniqueColors", "ptr", DllStructGetPtr($tPixel), "int", $aSize[0] * $aSize[1]) _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData) If Not @error Then Return $aret[0] EndFunc Func _GetUniqueColorsASM(ByRef $hImage) Local $aSize = _GDIPlus_ImageGetDimension($hImage) ; Nur 1 Dllcall statt 2 Local $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $aSize[0], $aSize[1], $GDIP_ILMREAD, $GDIP_PXF32ARGB) Local $tPixel = DllStructCreate('uint[' & $aSize[0] * $aSize[1] & '];', DllStructGetData($tBitmapData, 'Scan0')) ; Zusätzliche Vorbereitungen... hier geht auch einiges an Zeit flöten, da Structs in AutoIt langsamer erzeugt werden als z.B. in C. Local $vColor = DllStructCreate('byte[' & 0xFFFFFF + 1 & ']') Local $vData = DllStructCreate('ptr;ptr;uint') ;bitmapdaten;acolor;pixelzahl DllStructSetData($vData, 1, DllStructGetPtr($tPixel)) DllStructSetData($vData, 2, DllStructGetPtr($vColor)) DllStructSetData($vData, 3, $aSize[0] * $aSize[1]) ;~ _ASM_Call($iMyASM, DllStructGetPtr($vData)) ;~ Local $aret = [DllStructGetData($vData, 3)] ;~ ConsoleWrite(_ASM_GetBytecode($iMyASM) & @CRLF) Local $vByteCode = DllStructCreate("byte[92]") DllStructSetData($vByteCode, 1, "0x8B4424048B388B70048B4808BA000000008B1F81E3FFFFFF00803C1E00750542C6041E0183C7044977E7895008C3") DllCall('user32.dll', 'ptr', 'CallWindowProcW', 'ptr', DllStructGetPtr($vByteCode), 'ptr', DllStructGetPtr($vData), 'int', 0, 'int', 0, 'int', 0) Local $aret = [DllStructGetData($vData, 3)] _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData) If Not @error Then Return $aret[0] EndFunc #cs Func _MyASM() _('use32') _('mov eax, [esp+4]') ; Ptr auf die Datenstruct _('mov edi, [eax]') ; Ptr der Bitmapdaten _('mov esi, [eax+4]') ; Ptr der aColor Struct _('mov ecx, [eax+8]') ; Anzahl Pixel _('mov edx, 0') ; Wer weiß wer weiß _('_Label_01:') ; Main Loop _('mov ebx, [edi]') ; pixelfarbe _('and ebx, 0xffffff') ; Pixelfarbe & ffffff _('cmp byte[esi + ebx], 0') ; null ? _('jnz _Label_02') ; nein -> weiter _('inc edx') ; ja -> +1 _('mov byte[esi + ebx], 1') ; ja -> setze 1 _('_Label_02:') ; endif _('add edi, 4') ; nächster px _('dec ecx') ; schleife _('ja _Label_01') ; schleife _('mov dword[eax+8], edx') ; returnwert in vData3 (weil es ein uint ist.....) _('ret') EndFunc #ce
Edit: Zumindest bei mir lief es für 4K Bilder ca. 30-50% schneller. Aber keine Garantie dass der PC nicht explodiert wenn der Code ausgeführt wird
-
Hi zusammen,
um Farben in einem Bild zu zählen hatte ich mal eine Funktion in ASM erstellt....ist schon SEHR lange her^^ (aus 2010). Die "schöne" Variante habe ich leider nicht mehr gefunden, aber ggf. hilft ja das anliegende Script weiter.
Vorgehensweise:
Ich erstelle eine Struct mit der Anzahl möglicher Farben einer Bitmap, also 256^3 Farben.
Dann erhöhe ich für jedes Pixel die entsprechende Adresse innerhalb der Struct (was ja die Farbe darstellt) um 1 hoch. Fertig.
Um jetzt ein Array für die Farben zu bekommen, erstelle ich aus der Struct einen kommaseparierten Text, welcher aus der Farbe in Hex-Darstellung, gefolgt von der Anzahl dieser Farbe, besteht.
Diese Texterstellung (aus der Adresse die HEX-Darstellung der Farbe erzeugen) ist ca. 90% des ASM-Codes, das eigentliche "zählen" wird von einer kurzen Schleife mit 6 Zeilen ( erledigt.
Aus diesem so erstellten Text fummelt StringSplit() ein Array.....
Bei meinem Uralt-Laptop dauert die Farbseparierung in den Textstring ca. 30ms.
Das folgende Stringsplit, um daraus ein Array zu erstellen, habe ich nie ausgestoppt, da ich das Array nie brauchte....
C
Alles anzeigen#include <GDIPlus.au3> #include <memory.au3> ;~ #include <assembleit2_64.au3> #include <array.au3> #AutoIt3Wrapper_UseX64=n #cs _countpixelcolors ; Use32 ;32Bit! org $PTR_SOURCE_ASMCODE ;only needed for assembleit debugger mov edi,dword[esp+4] ;pointer bitmap mov ecx,dword[esp+8] ;width bitmap mov ebx,dword[esp+12] ;height bitmap mov esi,dword[esp+16] ;pointer pixelstruct movd xmm1,dword[esp+20] ;pointer textstruct rdtsc ;timer push eax ;~ _asmdbg_() mov eax,ebx ;h mul ecx ;w*h mov edx,eax ;w*h movd xmm0, edx ;sichern ;zuerst werden die Pixel durchlaufen, die Farbe jedes Pixels wird an der Speicherstelle seines "Farbwerts" hochgezählt @pixel_count: ;alle pixel mov eax,[edi] ;farbe and eax,0xFFFFFF ;eliminieren alpha-channel ;~ _asmdbg_() inc dword [esi+eax*4] ;farbwert=farbwert+1, address=colorRGB^^ ;add dword [esi+eax*4],1 ;schneller? add edi,4 sub edx,1 ;pixelcounter jnz near @pixel_count ;jump if not zero(<0) ;*********************************************************************************** ;jetzt werden die gezählten Farbwerte nacheinander als Text in HEX-Darstellung und der Anzahl als Integerwert in eine textstruct geschrieben ;Format: 6BF1E5,1234567@crlf movd xmm4,edi ;sichern mov edx,0x1000000 ;anzahl farben movd ebx,xmm1 ;pointer textstruct mov edi,-4 ;pointer colorstruct ;~ _asmdbg_() @count_colors: sub edx,1 ;jedes pixel jz @end ;alle pixel durchlaufen mov ecx,[esi+edx*4] ;ecx=anzahl, edx=farbwert cmp ecx,0 je @count_colors ;wenn ungleich null...ist farbe gefunden ;~ _asmdbg_() ;hex-werte farbe =6 Bytes ;aus den 6 nibbles die 6 bytes machen, nibble+48 (0x30) =ASCII-Ziffer ;zuerst aus den beiden bytes ( Bit 4444333322221111) die nibble erweitern zu 0000444400003333 und 0000222200001111 movd xmm3,eax ;sichern wg registerpressure movd xmm4,edx movd xmm5,esi movd xmm6,ecx mov eax,edx mov edi,eax ;~ _asmdbg_() ;************************************************************** mov edx,0 ;6 nibbles _int2hex: mov ecx,20 ;5*4 shiften lea esi,[4*edx] ;anzahl der zu shiftenden bits sub ecx,esi ;ecx=20,ecx shr eax,cl ;nach al shiften and al,0xF ;obere 4 nibble eliminieren cmp al,9 ;größer oder kleiner als A? jle _groesserA add al,7 ;A-F _groesserA: add al,48 ;1-9 mov byte[ebx+edx],al ;ascii-hexcode in struct mov eax,edi add edx,1 ; _asmdbg_() cmp edx,6 jne _int2hex add ebx,edx ;ein zeichen weiter mov byte [ebx],44 ;komma add ebx,1 ;ein zeichen weiter mov edi,ebx ;pointer text movd ebx,xmm6 ;anzahl integer ;************************************************************ ;aus einer Zahl(Registerinhalt) einen ZiffernString machen: http://dcla.rkhb.de/umwandlung/int2dez.html ;und in die Primstruct schreiben ;wird dieser Bereich auskommentiert, wird ein String aus nullen und einsen zurückgegeben 0=keine Prim 1=Prim ;dann können mit den AutoIt-Stringbefehlen die Primzahlen und deren Anzahl ausgelesen werden push ebx ;alle benötigten Register sichern push ecx ;alle benötigten Register sichern mov eax, ebx ;Zahl laden mov ebx, 10 ; Divisor xor ecx, ecx ;ECX=0 (Anzahl der Ziffern) Schleife_1: xor edx, edx div ebx ; EDX:EAX / EBX = EAX Rest EDX push dx ; LIFO add cl,1 ; ADD soll schneller sein als INC or eax, eax ; AX = 0? jnz Schleife_1 ; nein: nochmal Schleife_2: pop ax ; gepushte Ziffern zurückholen or al, 00110000b ; Umwandlung in ASCII stosb ; Nur AL nach [EDI] (EDI ist ein Zeiger auf den String) loop Schleife_2 ; bis keine Ziffern mehr da sind mov byte [edi],0Dh ;CR CarriageReturn, man könnte auch ein Komma (ascii=2C) einsetzen, dazu noch ein nullbyte als EndOfString add edi,1 ;ein Byte weiter pop ecx ;Register wiederherstellen pop ebx ;Register wiederherstellen ;************************************************************Ende Ziffer aus Register movd esi,xmm5 movd edx,xmm4 ;restaurieren mov ebx,edi jmp @count_colors ;schleife alle farben @end: pop ebx ;count clockticks rdtsc ;~ _asmdbg_() sub eax,ebx ret #ce AutoItSetOption("GUIOnEventMode", 1) _GDIPlus_Startup() $sFile = FileOpenDialog("Bilder", @ScriptDir, "Bilder (*.jpg;*.bmp;*.png)") $himage = _GDIPlus_ImageLoadFromFile($sFile) Local $iHeight = _GDIPlus_ImageGetHeight($himage) ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $iHeight = ' & $iHeight & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console Local $iWidth = _GDIPlus_ImageGetWidth($himage) ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $iWidth = ' & $iWidth & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console Global $fTimer Global Const $iStride = 4 * $iWidth Global $tBitmapData = _dllstructcreate64("dword pixel[" & $iStride * $iHeight & "]") Global $ptr_bitmap = DllStructGetPtr($tBitmapData) ; mod(DllStructGetPtr($tBitmapData),64) Global $hBitmap = _GDIPlus_BitmapCreateFromScan0($iWidth, $iHeight, $GDIP_PXF32ARGB, $iStride, $ptr_bitmap) ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : DllStructGetPtr($tBitmapData) = ' & DllStructGetPtr($tBitmapData) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console $hCtxt = _GDIPlus_ImageGetGraphicsContext($hBitmap) _GDIPlus_GraphicsDrawImage($hCtxt, $himage, 0, 0) _GDIPlus_GraphicsDispose($hCtxt) Global $hGUI = GUICreate("ASM Test", $iWidth, $iHeight) GUISetState() Global $hGFX = _GDIPlus_GraphicsCreateFromHWND($hGUI) GUISetOnEvent(-3, "_Exit") ;~ $binarycode = _AssembleIt2("retbinary", "_countpixelcolors") ;gibt nur den assemblierten code zurück ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $binarycode = ' & ($binarycode) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console $binarycode = "0x8B7C24048B4C24088B5C240C8B742410660F6E4C24140F315089D8F7E189C2660F6EC28B0725FFFFFF00FF048683C70483EA010F85EAFFFFFF660F6EE7BA00000001660F7ECBBFFCFFFFFF83EA010F848C0000008B0C9683F90074EF660F6ED8660F6EE2660F6EEE660F6EF189D089C7BA00000000B9140000008D34950000000029F1D3E8240F3C097E020407043088041389F883C20183FA0675D901D3C6032C83C30189DF660F7EF3535189D8BB0A00000031C931D2F7F3665280C10109C075F366580C30AAE2F9C6070D83C701595B660F7EEE660F7EE289FBE96BFFFFFF5B0F3129D8C3" ;nur für dllcalladdress() benötigt, den binarycode braucht man nur ein mal erstellen $tCodeBuffer = _dllstructcreate64("byte[" & StringLen($binarycode) / 2 - 1 & "]") ;reserve Memory for opcodes DllStructSetData($tCodeBuffer, 1, $binarycode) $colorstruct = _dllstructcreate64("uint[" & 256 ^ 3 & "]") ;anzahl möglicher Farben $ptr_colorstruct = DllStructGetPtr($colorstruct) $textstruct = _dllstructcreate64("char[" & 18 * $iWidth * $iHeight & "]") ;maximale Anzahl Zeichen $ptr_textstruct = DllStructGetPtr($textstruct) ;9 Stellen integer ;6 Stellen Hexdarstellung ;1 Stellen Komma ;2 Stellen CRLF $t = TimerInit() $ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "ptr", $ptr_bitmap, "int_ptr", $iWidth, "int_ptr", $iHeight, "int_ptr", $ptr_colorstruct, "int_ptr", $ptr_textstruct) ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret[0] & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console $ret = $ret[0] ;~ $ret = _AssembleIt2("uint", "_countpixelcolors", "ptr", $ptr_bitmap, "int_ptr", $iWidth, "int_ptr", $iHeight, "int_ptr", $ptr_colorstruct, "int_ptr", $ptr_textstruct) ConsoleWrite('Takte: ' & $ret & @CRLF) ;### Debug Console $m = TimerDiff($t) ConsoleWrite('Runtime: = ' & Int($m) & "ms" & @CRLF) ;### Debug Console $taktperpixel = $ret / $iWidth / $iHeight ConsoleWrite("taktperpixel = " & Int($taktperpixel) & @CRLF) ;### Debug Console _GDIPlus_GraphicsDrawImageRect($hGFX, $hBitmap, 0, 0, $iWidth, $iHeight) $text = DllStructGetData($textstruct, 1) $text = StringTrimRight($text, 1) ;letztes LF entfernen ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $text = ' & $text & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console $colorarray = StringSplit($text, @CRLF) _ArrayDisplay($colorarray) ;~ For $color = 1 To 256 ^ 3 ;alle farben per AutoIt auslesen ;~ $anzahl = DllStructGetData($colorstruct, 1, $color);auslesen ;~ If $anzahl <> 0 Then ConsoleWrite(Hex($color - 1, 6) & @TAB & $anzahl & @CRLF) ;~ Next _GDIPlus_GraphicsDrawImageRect($hGFX, $hBitmap, 0, 0, $iWidth, $iHeight) Do Until Not Sleep(100) Func _Exit() _GDIPlus_BitmapDispose($hBitmap) _GDIPlus_GraphicsDispose($hGFX) _GDIPlus_Shutdown() GUIDelete() Exit EndFunc ;==>_Exit Func _dllstructcreate64($struct) ;align auf 16-byte adresse ;~ ;msgbox(0,0,"structmem_start") Local $temp = DllStructCreate($struct) Local $tempsize = DllStructGetSize($temp) + 64 Local $ptr = DllStructGetPtr($struct) Local $a1 = Mod(Number($ptr), 64) Local $temp = 0 local $mem = _MemVirtualAlloc($ptr + $a1, $tempsize, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) $mem_dllstructcreate64_internal=$mem Local $a2 = Mod(Number($mem), 64) ;rest div 16 adresse = offset local $sstruct = DllStructCreate($struct, (Number($mem) - $a2 + 64)) Return $sstruct ;auf 16 alingned pointer EndFunc ;==>_dllstructcreate64
-
- Offizieller Beitrag
Andy: Vielen Dank für Dein Beispiel!
Mit der Hilfe habe ich mal mein erstes ASM-Programm erstellt und es funktioniert sogar.
Bei den kleinen Bildern ist es nicht viel schneller als die mit TCC erstellte DLL in C, aber bei dem großen Bild (4608 x 3456 px) ist es dann doch noch etwas schneller (185ms).
Wobei ich dazusagen muss, dass mir die 200ms bei der DLL schon ausreichen würden. Im Gegensatz zu den fast 29 Sekunden beim puren AutoIt ist das schon eine andere Liga.
Außerdem sollte ich noch erwähnen, dass der größte Teil von den 200 bzw. 185 ms für das "_GDIPlus_BitmapLockBits" unter AutoIt draufgehen (ca. 141 ms).
Aber vielleicht magst Du ja mal über mein ASM-Programm schauen und mir evtl. Verbesserungsvorschläge aufschreiben:
AutoIt
Alles anzeigen#AutoIt3Wrapper_UseX64=n ;32Bit-Modus #include <GDIPlus.au3> #include <Memory.au3> ;~ #include "assembleit2_64.au3" #cs _countpixelcolors ; Use32 ;32Bit! mov esi,dword[esp+4] ;pointer Pixelstruct mov ecx,dword[esp+8] ;width bitmap mov ebx,dword[esp+12] ;height bitmap mov edi,dword[esp+16] ;pointer Colorstruct mov eax,ebx ;h mul ecx ;w*h mov edx,eax ;edx = Pixelcounter (w*h) xor ecx,ecx ;ecx jetzt als Farbzaehler (auf null setzen) ; Schleife ANFANG fuer alle Pixel @pixel_count: ;alle pixel mov eax,[esi] ;Farbwert aus Pixelstruct holen and eax,0xffffff ;Alphachannel eliminieren movsx bx,byte[edi+eax] ;den Wert aus der Colorstruct holen cmp ebx,0 ;mit 0 vergleichen jnz @next ;wenn nicht 0, dann wurde die Farbe bereits gezaehlt weiter -> @next inc ecx ;den Farbzaehler um eins erhoehen mov byte[edi+eax],1 ;den Wert in der Colorstruct auf 1 setzen @next: add esi,4 ;den Pixelstruct-Pointer um 4 erhoehen (naechstes DWORD) dec edx ;Pixelcounter um eins verringern jnz near @pixel_count ;jump if not zero ; Schleife ENDE mov eax,ecx ret #ce ;~ $binarycode = _AssembleIt2("retbinary", "_countpixelcolors") ;gibt nur den assemblierten code zurück ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $binarycode = ' & ($binarycode) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console ;~ Exit Global $g_pMem, $g_iMemSize, $bCode, $tCodeBuffer, $hImage, $iW, $iH, $iTimer, $tBitmapData, $pScan0, $tPixel, $pPixel, $tColors, $pColors, $ret $bCode = "0x8B7424048B4C24088B5C240C8B7C241089D8F7E189C231C98B0625FFFFFF00660FBE1C0783FB00750541C604070183C6044A0F85E0FFFFFF89C8C3" $tCodeBuffer = _dllstructcreate64_("byte[" & StringLen($bCode) / 2 - 1 & "]") ;reserve Memory for opcodes DllStructSetData($tCodeBuffer, 1, $bCode) _GDIPlus_Startup() $hImage = _GDIPlus_BitmapCreateFromFile(@ScriptDir & '\test_g.jpg') $iW = _GDIPlus_ImageGetWidth($hImage) $iH = _GDIPlus_ImageGetHeight($hImage) ConsoleWrite(StringFormat('_GetUniqueColors (ASM)\n%d x %d = %s px\n', $iW, $iH, $iW * $iH)) $iTimer = TimerInit() $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $iW, $iH, $GDIP_ILMREAD, $GDIP_PXF32PARGB) $pScan0 = $tBitmapData.Scan0 ConsoleWrite('Zeit (BitmapLockBits): ' & Round(TimerDiff($iTimer), 2) & ' ms' & @CR) $tPixel = DllStructCreate('dword[' & $iW * $iH & '];', $pScan0) ; Pixelstruct (dword = 32 Bit pro Pixel) $pPixel = DllStructGetPtr($tPixel) ConsoleWrite('Zeit (Pixelstruct): ' & Round(TimerDiff($iTimer), 2) & ' ms' & @CR) $tColors = DllStructCreate('byte[' & 0xffffff + 1 & '];') ; Colorstruct $pColors = DllStructGetPtr($tColors) ConsoleWrite('Zeit (Colorstruct): ' & Round(TimerDiff($iTimer), 2) & ' ms' & @CR) $ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "ptr", $pPixel, "dword", $iW, "dword", $iH, "ptr", $pColors) $ret = $ret[0] ;~ $ret = _AssembleIt2("dword", "_countpixelcolors", "ptr", $pPixel, "dword", $iW, "dword", $iH, "ptr", $pColors) ConsoleWrite('Anzahl der Farben = ' & $ret & @CR) ConsoleWrite('Zeit: ' & Round(TimerDiff($iTimer), 2) & ' ms' & @CR) _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData) $tPixel = 0 $tColors = 0 $tBitmapData = 0 _GDIPlus_BitmapDispose($hImage) _GDIPlus_Shutdown() _MemVirtualFree($g_pMem, $g_iMemSize, $MEM_DECOMMIT) Exit Func _dllstructcreate64_($struct) ;align auf 16-byte adresse Local $temp = DllStructCreate($struct) $g_iMemSize = DllStructGetSize($temp) + 64 Local $ptr = DllStructGetPtr($struct) Local $a1 = Mod(Number($ptr), 64) Local $temp = 0 $g_pMem = _MemVirtualAlloc($ptr + $a1, $g_iMemSize, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) Local $a2 = Mod(Number($g_pMem), 64) ;rest div 16 adresse = offset $sstruct = DllStructCreate($struct, (Number($g_pMem) - $a2 + 64)) Return $sstruct ;auf 16 alingned pointer EndFunc ;==>_dllstructcreate64
Auf jeden Fall schonmal ein großes DANKESCHÖN für "assembleit2_64.au3"!
-
Außerdem sollte ich noch erwähnen, dass der größte Teil von den 200 bzw. 185 ms für das "_GDIPlus_BitmapLockBits" unter AutoIt draufgehen (ca. 141 ms).
Ja, das "sperren" dauert wirklich extrem lange.
Ich kann mich vage erinnern, dass bei meinen früheren Versuchen unter XP und WIN7, "schnell" Bitmaps zu bearbeiten, sich _GDIPlus_BitmapLockBits() als langsamste Variante herausgestellt hat, um einen Pointer auf die Pixeldaten zu bekommen.
Jedenfalls, wie auch schon bei dem Beitrag von Mars erwähnt, wenn man das erste Mal im Script das _GDIPlus_BitmapLockBits() aufruft.
Sämtliche weiteren Aufrufe dieser Funktion erfolgen teilweise bis zu 100x schneller!
GGf. hat sich das ja unter WIN10 gegenüber XP/WIN7 geändert?! Auf dem aktuellen WIN10 ist (bei mir zumindest) _GDIPlus_BitmapLockBits() SEHR schnell im Vergleich zu allen anderen getesteten Methoden!
Generell ist die resultierende Geschwindigkeit aber eine Frage der Programmierung einer selbst erstellten Funktion. Natürlich kann man ALLES was zum Aufruf dieser Funktion erforderlich ist, in den Funktions-Code hineinpacken. Das ist dann idiotensicher und Programmierer/Userfreundlich!
Ich habe mir allerdings angewöhnt, alles was in irgendeiner Weise mit Speicheranforderungen usw. zu tun hat, beim Start des Programms zu erledigen.
Dann dauert der Start des Programms ggf. eine Sekunde länger (was niemand bemerkt/stört), aber der Ablauf beim teilweise zig-tausendfachen Aufruf einer Funktion wird massiv beschleunigt (DAS merkt man deutlich!).
Übrigens Mars
GEILER ASM-Code! Sauschnell und clever umgesetzt!
Aber vielleicht magst Du ja mal über mein ASM-Programm schauen und mir evtl. Verbesserungsvorschläge aufschreiben:
Da ist nicht viel zu verbessern
Sehr einfach, strukturiert und nachvollziehbar, SO muss das sein!
Hast du den Debugger benutzt? Und ggf. Verbesserungsvorschläge?
//EDIT
Insgesamt ist festzustellen, dass sparsamer "Verbrauch" von Speicher für die Farbcodes, also BYTE statt UINT/DWORD in diesem Fall extrem viel an Performance bringt.
IdR ist das bei "Bildbearbeitung", also Filter u.ä., völlig unerheblich, da Pixel für Pixel nacheinander abgearbeitet wird.
Der Prozessor liest bei einer Anfrage aus dem Speicher die gesamte Cacheline, ggf. auch mehrere Blöcke. Daher ist das "nächste" Pixel immer im Cache. Es "schwuppt"!
Beim vorliegenden Script allerdings wird beim Lesen/Schreiben der "Farben" aber relativ wild im Speicher umhergesprungen, der Cache wird somit wesentlich seltener getroffen, Cache-Misses bremsen das Programm extrem aus!
Ich habe das ASM-Programm daher testweise aufgeteilt:
Die erste Schleife schreibt, wie gehabt, den Farbcode jedes Pixels als "1" in die BYTE-Struct."Wildes" Schreiben an unterschiedliche Adressen in den Speicher....
Die nächste Schleife durchläuft nun die BYTE-Struct und liest/addiert nacheinander die gesetzten BYTEs auf, zählt also die verwendeten Farben. Da alle Adressen aufeinander folgen, liegt das nächste zu lesende BYTE immer im Cache!
In der Summe dauert dieses Programm exakt genau so lange wie die "verschachtelte" Version, allerdings kann man die Zeiten der Schleifen per RDTSC besser ausstoppen.
Der Unterschied ist gewaltig! Die Schreibschleife benötigt 5-7 Mal so lange wie die Leseschleife.
Ich habe die von dir erstellte DLL mal mit dem Debugger bzw. IDA untersucht.
Der Compiler werkelt dabei in der Standard-Einstellung, verwendet also relativ großzügig (Speicher-)Variablen anstelle von Prozessorregistern.
Schau mal, ob es da einen/mehrere Compilerschalter gibt, die das optimieren, dann käme der Code sicher nahe an den selbstgestrickten ASM-Code heran.
Btw. wie unterscheiden sich bei dir die Laufzeiten von DLL und ASM?
-
- Offizieller Beitrag
Erstmal Danke für Deine "Bewertung"!
Ich bin ja schon froh, dass ich da nicht grobe Schnitzer eingebaut habe. Von Mars habe ich mir jetzt auch noch die kürzere Variante abgeguckt. Eigentlich lagen wir ja schon dicht zusammen.
Und so wie die Funktion jetzt arbeitet, reicht mir das völlig. Für ein 4k-Bild (3840x2160 px) braucht die Funktion ca. 85ms. Der reine ASM-Teil liegt bei 20ms. Ich denke, da hat es wenig Sinn weiter am ASM-Teil zu feilen.
Die ganz großen Bilder (direkt von der Kamera) liegen mit ca. 185ms auch völlig im grünen Bereich. Und das mit nur 40 Bytes Assemblercode.
Den Debugger habe ich auch benutzt und finde ihn sehr hilfreich. Man kann schnell mal nachsehen, ob in den Registern die vorgesehen Werte stehen. Top!
Ich denke, solche Inner-Loops werde ich jetzt öfter in Assembler schreiben.
Edit: Muss man sich eigentlich um das "Retten" der Registerinhalte selbst kümmern? Also bei Aufruf des ASM-Codes alle Register auf den Stack und am Ende wiederherstellen? Oder macht DllCallAddress das automatisch?
Ich habe das "Retten" bisher nicht gemacht und es klappte alles, aber ich weiß nicht, ob das nicht auf Dauer zum Absturz führen kann.
-
Ich hatte auch vor einiger Zeit diese Funktion in AutoIt und FreeBasic geschrieben und dabei bemerkt, dass die Anzahl der Farben sich von Programm zu Programm unterscheidet.
Dein Amsel Bild:
Unsere GDI+ Versionen: 160514
IfranView: 148516
XnView: 160537
Keine Ahnung, wie man nun weiß, welcher Wert der richtige ist.
Ach ja, meine FB Version benötigt für das HD Amsel Bild ca. 400ms, wobei ich den Weg über Arrays genommen hatte (hinzufügen, sortieren und zählen).
-
Ach ja, meine FB Version benötigt für das HD Amsel Bild ca. 400ms, wobei ich den Weg über Arrays genommen hatte (hinzufügen, sortieren und zählen).
Kannst du mal deine Variante umprogrammieren, so dass du sie nicht sortierst? Ich hatte ja ein AutoIt-Script bereits dazu am Anfang gepostet,
wenn du so viele Indizes wie Farben erzeugst, dann fällt die Suchzeit weg und aus 400ms sollten deutlich weniger werden.
-
Leider kann FB nicht 256^4 als Array adressieren oder ich weiß nicht, wie das gehen soll. Jedenfalls mit 256^3, sprich 24-Bit Support, liege ich jetzt bei ca. 150 ms für das HD Amsel Bild.
-
Das liegt vermutlich daran, dass 256^4 * 1 Byte = 4 Gigabyte sind. Versuch mal das Programm mit 64Bit zu kompilieren, oder 256^4 * 1 Bit (also 1 Bit je Pixelfarbe) zu adressieren. (Für die 1 Bit Variante brauchst du dann aber ein paar Bitshifts, damit du die einsen korrekt speichern kannst, da der Zugriff nur auf 8 Bit Blöcke möglich ist. Alernativ kann man auch per OR und 10000000b das erste, mit OR 01000000b das zweite usw Bit setzen und mit AND 10000000b abfragen ob dieses Bit gesetzt wurde)
-
Das Array unter 32-bit funzt nicht, aber als 64-bit. Da werden schlappe 4 GB einfach belegt. Nicht gut, aber wie soll's auch anders sein!
Die langsamere Variante unterstützt auch 32-bit Bilder und verbraucht nur einen Bruchteil an Speicher.
-
- Offizieller Beitrag
Ich hatte auch vor einiger Zeit diese Funktion in AutoIt und FreeBasic geschrieben und dabei bemerkt, dass die Anzahl der Farben sich von Programm zu Programm unterscheidet.
Dein Amsel Bild:
Unsere GDI+ Versionen: 160514IfranView: 148516
XnView: 160537
Keine Ahnung, wie man nun weiß, welcher Wert der richtige ist.Das ist mir auch schon aufgefallen. Ich kann mir aber nicht erklären wieso das so ist.
Vom Prinzip her dürfte die von uns verwendete Array-Methode keine "falschen" Ergebnisse liefern.
Kann es sein, dass die Laderoutinen von GDI+ und den von IrfanView bzw. XnView die JPEG-Daten anders "interpretieren"?
Edit: Zum zählen der echten 32 Bit (ARGB): da wirklich 4 GB RAM zu reservieren, finde ich übertrieben. Ich bin in meiner Funktion ja den Weg über PARGB gegangen. So werden die Alphawerte vor dem Bitlock auf die RGB-Werte addiert.
-
Die unterschiedlichen Farben habe ich auch bemerkt. Wenn ich das .jpg Bild als .png abspeichere sollte sich ja an den Farben nichts ändern.
-
- Offizieller Beitrag
Bei PNGs habe ich auch keinen Unterschied zwischen meiner Funktion und IrfanView festgestellt.
-
Oscar : stimmt, wo du es jetzt sagst, hatte ich auch schon recherchiert und festgestellt, dass es am JPEG Decoder hing. Hab's wieder vergessen (Altersamnesie).
-
Ich bin mal ganz naiv rangegangen und dachte mir: Es gibt ne Set in C++ - warum nicht einfach die nutzen?
So ist der Code brutal simpel:
C
Alles anzeigen#include <windows.h> #include <unordered_set> extern "C" __declspec(dllexport) int __stdcall count_colors(int len, int *aPixel ) { std::unordered_set<int> setOfColors(aPixel, &aPixel[len]); return static_cast<int>(setOfColors.size()); } BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) { switch (reason) { case DLL_PROCESS_ATTACH: break; case DLL_PROCESS_DETACH: break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break;} return TRUE; }
Das Ergebnis in Form zweier DLL liegt im Anhang.
Bei kleinen Bildern könnte der Code fix sein.
Bei größeren Bildern womöglich langsamer als die vorgestellten Lösungen.Auch wenn es vielleicht nicht die Performance-Bestmarke knackt: Viel einfacher geht es im Grunde nicht mehr.
Da ich bisschen neidisch auf die hier vorgestellten ASM-Lösungen schiele wollte ich dennoch noch was hier beitragen.
Edit: Ach ja: 32 Bit-Bilder funktionieren hiermit ebenfalls.
-