x86 max / min asm指令?

时间:2009-12-28 14:46:45

标签: assembly x86 intrinsics

是否有任何asm指令可以加速Core i7架构上双精度/整数向量的最小值/最大值的计算?

更新

我没想到会有如此丰富的答案,谢谢。 所以我看到max / min可以不分支。 我有一个小问题:

有没有一种有效的方法来获得数组中最大双精度的索引?

6 个答案:

答案 0 :(得分:12)

对于32位有符号/无符号整数,SSE4具有PMAXSDPMAXUD,这可能很有用。

SSE2有MAXPDMAXSD,它们在两对之间和之间进行比较,所以你跟随n / 2-1 MAXPD和一个MAXSD得到n的向量的最大值,通常交错负荷和操作。

上面有MIN等价物。

对于双重案例,在SSE模式下,你可能不会在汇编程序方面做得比半合适的C ++编译器更好:

peregrino:$ g++ -O3 src/min_max.cpp -o bin/min_max
peregrino:$ g++ -O3 -msse4 -mfpmath=sse src/min_max.cpp -o bin/min_max_sse
peregrino:$ time bin/min_max
0,40

real    0m0.874s
user    0m0.796s
sys 0m0.004s
peregrino:$ time bin/min_max_sse 
0,40

real    0m0.457s
user    0m0.404s
sys 0m0.000s

其中min_max使用朴素循环计算500次双倍100,000次的最小值和最大值:

bool min_max ( double array[], size_t len, double& min, double& max )
{
    double min_value = array [ 0 ];
    double max_value = array [ 0 ];

    for ( size_t index = 1; index < len; ++index ) {
        if ( array [ index ] < min_value ) min_value = array [ index ];
        if ( array [ index ] > max_value ) max_value = array [ index ];
    }

    min = min_value;
    max = max_value;
}

响应第二部分,从max操作中删除分支的传统优化是比较值,将标志作为单个位(给出0或1),减去1(给出0或0xffff_ffff)和'和'它与两个可能结果的xor相同,因此你得到的等价于( a > best ? ( current_index ^ best_index ) : 0 ) ^ best_index )。我怀疑有一种简单的SSE方式,只是因为SSE倾向于对打包值而不是标记值进行操作;有一些水平索引操作,所以你可以尝试找到最大值,然后从原始向量中的所有元素中减去它,然后收集符号位,零符号将对应于最大值的索引,但这可能是除非你使用短路或字节,否则不是改进。

答案 1 :(得分:4)

来自SSE的MAXPS和MINPS都在压缩的单精度浮点数上运行。 PMAXSW,PMINSW,PMAXUB和PMINUB都在压缩的8位字上运行,无论是有符号还是无符号。请注意,它们按元件比较两个输入SSE寄存器或地址位置,并将结果存储到SSE寄存器或存储器位置。

MAXPS和MINPS的SSE2版本应该适用于双精度浮点数。

您使用的是哪些编译器和优化标志? gcc 4.0和更好的应该自动向量化操作,如果你的目标支持它们,早期版本可能需要一个特定的标志。

答案 2 :(得分:2)

如果您使用英特尔的IPP库,则可以使用向量statistical functions计算向量最小值/最大值(以及其他内容)

答案 3 :(得分:2)

回答你的第二个问题:在大多数平台上,有些库已经包含了这个非常操作的优化实现(以及大多数其他简单的向量操作)。 使用

  • 在OS X上,Accelerate.framework中有vDSP_maxviD( )cblas_idamax( )
  • 英特尔编译器包括IPP和MKL库,它们具有高性能实现,包括cblas_idamax( )
  • 大多数Linux系统在BLAS库中都会有cblas_idamax( ),根据其来源,可能会也可能不会很好地调整;关心性能的用户通常会有一个很好的实现(或者可以被说服安装一个)
  • 如果一切都失败了,您可以使用ATLAS(自动调谐线性代数软件)在目标平台上获得不错的性能实施

答案 4 :(得分:1)

更新:我刚刚意识到你在第2部分中说过“数组”,而不是“矢量”。无论如何我都会把它留在这里以防它有用。

re:第二部分:找到SSE向量中max / min元素的索引:

  • 执行水平最大值。对于2个double元素的128b向量,只有一个shufpd + maxpd将结果广播留给两个元素。

    对于其他情况,它当然会采取更多步骤。有关提示,请参阅Fastest way to do horizontal float vector sum on x86,将addps替换为maxpsminps。 (但请注意,16位整数是特殊的,因为您可以使用SSE4 phminposuw。对于最大值,从255减去

  • 在矢量原始矢量和矢量之间进行打包比较,其中每个元素都是最大值。

    pcmpeqq整数位模式或通常的cmpeqpd都适用于double情况。

  • int _mm_movemask_pd (__m128d a) (movmskpd)将比较结果作为整数位图。
  • 用于(第一次)匹配的位扫描(bsf):index = _bit_scan_forward(cmpmask)。如果使用整数比较,则cmpmask = 0是不可能的(因为即使它们是NaN,至少有一个元素也会匹配)。

这应该只编译为6条指令(包括movapd)。是的,只需检查the Godbolt compiler explorer就可以了。它与SSE一起检查。

#include <immintrin.h>
#include <x86intrin.h>

int maxpos(__m128d v) {
  __m128d swapped = _mm_shuffle_pd(v,v, 1);
  __m128d maxbcast = _mm_max_pd(swapped, v);
  __m128d cmp = _mm_cmpeq_pd(maxbcast, v);
  int cmpmask = _mm_movemask_pd(cmp);
  return _bit_scan_forward(cmpmask);
}

请注意_mm_max_pd is not commutative with NaN inputs。如果可以使用NaN,并且您不关心Intel Nehalem的性能,则可以考虑使用_mm_cmpeq_epi64来比较位模式。但是,从浮点数到vec-int的旁路延迟是Nehalem的一个问题。

NaN!= IEEE浮点中的NaN,因此在全NaN情况下_mm_cmpeq_pd结果掩码可以全为零。

在2元素的情况下,总是得到0或1可以做的另一件事是用cmpmask >> 1替换位扫描。 (bsf很奇怪,输入=全零)。

答案 5 :(得分:-1)

在回答您的第二个问题时,您可能需要考虑收集和存储此数据的方式。

您可以将数据存储在B树中,以便始终对数据进行排序,只需要进行对数比较操作。

然后你随时都知道最大值是什么。

http://en.wikipedia.org/wiki/B_tree