优化速度 - C中的4维数组查找

时间:2010-03-13 20:50:42

标签: c arrays performance lookup

我有一个适应度函数,它根据位于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特定的优化也很有帮助。 感谢

12 个答案:

答案 0 :(得分:9)

数组项的范围是多少?如果您可以将数组基类型更改为unsigned short或unsigned char,则可能会减少缓存未命中次数,因为数组的大部分适合缓存。

答案 1 :(得分:5)

您的大部分时间可能都会进入缓存未命中状态。如果你可以优化它们,你可以获得巨大的性能提升。

答案 2 :(得分:2)

请记住,C / C ++数组存储在row-major order中。请记住存储数据,以便及时密切引用的地址紧密地驻留在内存中。例如,将子结果存储在临时数组中可能是有意义的。然后,您可以按顺序处理一行元素。这样,处理器高速缓存将始终在迭代期间包含行,并且将需要较少的存储器操作。但是,您可能需要模块化lookup_array函数。甚至可能将它分成四个(按数组中的维数)。

答案 3 :(得分:2)

问题肯定与矩阵的大小有关。您不能通过将其声明为单个数组来优化它,因为它是编译器自动执行的操作。

一切都取决于您用于访问数据的顺序,即输入数组的内容。

唯一可以做的就是在当地工作:阅读this one,它应该会给你一些灵感。

顺便说一下,我建议你用四个参数替换输入数组:它会更直观,并且不会出错。

祝你好运

答案 4 :(得分:2)

提高绩效的一些建议:

  • Parallelise。这是一个非常简单的缩减,可以在OpenMP或MPI中编程。
  • 重新排序数据以改善地点。例如,首先尝试排序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;
}

移动寄存器可能比访问内存更快,尽管这种内存仍应保留在最里面的缓存中。