提高访问大型数组元素的性能
Posted
技术标签:
【中文标题】提高访问大型数组元素的性能【英文标题】:Improve performance for accessing large array elements 【发布时间】:2017-12-01 13:48:51 【问题描述】:我正在尝试计算 (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 x n) 乘法表中的唯一条目数。 我注意到访问大型 'mark' 数组的元素需要很长时间。虽然如果我每次都访问一个元素不会花费太多时间。 现在我的代码需要大约 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) 表中唯一条目的数量。建立表格不是这里的主要目标。我正在使用“标记”数组来检查新号码是否唯一。如果没有数组,我想不出任何其他方法。我尝试使用矢量位,它有助于降低内存,但需要更多时间。代码中真正慢的部分是访问数组元素。对于 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) 表获取答案。
【问题讨论】:
12x12 乘法表根本不值得存储在内存中。只需在打印时即时计算条目.... ups 误读,但如果是 100 万 x 100 万,那么更多的 cpu 会胜过内存 您尝试过优化构建吗? 这个程序的慢部分是 printf() ...正如@tobi303 所说,它不值得记忆。进行计算的额外周期将花费您 1000 秒。但可以为您节省 8000 GB 的内存(需要长整数,而不仅仅是整数)。即使您可以存储其中的一小部分,RAM 成本也不值得 @UKMonkey 我不明白你是如何得出这个 sn-p 需要 8000 GB 的结论的。 @FrançoisAndrieux 你是对的 - 我读了 tobis 的回复并错过了 OP 的 30,000 所以它是 4(int) * 30,000 * 30,000 = 14G ...仍然比我愿意投资的 RAM 多在乘法表中 【参考方案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;
蛮力计数不会扩展,更好的算法会有所帮助。不幸的是,我什至不确定是否有这样的算法......
【讨论】:
您现在可以进行进一步的微优化,即一次测试多个位。数据通常是稀疏的,因此您可以通过一次屏蔽多个位或一次对 64 位使用 FFS 指令来避免单独检查每个位:en.wikipedia.org/wiki/Find_first_set【参考方案2】:虽然编译可能足够聪明,可以自己完成,但您可以尝试在最内层循环中使用无分支构造:
int b= !mark[x];
mark[x]|= b;
cnt+= b;
【讨论】:
我试过了,性能比原来的方案差一点... @AndriyBerestovskyy:是的,我并不感到惊讶,因为每次都会执行写入。mark
中的值可以是什么?
这些只是统计唯一数字的标志。如果没有设置标记,我们只是设置它并增加唯一数字的计数器......所以我们可能会尝试使用位来代替,但我不确定它会有多大帮助......
@AndriyBerestovskyy:你真的愿意优化这个功能,或者其他方法可以计算唯一条目吗?你需要所有的 ans[i] 吗?【参考方案3】:
要生成这个(可能很大且可能毫无意义的)表,您根本不需要使用乘法。
第一行只是乘以 1,因此您不需要实际乘以。
对于第二行,您可以将第一行中的值添加到自身。
对于第三行,您可以将第一行的值添加到第二行的值中。
对于第四行,您可以将第一行的值添加到第三行的值中。
对于第 N 行,您可以将第一行的值添加到第 N-1 行的值中。
下一步是使用 SIMD(例如 MMX、SSE、AVX)在一条指令中执行 4 个(或 8 个或 16 个或其他)加法。
之后的步骤可能是使用多个 CPU/线程(尽管我怀疑这不会有太大帮助,因为写入可能是瓶颈,而不是 CPU 时间)。
注意:我是说该表“可能很大而且可能毫无意义”,因为它太大而无法放入 CPU 的缓存中,而且(对于大多数 CPU)缓存未命中比整数乘法要慢。
【讨论】:
这与乘法表无关。代码中没有。问题中的表格仅用于解释。瓶颈不是 CPU,所以我猜 SIMD 在这里无济于事......以上是关于提高访问大型数组元素的性能的主要内容,如果未能解决你的问题,请参考以下文章