我有一个适应度函数,它根据位于4D数组上的数据对int数组上的值进行评分。分析器说这个功能占用了80%的CPU时间(需要数百万次)。我似乎无法进一步优化它(如果它甚至可能)。这是功能:
unsigned int lookup_array[26][26][26][26]; /* lookup_array is a global variable */
unsigned int get_i_score(unsigned int *input) {
register unsigned int i, score = 0;
for(i = len - 3; i--; )
score += lookup_array[input[i]][input[i + 1]][input[i + 2]][input[i + 3]];
return(score)
}
我试图将阵列扁平化为单一维度,但性能没有任何改善。这是在IA32 CPU上运行的。任何CPU特定的优化也很有帮助。 感谢
答案 0 :(得分:9)
数组项的范围是多少?如果您可以将数组基类型更改为unsigned short或unsigned char,则可能会减少缓存未命中次数,因为数组的大部分适合缓存。
答案 1 :(得分:5)
您的大部分时间可能都会进入缓存未命中状态。如果你可以优化它们,你可以获得巨大的性能提升。
答案 2 :(得分:2)
请记住,C / C ++数组存储在row-major order中。请记住存储数据,以便及时密切引用的地址紧密地驻留在内存中。例如,将子结果存储在临时数组中可能是有意义的。然后,您可以按顺序处理一行元素。这样,处理器高速缓存将始终在迭代期间包含行,并且将需要较少的存储器操作。但是,您可能需要模块化lookup_array函数。甚至可能将它分成四个(按数组中的维数)。
答案 3 :(得分:2)
问题肯定与矩阵的大小有关。您不能通过将其声明为单个数组来优化它,因为它是编译器自动执行的操作。
一切都取决于您用于访问数据的顺序,即输入数组的内容。
唯一可以做的就是在当地工作:阅读this one,它应该会给你一些灵感。
顺便说一下,我建议你用四个参数替换输入数组:它会更直观,并且不会出错。
祝你好运答案 4 :(得分:2)
提高绩效的一些建议:
input
。关于重新排序,如果你展平数组并使用线性坐标,那么就有可能。
另一点,将处理器的理论峰值性能(整数运算)与您获得的性能进行比较(快速计算汇编生成的指令,乘以输入的长度等)并查看是否那里有显着改善的空间。
答案 5 :(得分:1)
我有几点建议:
unsigned int lookup_array[26][26][26][26]; /* lookup_array is a global variable */
unsigned int get_i_score(unsigned int *input, len) {
register unsigned int i, score = 0;
unsigned int *a=input;
unsigned int *b=input+1;
unsigned int *c=input+2;
unsigned int *d=input+3;
for(i = 0; i < (len - 3); i++, a++, b++, c++, d++)
score += lookup_array[*a][*b][*c][*d];
return(score)
}
或尝试
for(i = 0; i < (len - 3); i++, a=b, b=c, c=d, d++)
score += lookup_array[*a][*b][*c][*d];
另外,鉴于只有26个值,为什么要将输入数组放在无符号整数中?如果它是char *input
,那么你将使用1/4的内存,因此使用1/4的内存带宽。显然,d的类型必须匹配。同样,如果得分值不需要是无符号整数,则使用chars或uint16_t使数组变小。
答案 6 :(得分:0)
如果lookup_array主要为零,则可以用较小数组上的哈希表查找替换。内联查找函数可以计算4维的偏移量([5,6,7,8] =(4 * 26 * 26 * 26)+(5 * 26 * 26)+(6 * 26)+7 = 73847)。散列键可能只是偏移的低几位(取决于预期数组的稀疏程度)。如果哈希表中存在偏移量,则使用该值,如果它不存在,则为0 ...
如果输入具有任意长度,也可以使用类似的东西展开循环。只需要len输入所需的输入(而不是原始循环中的len * 4)。register int j, x1, x2, x3, x4;
register unsigned int *p;
p = input;
x1 = *p++;
x2 = *p++;
x3 = *p++;
for (j = (len - 3) / 20; j--; ) {
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = *p++;
score += lookup_array[x2][x3][x4][x1];
x2 = *p++;
score += lookup_array[x3][x4][x1][x2];
x3 = *p++;
score += lookup_array[x4][x1][x2][x3];
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = *p++;
score += lookup_array[x2][x3][x4][x1];
x2 = *p++;
score += lookup_array[x3][x4][x1][x2];
x3 = *p++;
score += lookup_array[x4][x1][x2][x3];
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = *p++;
score += lookup_array[x2][x3][x4][x1];
x2 = *p++;
score += lookup_array[x3][x4][x1][x2];
x3 = *p++;
score += lookup_array[x4][x1][x2][x3];
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = *p++;
score += lookup_array[x2][x3][x4][x1];
x2 = *p++;
score += lookup_array[x3][x4][x1][x2];
x3 = *p++;
score += lookup_array[x4][x1][x2][x3];
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = *p++;
score += lookup_array[x2][x3][x4][x1];
x2 = *p++;
score += lookup_array[x3][x4][x1][x2];
x3 = *p++;
score += lookup_array[x4][x1][x2][x3];
/* that's 20 iterations, add more if you like */
}
for (j = (len - 3) % 20; j--; ) {
x4 = *p++;
score += lookup_array[x1][x2][x3][x4];
x1 = x2;
x2 = x3;
x3 = x4;
}
答案 7 :(得分:0)
你可以通过在Duffs device.
的某些变体中展开循环来挤出一点点答案 8 :(得分:0)
多维数组通常会将编译器限制为一个或多个乘法运算。某些CPU可能会很慢。常见的解决方法是将N维数组转换为指向(N-1)维元素的指针数组。 4维度。数组很烦人(26个指针到26 * 26指针到26 * 26 * 26行...)我建议尝试并比较结果。不能保证它更快:编译器在优化数组访问方面非常聪明,而间接访问链则更有可能使缓存无效。
再见
答案 9 :(得分:0)
如果将其转换为大小为26 * 26 * 26 * 26的平面数组,则每个循环只需查找一次input
数组:
unsigned int get_i_score(unsigned int *input)
{
unsigned int i = len - 3, score = 0, index;
index = input[i] * 26 * 26 +
input[i + 1] * 26 +
input[i + 2];
while (--i)
{
index += input[i] * 26 * 26 * 26;
score += lookup_array[index];
index /= 26 ;
}
return score;
}
额外费用是乘法和除法。它是否最终在实践中更快 - 你将不得不进行测试。
(顺便说一句,现代编译器经常会忽略register
关键字 - 通常最好将寄存器分配保留给优化器。)
答案 10 :(得分:0)
阵列的内容是否变化很大?也许预先计算得分会更快,然后每次阵列更改时修改预先计算的得分?与使用触发器在SQL中实现视图的方式类似。
答案 11 :(得分:0)
也许你可以通过使用局部变量来消除对input
数组的一些访问。
unsigned int lookup_array[26][26][26][26]; /* lookup_array is a global variable */ unsigned int get_i_score(unsigned int *input, unsigned int len) { unsigned int i, score, a, b, c, d; score = 0; a = input[i + 0]; b = input[i + 1]; c = input[i + 2]; d = input[i + 3]; for (i = len - 3; i-- > 0; ) { d = c, c = b, b = a, a = input[i]; score += lookup_array[a][b][c][d]; } return score; }
移动寄存器可能比访问内存更快,尽管这种内存仍应保留在最里面的缓存中。