矢量化具有间接访问的循环

时间:2012-12-06 20:02:22

标签: c performance optimization vectorization simd

我有以下代码段,它是应用程序中的热点。 由于向量依赖性,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;
}

这里第一个循环得到矢量化。但整体而言,表现并没有改善。

2 个答案:

答案 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或左右来获得性能,排序最重要的五或六位。然后你有一大堆小数组,其中大部分可能都适合缓存,你可以使用另一个基数排序来对它们进行排序。