也就是说,进行测试的最快方法是什么
if( a >= ( b - c > 0 ? b - c : 0 ) &&
a <= ( b + c < 255 ? b + c : 255 ) )
...
如果a,b和c都是unsigned char
又名BYTE
。我正在尝试优化图像扫描过程以查找子图像,并且每次扫描大约需要300万次这样的比较,因此即使是微小的优化也可能会有所帮助。
不确定,但也许某种按位操作?可能在c中添加1并测试小于和大于没有等于零件的情况?我不知道!
答案 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
a
,b
和c
,您就可以将代码更改为更简单的
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) ...
假设a
,b
和c
为普通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汇编程序喜欢这种类型的比较,并且有操作码来加速这种类型的饱和数学运算。