我有以下代码段,它是应用程序中的热点。
由于向量依赖性,for
循环未向量化。
有没有办法重写这个循环,使其运行得更快。
#define NUM_KEYS (1L << 20)
#define NUM_BUCKETS (1L << 10)
int i,k;
int shift = (1L << 11);
int key_array[NUM_KEYS],key_buff[NUM_KEYS];
int bucket_ptrs[NUM_BUCKETS];
for( i=0; i<NUM_KEYS; i++ )
{
k = key_array[i];
key_buff[bucket_ptrs[k >> shift]++] = k;
}
我尝试过的一种方法是创建一个临时数组来保存key_array
的移位值。
for( i=0; i<NUM_KEYS; i++ )
{
key_arrays[i] = key_array[i] >> shift;
}
for( i=0; i<NUM_KEYS; i++ )
{
k = key_array[i];
j = key_arrays[i];
key_buff[bucket_ptrs[j]++] = k;
}
这里第一个循环得到矢量化。但整体而言,表现并没有改善。
答案 0 :(得分:3)
为什么循环不可矢量化?
这是因为你在这里有非顺序内存访问:
key_buff[bucket_ptrs[k >> shift]++] = k;
bucket_ptrs
确定访问key_buff
的索引。由于这些索引遍布整个地方,因此内存访问是非顺序的。
目前,x86处理器仅支持SIMD加载/存储到连续的内存块。 (理想情况下也是对齐的)
如果您想要它进行矢量化,则需要AVX2的收集/散布指令。那些不存在,但应该出现在下一代英特尔处理器中。
这里第一个循环得到矢量化。但总的来说没有 绩效改善。
这是因为您正在添加额外的循环开销。所以你现在正在key_array
进行两次传球。如果有的话,我很惊讶它不是慢。
有没有办法重写这个循环以使其运行得更快。
我对此表示怀疑 - 至少在没有改变算法的情况下也是如此。至少,您希望key_buff
能够轻松适应您的L1缓存。
AVX2会让它矢量化,但问题是key_buff
是4MB。这不适合较低级别的缓存。所以即使AVX2也可能没什么用。您将完全受到内存访问的约束。
答案 1 :(得分:2)
你可能会咬你的是,虽然你对key_array的访问是顺序的,而bucket_ptrs足够小以适应L1,你对key_buff的访问都到处都是。
然而,你正在做的事情看起来像是基数排序中的一步。如果这实际上是你正在做的事情,你可以通过首先将桶的数量减少到32或64或左右来获得性能,排序最重要的五或六位。然后你有一大堆小数组,其中大部分可能都适合缓存,你可以使用另一个基数排序来对它们进行排序。