提高访问大型数组元素的性能

Improve performance for accessing large array elements

本文关键字:大型 数组元素 性能 访问 高访问      更新时间:2023-10-16

我正在尝试计算(n x n)乘法表中的唯一条目数量。

(12x12)乘法表:

×   1   2   3   4   5   6   7   8   9   10  11  12
1   1   2   3   4   5   6   7   8   9   10  11  12
2   2   4   6   8   10  12  14  16  18  20  22  24
3   3   6   9   12  15  18  21  24  27  30  33  36
4   4   8   12  16  20  24  28  32  36  40  44  48
5   5   10  15  20  25  30  35  40  45  50  55  60
6   6   12  18  24  30  36  42  48  54  60  66  72
7   7   14  21  28  35  42  49  56  63  70  77  84
8   8   16  24  32  40  48  56  64  72  80  88  96
9   9   18  27  36  45  54  63  72  81  90  99  108
10  10  20  30  40  50  60  70  80  90  100 110 120
11  11  22  33  44  55  66  77  88  99  110 121 132
12  12  24  36  48  60  72  84  96  108 120 132 144

现在," ANS"数组的第n个元素在A(n x n)乘法表中保存了许多唯一条目。
我注意到,访问大"标记"数组的元素需要花费很多时间。尽管我每次都访问一个元素并不需要太多时间。
现在,我的代码大约需要12-13秒才能完成构建" ANS"数组。我可以更优化它,以便大约5秒钟吗?

#include <stdio.h>
#define MAX 30000
char mark[(MAX*MAX) + 1];
int ans[MAX+1];
void calc(){
    int i, j, cnt = 0, x;
    ans[0] = 0;
    for (i = 1; i <= MAX; i++){
        for (j = 1; j <= i; j++){
            x = i*j;
            if (!mark[x]){
                mark[x] =  1;
                cnt++;
            }
        }
        ans[i] = cnt;
    }
}
int main(){
    calc();
    printf("answers[%d] = %dn", MAX, ans[MAX]);
    /*
    int n, t;
    scanf("%d", &t);
    while (t--){
        scanf("%d", &n);
        printf("%dn", ans[n]);
    }
    */
    return 0;
}

编辑:我正在尝试计算(n x n)表中的唯一条目数。建造桌子不是这里的主要目标。我正在使用"标记"阵列检查新数字是否唯一。没有数组,我想不出任何其他方法。我尝试使用矢量位,这有助于降低内存,但需要更多时间。代码的真正慢部分是访问数组元素。每次n = 30k都有很大的进步。没有内存访问零件 -

        if (!mark[x]){
            mark[x] =  1;
            cnt++;
        }

完成该程序大约需要1秒钟。但是我想不出任何改变访问模式的方法。
如果我只是想在(30k x 30k)表中找出唯一的条目,我可以更改访问模式,例如

1 
2   4
3   6   9
4   8   12  16
5   10  15  20  25

当前,我们从头到尾(从左到右)检查每一行
我们可以接近每一列(上到底)

int lim = 1;
for (i = 1; i <= MAX; i++){
    lim += MAX;
    for (j = i*i; j <= lim; j+= i){
        if (!mark[j]){
            mark[j] = 1;
            cnt++;
        }
    }
    //ans[i] = cnt;
}
ans[MAX] = cnt;

这样,访问内存的大步越来越少。代码在我的PC中大约9秒(少3秒)完成。但是我试图为每张(n x n)表获得答案。

这是基于位标记的我的解决方案(即,我们使用一些索引作为标记)。它比原始解决方案将内存足迹降低了8次,大约1/4:

#include <stdio.h>
#define MAX 45000UL
unsigned char bit_mark[(MAX*MAX) >> 3]; // 8 bits per byte
unsigned int ans[MAX + 1];
unsigned char mask_tbl[] = {
    0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80
};
void bit_calc()
{
    unsigned int i, j, cnt = 0, x;
    ans[0] = 0;
    for (i = 1; i <= MAX; i++) {
        for (j = 1; j <= i; j++) {
            x = i * j;
            unsigned int idx = x >> 3;
            unsigned char mask = mask_tbl[x & 0x7];
            if (!(bit_mark[idx] & mask)) {
                bit_mark[idx] |= mask;
                cnt++;
            }
        }
        ans[i] = cnt;
    }
}
int main(){
    bit_calc();
    printf("answers[%ld] = %dn", MAX, ans[MAX]);
    return 0;
}

蛮力计数不会扩展,更好的算法会有所帮助。不幸的是,我什至不确定是否有这样的算法...

尽管编译可能已经足够聪明,可以自己做,但您可以在最内向的循环中尝试一个无分支的结构:

int b= !mark[x];
mark[x]|= b;
cnt+= b;

要生成此表(可能毫无意义)的表,您根本不需要使用乘法。

第一行只是乘法乘以1,因此您无需为此而乘。

对于第二行,您可以将第一行中的值添加到自己中。

对于第三行,您可以从第二行添加从第一行的值添加值。

对于第四行,您可以将第一个行的值添加到第三行的值。

对于第n行,您可以将第一行的值添加到第n-1行的值。

下一步是使用SIMD(例如MMX,SSE,AVX)在单个指令中进行4(或8或16或其他)。

之后的一步可能是使用多个CPU/线程(尽管我怀疑这对瓶颈而不是CPU时间可能没有多大帮助)。

注意:我说的是桌子"可能是巨大且可能毫无意义的",因为它太大而无法适合CPU的缓存,并且(对于大多数CPU而言),缓存失误比整数乘法慢。/em>