2015-06-14 75 views
0

我本来应该做一个项目来通过我的课程。我想问一下,如果有可能使我的代码更有效或更好。我这样做是因为我的协调员是一个非常细致的完美主义者,对效率感到疯狂。 这是一个混合程序,它修改了一个24bpp位图。这是一个对比度减弱,算法是这样的(它批准了我的协调员):对比度降低 - 英特尔x86

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

“补偿”是指一个像素的每一个组成部分,从字面上:每个值的红,绿,每个像素的蓝色。 这个函数只是这样做的,我使用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 

有什么需要改进的?谢谢你的所有建议,我不得不打动我的协调员;)

+0

是否允许使用SSE 4.x和/或AVX等扩展? – 2015-06-14 19:02:20

+0

@ knm241是的,但我不知道如何使用它在这种情况下 –

+0

我不确定值得使用SSE4.x/AVX,因为它们需要最近的CPU进行测试。我用SIMD instrunctions写了一段代码的草图,需要做一些测试。无论如何,你可以做一些微型优化:1)使用'loop'指令而不是'jnz loop',这样你就可以避免'dec ecx'。 2)不要零eax 3)将你的等式从'out =(in-128)* r/128 + 128'改写成'out =(in * r >> 7)+ 128 - r',这样你可以预先计算' 128 -r',并简化循环(现在应该可以使用'shr',因为'in * r'是正数。4)展开循环以一次处理4个像素,一次加载它们 – 2015-06-14 20:03:46

回答

1

我你仍然有兴趣在这里SIMD版本之一。
它使用AVX2指令,所以你至少需要第四代处理器(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语言中的原型是你的旧(含线宽):

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的帮助,以供将来参考。

+0

我想我会管理。非常非常感谢你! –

+0

为什么你使用'vinserti128'两次?第一个可以是'vmovdqu',它不需要任何端口015的uops(并且破坏对'ymm' reg的旧值的依赖)。另外,您可以使用'vpbroadcastd'将32位值广播到'ymm'的所有​​元素,而不是4x'push'。对于商店,'vextracti128 [esp],ymm0,0'等于'vmovdqu [esp],xmm0'的100%。我认为它只存在(而不是storehigh128),所以它在avx-512中的作用是一样的,其中imm8将索引4个可能的128个通道中的一个。 –

+0

也不是'lodsd' /'stosb'序列,你不能将每一个其他字节都用'vpsrlw',然后'vpblendsomething'换成一个寄存器来保存想要的移位字节组合和非移动字节?并使用'vpackuswb'从16bit回到8bit通道。 –

0

你可能会挤出多一点你的循环时,不计算,但减计数,就像这样(我改变头来表明意义,我省略了拖车:。

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 
0

最后,我设法感谢您的所有建议,我希望我的协调会满意。如果没有,我将被迫使用矢量
做到这一点;无效对比(无效* img,int pixels,int rfactor); ; stack:EBP + 8 - > * img ; EBP + 12 - > counter(需要修改多少字节) ; EBP + 16 - > rfactor .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 
+0

在Nehalem之前的Intel CPU上,16位'shr'在循环中每次解码都会很慢。 (后来的CPU有一个足够大的循环缓冲区来容纳那个小循环的所有微分)。 –

+0

如果你不打算使用向量指令,直接在asm中编写就没有多少意义了。像图形,海事组织。您的汇编版本比C编译器快得多吗? –