C 对具有间接访问的循环进行矢量化
我有下面的代码片段,它是应用程序中的一个热点。 由于向量依赖性,循环的C 对具有间接访问的循环进行矢量化,c,performance,optimization,vectorization,simd,C,Performance,Optimization,Vectorization,Simd,我有下面的代码片段,它是应用程序中的一个热点。 由于向量依赖性,循环的未向量化。 是否有方法重写此循环以使其运行更快 #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];
未向量化。
是否有方法重写此循环以使其运行更快
#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_数组的访问是连续的,bucket_ptr足够小,可以放入L1,但对key_buff的访问却无处不在
不过,您所做的看起来像是基数排序中的一步。如果这就是您实际正在做的,那么您可以通过首先将存储桶的数量减少到32或64个左右,并按最重要的5或6位进行排序来获得性能。然后你有一大堆小数组,其中大部分可能适合缓存,你可以使用另一个基数排序过程对它们进行排序。可能让你头疼的是,虽然你对key_数组的访问是顺序的,bucket_ptr足够小,可以放入L1,但你对key_buff的访问到处都是
不过,您所做的看起来像是基数排序中的一步。如果这就是您实际正在做的,那么您可以通过首先将存储桶的数量减少到32或64个左右,并按最重要的5或6位进行排序来获得性能。然后你有一大堆小数组,其中大部分可能适合缓存,你可以使用另一个基数排序过程对它们进行排序
为什么循环不能矢量化
这是因为您在此处具有非顺序内存访问:
key_buff[bucket_ptrs[k >> shift]++] = k;
bucket\u ptrs
确定访问key\u buff
的索引。因为这些索引到处都是,所以内存访问是非顺序的
目前,x86处理器仅支持将SIMD加载/存储到连续的内存块。(也是理想的对齐方式)
如果希望它矢量化,则需要AVX2的聚集/分散指令。这些都不存在,但应该在下一代英特尔处理器中出现
这里,第一个循环被矢量化。但总的来说没有
业绩的改善
这是因为您正在添加额外的循环开销。因此,您现在对键数组进行了两次传递。如果有什么不同的话,我很惊讶它没有变慢
是否有方法重写此循环以使其运行更快
#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\u buff
能够舒适地放入一级缓存中
AVX2会让它矢量化,但问题是key\u buff
是4MB。这不适合放在较低级别的缓存中。因此,即使是AVX2也可能没有多大帮助。您将完全被内存访问绑定
为什么循环不能矢量化
这是因为您在此处具有非顺序内存访问:
key_buff[bucket_ptrs[k >> shift]++] = k;
bucket\u ptrs
确定访问key\u buff
的索引。因为这些索引到处都是,所以内存访问是非顺序的
目前,x86处理器仅支持将SIMD加载/存储到连续的内存块。(也是理想的对齐方式)
如果希望它矢量化,则需要AVX2的聚集/分散指令。这些都不存在,但应该在下一代英特尔处理器中出现
这里,第一个循环被矢量化。但总的来说没有
业绩的改善
这是因为您正在添加额外的循环开销。因此,您现在对键数组进行了两次传递。如果有什么不同的话,我很惊讶它没有变慢
是否有方法重写此循环以使其运行更快
#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\u buff
能够舒适地放入一级缓存中
AVX2会让它矢量化,但问题是key\u buff
是4MB。这不适合放在较低级别的缓存中。因此,即使是AVX2也可能没有多大帮助。您将完全被内存访问绑定。Man。。首先对其进行分析,找出瓶颈所在。然后优化。为什么猜测?不,它是不可矢量化的,因为内存访问不是连续的。您需要AVX2的收集/分散指令。我相信,明年将推出的英特尔Haswell处理器中也会出现这种情况。我的第二种循环拆分方法没有任何改进。在这种方法中,第一个循环被矢量化。因此,我期望获得一些性能提升。这可能是因为非顺序访问非常昂贵,它会淹没其他所有内容(例如额外的循环开销)。第二种方法会带来伤害,因为您正在进行额外的缓存活动。我不太相信即使使用AVX2指令,您也能使代码更快,因为对key\u buff
的访问的局部性很差。。首先对其进行分析,找出瓶颈所在。然后优化。为什么猜测?不,它是不可矢量化的,因为内存访问不是连续的。您需要AVX2的收集/分散指令。我相信,明年将推出的英特尔Haswell处理器中也会出现这种情况。我的第二种循环拆分方法没有任何改进。在这种方法中,第一个循环被矢量化。因此,我期望获得一些性能提升。这可能是因为非顺序访问非常昂贵,它会淹没其他所有内容(例如额外的循环开销)。第二种方法会带来伤害,因为您正在进行额外的缓存活动。即使使用AVX2指令,我也不太相信您会使代码更快,因为对key\u buff
的访问的局部性很差。