找出最少3个数字的最快方法?

在我写的一个程序中,在这个例程中,20%的时间用于在内循环中找出最少3个数字:

static inline unsigned int min(unsigned int a, unsigned int b, unsigned int c) { unsigned int m = a; if (m > b) m = b; if (m > c) m = c; return m; } 

有什么方法可以加快速度吗? 对于x86 / x86_64,我也可以使用汇编代码。

编辑:回复一些评论:
*正在使用的编译器是gcc 4.3.3
*就集会而言,我只是初学者。 我在这里要求组装,学习如何做到这一点。 🙂
*我运行四核Intel 64,因此支持MMX / SSE等。
*这里很难发布循环,但我可以告诉你它是levenshtein算法的一个高度优化的实现。

这是编译器给我的非内联版本的min:

 .globl min .type min, @function min: pushl %ebp movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %eax movl 16(%ebp), %ecx cmpl %edx, %eax jbe .L2 movl %edx, %eax .L2: cmpl %ecx, %eax jbe .L3 movl %ecx, %eax .L3: popl %ebp ret .size min, .-min .ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3" .section .note.GNU-stack,"",@progbits 

内联版本在-O2优化代码内(甚至我的标记mrk = 0xfefefefe,在调用min()之前和之后)都被gcc优化掉了,所以我无法掌握它。

更新:我测试了Nils建议的变化,但是使用min()的汇编版本没有明显的性能提升。 但是,通过使用-march = i686编译程序,我获得了12.5%的提升,我想这是因为整个程序正在获得gcc使用此选项生成的新快速指令的好处。 谢谢你的帮助。

PS – 我使用ruby探测器来测量性能(我的C程序是一个由ruby程序加载的共享库),所以我只能花时间花在ruby程序调用的顶级C函数上,最终调用min ()在堆栈中。 请看这个问题 。

首先确保使用适当的-march设置。 GCC默认不使用原始i386不支持的任何指令 – 允许它使用更新的指令集有时会产生巨大的差异! 在-march=core2 -O2我得到:

 min: pushl %ebp movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %ecx movl 16(%ebp), %eax cmpl %edx, %ecx leave cmovbe %ecx, %edx cmpl %eax, %edx cmovbe %edx, %eax ret 

在这里使用cmov可以帮助您避免分支延迟 – 并且只需通过-march就可以在没有任何内联asm的情况下获得它。 当内联到更大的function时,这可能更有效,可能只有四个assembly操作。 如果你需要比这更快的东西,看看你是否可以让SSE向量操作在整个算法的上下文中工作。

假设你的编译器没有去吃午餐,这应该编译为两个比较和两个条件移动。 不可能做得更好。

如果您发布编译器实际生成的程序集,我们可以看到是否有任何不必要的东西会降低它的速度。

要检查的首要问题是例程实际上是内联的。 编译器没有义务这样做,如果它正在生成一个函数调用,那么对于这样一个简单的操作来说,这将是非常昂贵的。

如果调用真的被内联,那么循环展开可能是有益的,正如DigitalRoss所说,或者矢量化是可能的。

编辑:如果你想对代码进行矢量化,并且正在使用最新的x86处理器,你将需要使用SSE4.1 pminud指令(内在: _mm_min_epu32 ),该指令采用两个四个无符号整数的向量,并生成一个向量四个无符号的整数。 结果的每个元素是两个输入中相应元素的最小值。

我还注意到你的编译器使用了分支而不是条件移动; 你可能应该首先尝试一个使用条件移动的版本,看看在你进行矢量实现的比赛之前是否能获得任何加速。

我对x86汇编器实现,GCC语法的看法。 翻译成另一个内联汇编语法应该是微不足道的:

 int inline least (int a, int b, int c) { int result; __asm__ ("mov %1, %0\n\t" "cmp %0, %2\n\t" "cmovle %2, %0\n\t" "cmp %0, %3\n\t" "cmovle %3, %0\n\t" : "=r"(result) : "r"(a), "r"(b), "r"(c) ); return result; } 

新版和改进版:

 int inline least (int a, int b, int c) { __asm__ ( "cmp %0, %1\n\t" "cmovle %1, %0\n\t" "cmp %0, %2\n\t" "cmovle %2, %0\n\t" : "+r"(a) : "%r"(b), "r"(c) ); return a; } 

注意:它可能也可能不比C代码快。

这取决于很多因素。 如果分支不可预测,通常cmov会胜出(在某些x86架构上)OTOH内联汇编程序始终是优化器的问题,因此对周围代码的优化代价可能超过所有增益。

Btw Sudhanshu,听听这段代码如何与你的testdata一起表演会很有趣。

SSE2指令扩展包含一个整数min指令,一次可以选择8个最小值。 请参阅http://www.intel.com/software/products/compilers/clin/docs/ug_cpp/comm1046.htm中的 _mm_mulhi_epu16

我的AMD Phenom上的这种替换时钟速度提高了大约1.5%:

 static inline unsigned int min(unsigned int a, unsigned int b, unsigned int c) { asm("cmp %1,%0\n" "cmova %1,%0\n" "cmp %2,%0\n" "cmova %2,%0\n" : "+r" (a) : "r" (b), "r" (c)); return a; } 

结果可能有所不同 一些x86处理器不能很好地处理CMOV。

首先,看看反汇编。 那会告诉你很多。 例如,正如所写,有2个if语句(这意味着有2个可能的分支错误预测),但我的猜测是,一个体面的现代C编译器将有一些聪明的优化,可以在没有分支的情况下完成。 我很想知道。

其次,如果您的libc具有特殊的内置最小/最大function,请使用它们。 例如,GNU libc的浮点数为fmin / fmax,他们声称“在某些处理器上,这些函数可以使用特殊的机器指令比同等的C代码更快地执行这些操作”。 也许有类似的东西。

最后,如果你对一堆数字并行执行此操作,可能会有向量指令来执行此操作,这可以提供显着的加速。 但是我甚至看到使用矢量单位时非矢量代码更快。 像“将一个uint加载到向量寄存器,调用向量最小函数,得到结果”这样的东西看起来很笨,但实际上可能更快。

如果您只进行一次比较,则可能需要手动展开循环。

首先,看看你是否可以让编译器为你展开循环,如果你不能,你可以自己动手。 这至少会减少循环控制开销……

是的,发布后,但我天真的优化是:

 static inline unsigned int min(unsigned int a, unsigned int b, unsigned int c) { unsigned int m = a; if (m > b) m = b; if (m > c) return c; return m; } 

您可以尝试这样的方法来保存声明和不必要的比较:

 static inline unsigned int min(unsigned int a, unsigned int b, unsigned int c) { if (a < b) { if (a < c) return a; else return c; } if (b < c) return b; else return c; } 

这些都是很好的答案。 冒着被指责不回答问题的风险,我也会看其他80%的时间。 Stackshots是我最喜欢的方法,可以找到值得优化的代码,特别是如果它是您发现的并不是绝对需要的函数调用。