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

时间:2017-12-01 13:48:51

标签: c++ arrays algorithm performance optimization

我试图计算(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个元素数组保存(n×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("ans[%d] = %d\n", MAX, ans[MAX]);

    /*
    int n, t;
    scanf("%d", &t);

    while (t--){

        scanf("%d", &n);
        printf("%d\n", ans[n]);
    }
    */
    return 0;
}

编辑:我试图计算(n x n)表中唯一条目的数量。建造桌子不是这里的主要目标。我正在使用&#39;标记&#39;用于检查新数字是否唯一的数组。没有数组,我无法想到任何其他方法。我尝试使用矢量位,它有助于降低内存但需要更多时间。代码的真正缓慢部分是访问数组元素。 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;

因此,访问内存的步伐越来越少。并且代码在我的电脑中完成大约9秒(少约3秒)。但是我试图为每个(n x n)表找到答案。

3 个答案:

答案 0 :(得分:1)

这是我的基于位标记的解决方案(即我们使用位索引作为标记)。它将内存占用空间减少了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("ans[%ld] = %d\n", MAX, ans[MAX]);

    return 0;
}

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

答案 1 :(得分:0)

虽然编译可能已经足够聪明,可以单独执行,但您可以在最里面的循环中尝试无分支构造:

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

答案 2 :(得分:0)

为了生成这个(可能是巨大且可能毫无意义的)表,你根本不需要使用乘法。

第一行只是乘以1,所以你不需要实际乘以它。

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

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

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

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

下一步是使用SIMD(例如MMX,SSE,AVX)在一条指令中添加4(或8或16或其他)。

之后的步骤可能是使用多个CPU /线程(虽然我怀疑这不会有太大帮助,因为写入可能是瓶颈,而不是CPU时间)。

注意:我说这张桌子很可能很大,而且可能毫无意义。&#34;因为它太大而不适合CPU的缓存,并且(对于大多数CPU)缓存未命中比整数乘法慢。