测试值是否在阈值范围内的最佳方法(性能方面)是什么?

时间:2011-05-07 23:12:19

标签: c++ performance optimization byte

也就是说,进行测试的最快方法是什么

if( a >= ( b - c > 0 ? b - c : 0 ) &&
    a <= ( b + c < 255 ? b + c : 255 ) )

    ...

如果a,b和c都是unsigned char又名BYTE。我正在尝试优化图像扫描过程以查找子图像,并且每次扫描大约需要300万次这样的比较,因此即使是微小的优化也可能会有所帮助。

不确定,但也许某种按位操作?可能在c中添加1并测试小于和大于没有等于零件的情况?我不知道!

4 个答案:

答案 0 :(得分:4)

嗯,首先让我们看看你要检查的是什么,没有各种上/下溢检查:

a >= b - c
a <= b + c
subtract b from both:
a - b >= -c
a - b <= c

现在等于

abs(a - b) <= c

在代码中:

(a>b ? a-b : b-a) <= c

现在,这段代码速度更快,不包含(或需要)复杂的下溢/溢出检查。


我用我的100万次重复描述了我和6502的代码,并且正式没有任何区别。我建议选择最优雅的解决方案(这是我的IMO,但意见不同),因为性能不是一个论据。


然而,我和提问者的代码之间存在显着差异。这是我使用的分析代码:

#include <iostream>

int main(int argc, char *argv[]) {  
    bool prevent_opti;
    for (int ai = 0; ai < 256; ++ai) {
        for (int bi = 0; bi < 256; ++bi) {
            for (int ci = 0; ci < 256; ++ci) {
                unsigned char a = ai;
                unsigned char b = bi;
                unsigned char c = ci;
                if ((a>b ? a-b : b-a) <= c) prevent_opti = true;
            }
        }
    }

    std::cout << prevent_opti << "\n";

    return 0;
}

我的if语句平均花费120毫秒,而提问者的if语句平均花费135毫秒。

答案 1 :(得分:2)

只需使用普通int abc,您就可以将代码更改为更简单的

if (a >= b - c && a <= b + c) ...

另外,作为替代方案,256 * 256 * 256仅为16M,16M位的映射为2MBytes。这意味着使用像

这样的查找表是可行的
int index = (a<<16) + (b<<8) + c;
if (lookup_table[index>>3] & (1<<(index&7))) ...

但我认为即使现代处理器讨厌条件限制,缓存垃圾也会变得更慢......

另一种选择是使用一些代数

b - c <= a <= b + c
      iff
- c <= a - b <= c        (subtracted b from all terms)
      iff
0 <= a - b + c <= 2*c    (added c to all terms)

这允许只使用一个测试

if ((unsigned)(a - b + c) < 2*c) ...

假设abc为普通int。原因是,如果a - b + c为负,则无符号算术将使其大于2*c(如果c为0..255)。 如果处理器具有专用的有符号/无符号比较指令,如x86(ja / jg),则应该使用单个分支生成高效的机器代码。

#include <stdio.h>

int main()
{
    int err = 0;

    for (int ia=0; ia<256; ia++)
        for (int ib=0; ib<256; ib++)
            for (int ic=0; ic<256; ic++)
            {
                unsigned char a = ia;
                unsigned char b = ib;
                unsigned char c = ic;
                int res1 = (a >= ( b - c > 0 ? b - c : 0 ) &&
                            a <= ( b + c < 255 ? b + c : 255 ));
                int res2 = (unsigned(a - b + c) <= 2*c);

                err += (res1 != res2);
            }
    printf("Errors = %i\n", err);
    return 0;
}

在带有g ++的x86上,为res2测试生成的汇编代码只包含一个条件指令。

以下循环的汇编代码是

void process(unsigned char *src, unsigned char *dst, int sz)
{
    for (int i=0; i<sz; i+=3)
    {
        unsigned char a = src[i];
        unsigned char b = src[i+1];
        unsigned char c = src[i+2];
        dst[i] = (unsigned(a - b + c) <= 2*c);
    }
}


.L3:
    movzbl  2(%ecx,%eax), %ebx    ; This loads c
    movzbl  (%ecx,%eax), %edx     ; This loads a
    movzbl  1(%ecx,%eax), %esi    ; This loads b
    leal    (%ebx,%edx), %edx     ; This computes a + c
    addl    %ebx, %ebx            ; This is c * 2
    subl    %esi, %edx            ; This is a - b + c
    cmpl    %ebx, %edx            ; Comparison
    setbe   (%edi,%eax)           ; Set 0/1 depending on result
    addl    $3, %eax              ; next group
    cmpl    %eax, 16(%ebp)        ; did we finish ?
    jg  .L3                   ; if not loop back for next

代替dst[i] = (a<b ? b-a : a-b);代码变得更长

.L9:
    movzbl  %dl, %edx
    andl    $255, %esi
    subl    %esi, %edx
.L4:
    andl    $255, %edi
    cmpl    %edi, %edx
    movl    12(%ebp), %edx
    setle   (%edx,%eax)
    addl    $3, %eax
    cmpl    %eax, 16(%ebp)
    jle .L6
.L5:
    movzbl  (%ecx,%eax), %edx
    movb    %dl, -13(%ebp)
    movzbl  1(%ecx,%eax), %esi
    movzbl  2(%ecx,%eax), %edi
    movl    %esi, %ebx
    cmpb    %bl, %dl
    ja  .L9
    movl    %esi, %ebx
    movzbl  %bl, %edx
    movzbl  -13(%ebp), %ebx
    subl    %ebx, %edx
    jmp .L4
    .p2align 4,,7
    .p2align 3
.L6:

现在我太累了,试图破译它(2:28 AM)

无论如何更长并不意味着必然更慢(乍一看似乎g ++决定通过在这种情况下一次写几个元素来展开循环)。

正如我之前所说,你应该用你的实际计算和真实数据做一些实际的分析。请注意,如果需要真正的性能,最佳策略可能因处理器而异。

例如Linux在bootstrap期间进行ae测试以确定执行内核所需的某个计算的更快方法。变量太多了(缓存大小/级别,ram速度,cpu时钟,芯片组,cpu类型......)。

答案 2 :(得分:2)

它认为通过以最清晰的方式编写它然后打开编译器优化器,您将获得最佳性能。编译器相当擅长这种优化,并且在大多数情况下会打败你(在最坏的情况下它会与你相等)。

我的偏好是:

int min = (b-c) > 0  ? (b-c) : 0 ;
int max = (b+c) < 255? (b+c) : 255;

if ((a >= min) && ( a<= max))

原始代码:(汇编)

movl    %eax, %ecx
movl    %ebx, %eax
subl    %ecx, %eax
movl    $0, %edx
cmovs   %edx, %eax
cmpl    %eax, %r12d
jl  L13
leal    (%rcx,%rbx), %eax
cmpl    $255, %eax
movb    $-1, %dl
cmovg   %edx, %eax
cmpl    %eax, %r12d
jmp L13

我的代码(汇编)

movl    %eax, %ecx
movl    %ebx, %eax
subl    %ecx, %eax
movl    $0, %edx
cmovs   %edx, %eax
cmpl    %eax, %r12d
jl  L13
leal    (%rcx,%rbx), %eax
cmpl    $255, %eax
movb    $-1, %dl
cmovg   %edx, %eax
cmpl    %eax, %r12d
jg  L13

nightcracker的代码(汇编)

movl    %r12d, %edx
subl    %ebx, %edx
movl    %ebx, %ecx
subl    %r12d, %ecx
cmpl    %ebx, %r12d
cmovle  %ecx, %edx
cmpl    %eax, %edx
jg  L16

答案 3 :(得分:1)

很少将三元运算符嵌入到另一个语句中来提高性能:)

如果每个操作码都很重要,请自行编写操作码 - 使用汇编程序。如果可能,还要考虑使用simd指令。我也对目标平台感兴趣。 ARM汇编程序喜欢这种类型的比较,并且有操作码来加速这种类型的饱和数学运算。