是否有任何 asm 指令可以加快 Core i7 架构上双精度/整数向量的最小值/最大值的计算?
更新:
没想到这么丰富的答案,谢谢。所以我看到 max/min 可以在没有分支的情况下完成。我有子问题:
有没有一种有效的方法来获取数组中最大双精度数的索引?
是否有任何 asm 指令可以加快 Core i7 架构上双精度/整数向量的最小值/最大值的计算?
更新:
没想到这么丰富的答案,谢谢。所以我看到 max/min 可以在没有分支的情况下完成。我有子问题:
有没有一种有效的方法来获取数组中最大双精度数的索引?
SSE4 具有PMAXSD
或PMAXUD
用于 32 位有符号/无符号整数,这可能很有用。
SSE2 具有MAXPD
并且MAXSD
在成对的双精度之间和之间进行比较,因此您遵循 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 个数组的 min 和 max 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;
}
针对第二部分,从最大操作中删除分支的传统优化是比较值,将标志作为单个位(给出 0 或 1),减去一个(给出 0 或 0xffff_ffff)并使用两个可能结果的异或,所以你得到( a > best ? ( current_index ^ best_index ) : 0 ) ^ best_index )
. 我怀疑是否有一种简单的 SSE 方法可以做到这一点,仅仅是因为 SSE 倾向于对打包值而不是标记值进行操作;有一些水平索引操作,所以你可以尝试找到最大值,然后从原始向量中的所有元素中减去它,然后收集符号位,零符号位将对应于最大值的索引,但这可能除非您使用短裤或字节,否则不会有任何改进。
SSE 的 MAXPS 和 MINPS 都对压缩的单精度浮点数进行运算。PMAXSW、PMINSW、PMAXUB 和 PMINUB 都对压缩的 8 位字(有符号或无符号)进行操作。请注意,它们按元素比较两个输入 SSE 寄存器或地址位置,并将结果存储到 SSE 寄存器或内存位置。
SSE2 版本的 MAXPS 和 MINPS 应该适用于双精度浮点数。
您使用的是什么编译器和优化标志?如果您的目标支持,gcc 4.0 及更高版本应自动矢量化操作,早期版本可能需要特定标志。
回答您的第二个问题:在大多数平台上,有些库已经包含此操作(以及大多数其他简单向量操作)的优化实现。 使用它们。
vDSP_maxviD( )
和cblas_idamax( )
cblas_idamax( )
cblas_idamax( )
BLAS 库,根据其出处可能会或可能不会进行良好调整;关心性能的用户通常会有一个很好的实现(或者可以被说服安装一个)更新:我刚刚意识到你在第 2 部分中说的是“数组”,而不是“向量”。无论如何我都会把它留在这里,以防它有用。
回复:第二部分:在 SSE 向量中找到最大/最小元素的索引:
做一个水平最大值。对于 2 个double
元素的 128b 向量,只需一个shufpd
+maxpd
即可将结果广播留给两个元素。
对于其他情况,当然会采取更多的步骤。有关想法,请参阅在 x86 上进行水平浮点向量求和的最快方法,替换addps
为maxps
or minps
。(但请注意,16 位整数是特殊的,因为您可以使用 SSE4 phminposuw
。对于最大值,从 255 中减去)
在向量原始向量和每个元素都是最大值的向量之间进行压缩比较。
(pcmpeqq
整数位模式或通常cmpeqpd
都适用于这种double
情况)。
int _mm_movemask_pd (__m128d a)
( movmskpd
)将比较结果作为整数位图。bsf
)它的(第一个)匹配:index = _bit_scan_forward(cmpmask)
。如果您使用整数比较, cmpmask = 0 是不可能的(因为即使它们是 NaN 也至少有一个元素会匹配)。这应该只编译为 6 条指令(包括 a movapd
)。是的,刚刚检查了 Godbolt 编译器资源管理器,它确实使用了 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
它与 NaN 输入不可交换。如果 NaN 是可能的,并且您不关心 Intel Nehalem 的性能,您可以考虑使用_mm_cmpeq_epi64
来比较位模式。不过,从 float 到 vec-int 的旁路延迟是 Nehalem 的一个问题。
NaN != IEEE 浮点中的 NaN,因此_mm_cmpeq_pd
在全 NaN 情况下,结果掩码可能全为零。
在 2 元素的情况下,您可以做的另一件事总是得到 0 或 1 是用cmpmask >> 1
. (bsf
输入=全零很奇怪)。
在回答您的第二个问题时,您可能值得考虑一下您收集和存储这些数据的方式。
您可以将数据存储在始终保持数据排序的 B 树中,只需要对数比较操作。
然后,您始终知道最大值在哪里。