对比度降低 - intel x86

Contrast reduction - intel x86

我应该做一个项目来通过我的课程。我想问一下,是否有可能使我的代码更有效或更好。我这样做是因为我的协调员是一个非常一丝不苟的完美主义者,并且非常追求效率。 这是一个混合程序,它修改 24bpp 位图。这是一个对比度降低,算法看起来像这样(它被我的协调员批准):

comp-=128;
comp*=rfactor
comp/=128
comp+=128

'comp'表示一个像素的每个分量,字面意思是:每个像素中的每个红色、绿色和蓝色的值。 该函数就是这样做的,我使用 C 中的另一个函数从文件中读取。我转发了一个包含组件的数组、bmp 的宽度、每行中的像素数量以及 'rfactor' - 对比度降低的值.然后我就做这个:

;  void contrast(void *img, int width, int lineWidth, int rfactor);
;   stack:  EBP+8 ->        *img
;       EBP+12 ->       width [px]
;       EBP+16 ->       lineWidth [B]
;       EBP+20 ->       rfactor (values in range of 1-128)
section .text 
global  contrast

contrast:
push    ebp         
mov ebp, esp     

push    ebx         
mov     ebx, [ebp+12]   ; width
mov     eax, [ebp+16]   ; lineWidth
mul     ebx     ; how much pixels to reduce
mov     ecx, eax    ; set counter
mov     edx, [ebp+8]    ; edx = pointer at img
mov     ebx, [ebp+20]   ; ebx=rfactor

loop:
xor     eax, eax    
dec     ecx         ; decrement counter
mov     al, [edx]   ; current pixel to al
add     eax, -128   
imul    bl          ; pixel*rfactor
sar     eax, 7      ; pixel/128
add     eax, 128    
mov     byte[edx], al   ; put the pixel back
inc     edx         ; next pixel
test    ecx, ecx    ; is counter 0?
jnz     loop        

koniec:
pop     ebx
mov     esp, ebp    
pop     ebp
ret 

有什么需要改进的吗?感谢您的所有建议,我必须打动我的协调员 ;)

当不是 up-counting,而是向下计数时,你可以从你的循环中挤出更多一点,就像这样(我改变了 header 来表明意思,我省略了预告片:

    mov edx, img
    mov ecx, width*lineWidth  ; pseudo-assembler here
    mov ebx, rfactor
loop:
        xor  eax, eax
        mov  al, [ecx+edx-1]
        sub  eax, 128
        imul bl
        sar  eax, 7
        add  eax, 128
        mov  byte ptr[ecx+edx-1], al
        dec  ecx
        jnz  loop
; add trailer here

终于,我成功了。感谢您的所有建议。我希望我的协调员会满意。如果没有,我将被迫使用 vectors
; void contrast(void *img, int pixels, int rfactor); ;堆栈:EBP+8 -> *img ; EBP+12 -> 计数器(修改多少字节) ; EBP+16 -> 因子 节.text 全局对比度

contrast:
push    ebp 
mov     ebp, esp     
push    ebx 
push    edi

mov     ecx, [ebp+12]   ; set counter
mov ebx, [ebp+16]   ; rfactor to ebx
mov edi, [ebp+8]    ; img pointer
mov dl, 128     
sub dl, bl      ; 128-rfactor

loop:
mov al, [edi]  ; current pixel to al
mul bl      ;
shr ax, 7   ; byte*rfactor>>7+(128-rfactor)
add al, dl  ;
stosb           ; store al, inc edi
loop loop   

koniec: 
pop     edi
pop     ebx
mov     esp, ebp    
pop     ebp
ret 

你对 SIMD 版本仍然感兴趣,这里是一个。
它使用 AVX2 指令,因此您至少需要第 4 代处理器(Haswell 微架构)。

BITS 32

GLOBAL _contrast

SECTION .code

;rfactor
;lineWidth 
;width
;ptr to buffer
_contrast:
    push ebp         
    mov ebp, esp     

    and esp, 0fffffff0h

    push edi
    push esi
    push ebx 
    push eax


    mov eax, DWORD [ebp+0ch]        ;witdh
    mul DWORD [ebp+10h]             ;total bytes
    mov ecx, eax                    ;Number of bytes to process
    shr ecx, 04h                    ;Process chunks of 16 bytes per cycle

    mov edi, DWORD [ebp+08h]        ;Buffer


    ;--- Prepare ymm registers ---

    vzeroall

    sub esp, 10h 

    ;ymm1 contains the r factor (x16)
    movzx ebx, WORD [ebp+14h]
    mov DWORD [esp], ebx
    vpbroadcastw ymm1, WORD [esp]   ;ymm1 = r (x16)

    ;ymm0 contains the 128-r value (x16)
    neg WORD [esp]                  ;-r
    mov al, 128
    add WORD [esp], ax              ;128-r
    vpbroadcastw ymm0, WORD [esp]   ;ymm0 = 128-r (x16)

    add esp, 10h

.loop:
    ;Computer channels values
    vpmovzxbw ymm2, [edi]           ;16 channels (128 bit) to 16 words
    vpmullw ymm2, ymm2, ymm1        ;ymm2 = in*r
    vpsrlw ymm2, ymm2, 7            ;ymm2 = in*r>>7
    vpaddw ymm2, ymm2, ymm0         ;ymm2 = in*r>>7 + r-128

    vpackuswb ymm2, ymm2, ymm2      ;xmm2 = 16 computes values 

    ;Store to memory
    movdqa [edi], xmm2

    add edi, 10h

loop .loop

    pop eax
    pop ebx
    pop esi
    pop edi 

    mov esp, ebp
    pop ebp
    ret 

我已经通过比较它的输出和你的代码的输出来测试它。

C 中的原型是您的旧原型(带有 lineWidth):

void contrast(void* buffer, unsigned int width, unsigned int Bpp, unsigned short rfactor);

我已经在我的机器上做了一些分析。我有 运行 这个版本和你在 2048x20480 图像(120MiB 缓冲区)上的答案中的版本 10 次。你的代码需要 2.93 秒,这个 1.09 秒。虽然这个时间可能不是很准确。

此版本需要的缓冲区大小是 16 的倍数(因为它每个周期处理 16 个字节,一次处理 5 个像素的三分之一),您可以用零填充。如果缓冲区在 16 字节边界上对齐,它将 运行 更快。

如果您想要更详细的答案(例如有用的评论:D),请在评论中提问。

编辑:在 Peter Cordes 的伟大帮助下更新了代码,以供将来参考。