我有一个非常大的数组(例如1000万个元素),它只包含1和0。我还有一堆并行线程(例如10个),我想将这个大型数组块分成不同的线程,并使每个线程对它们负责的部分求和。
我在C&使用“+”运算符进行pthreads。但是,由于数组只包含1和0,我想知道有没有更快的方法来实现这个求和? (通过按位运算符,移位等?)因为我正在处理非常大的数组,所以天真的求和会破坏性能。
答案 0 :(得分:6)
你在现代CPU上添加2个1000万个元素的数组......每秒可以执行大约3亿个指令(3GHz)。
即使必须单独添加每个元素,也可以在0.003秒内添加两个完整的数组。 (这确实是最糟糕的情况。在64位计算机上,你应该一次能够添加64个元素)
除非在内部循环中发生这种情况,否则这不应该会破坏性能。
考虑更全面地描述您的问题,并展示您当前的实施。
答案 1 :(得分:1)
首先,转换为执行SIMD向量和,并将向量寄存器的元素在循环外的末尾减少为单个和。这应该会在1/4的操作中得到相同的结果。然后展开该向量化循环,每个展开的迭代在单独的向量中求和,以暴露更大的指令级并行性,并在末尾组合部分和。有了它,你应该很容易地最大化内存带宽。
答案 2 :(得分:1)
如果您可以使用所有位而不是每个int使用1,那么性能至少可以提高;)
还使用SSE,__m128i _mm_add_epi32
,registers等等(eka)进行了测试,但未获得任何明显的提升。 (很可能我没有正确地做到这一点。)。
一切都很大程度上取决于环境如何创建数组,如何在其他地方使用等等。人们可以查看GPU处理,但这又变得专业化,并且可能更好地用于更重的计算然后+
。
无论如何,这是我在P4 2.8GHz上使用2G 慢 SDRAM进行的粗略样本结果;使用正常 1增量循环,展开2和8(在一个数字pr.int上),以及从CountBitsSetParallel结合展开的第二位旋转。既有线程也没有。如果您决定将它与线程结合使用,那么要小心点错。
./bcn -z330000000 -s3 -i1
sz_i : 330000000 * 4 = 1320000000 (bytes int array)
sz_bi : 10312500 * 4 = 41250000 (bytes bit array)
set every : 3 (+ 1 controll-bit)
iterations: 1
Allocated 1320000000 bytes for ari (0x68cff008 - 0xb77d8a08)
1289062 KiB
1258 MiB
1 GiB
Allocated 41250000 bytes for arbi (0x665a8008 - 0x68cfecd8)
40283 KiB
39 MiB
Setting values ...
--START--
1 iteration over 330,000,000 values
Running TEST_00 Int Normal ; sum = 110000001 ... time: 0.618463440
Running TEST_01 Int Unroll 2 ; sum = 110000001 ... time: 0.443277919
Running TEST_02 Int Unroll 8 ; sum = 110000001 ... time: 0.425574923
Running TEST_03 Int Bit Calc ; sum = 110000001 ... time: 0.068396207
Running TEST_04 Int Bit Table ; sum = 110000001 ... time: 0.056727713
...
1 iteration over 200,000,000
Running TEST_00 Int Normal ; sum = 66666668 ... time: 0.339017852
Running TEST_01 Int Unroll 2 ; sum = 66666668 ... time: 0.273805886
Running TEST_02 Int Unroll 8 ; sum = 66666668 ... time: 0.264436688
Running TEST_03 Int Bit Calc ; sum = 66666668 ... time: 0.032404574
Running TEST_04 Int Bit Table ; sum = 66666668 ... time: 0.034900498
...
100 iterations over 2,000,000 values
Running TEST_00 Int Normal ; sum = 666668 ... time: 0.373892700
Running TEST_01 Int Unroll 2 ; sum = 666668 ... time: 0.270294678
Running TEST_02 Int Unroll 8 ; sum = 666668 ... time: 0.260143237
Running TEST_03 Int Bit Calc ; sum = 666668 ... time: 0.031871318
Running TEST_04 Int Bit Table ; sum = 666668 ... time: 0.035358995
...
1 iteration over 10,000,000 values
Running TEST_00 Int Normal ; sum = 3333335 ... time: 0.023332354
Running TEST_01 Int Unroll 2 ; sum = 3333335 ... time: 0.011932137
Running TEST_02 Int Unroll 8 ; sum = 3333335 ... time: 0.013220130
Running TEST_03 Int Bit Calc ; sum = 3333335 ... time: 0.002068979
Running TEST_04 Int Bit Table ; sum = 3333335 ... time: 0.001758484
主题......
4 threads, 1 iteration pr. thread over 200,000,000 values
Running TEST_00 Int Normal ; sum = 66666668 ... time: 0.285753177
Running TEST_01 Int Unroll 2 ; sum = 66666668 ... time: 0.263798773
Running TEST_02 Int Unroll 8 ; sum = 66666668 ... time: 0.254483912
Running TEST_03 Int Bit Calc ; sum = 66666668 ... time: 0.031457365
Running TEST_04 Int Bit Table ; sum = 66666668 ... time: 0.036319760
Snip(对不起,短命名):
/* I used an array named "ari" for integer 1 value based array, and
"arbi" for integer array with bits set to 0 or 1.
#define SZ_I : number of elements (int based)
#define SZ_BI: number of elements (bit based) on number of SZ_I, or
as I did also by user input (argv)
*/
#define INT_BIT (CHAR_BIT * sizeof(int))
#define SZ_I (100000000U)
#define SZ_BI ((SZ_I / INT_BIT ) + (SZ_I / INT_BIT * INT_BIT != SZ_I))
static unsigned int sz_i = SZ_I;
static unsigned int sz_bi = SZ_BI;
static unsigned int *ari;
static unsigned int *arbi;
/* (if value (sz_i) from argv ) */
sz_bi = sz_i / INT_BIT + (sz_i / INT_BIT * INT_BIT != sz_i);
...
#define UNROLL 8
static __inline__ unsigned int bitcnt(unsigned int v)
{
v = v - ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
}
unsigned int test_03(void)
{
unsigned int i = 0;
unsigned int sum = 0;
unsigned int rep = (sz_bi / UNROLL);
unsigned int rst = (sz_bi % UNROLL);
while (rep-- > 0) {
sum += bitcnt(arbi[i]);
sum += bitcnt(arbi[i+1]);
sum += bitcnt(arbi[i+2]);
sum += bitcnt(arbi[i+3]);
sum += bitcnt(arbi[i+4]);
sum += bitcnt(arbi[i+5]);
sum += bitcnt(arbi[i+6]);
sum += bitcnt(arbi[i+7]);
i += UNROLL;
}
switch (rst) {
case 7: sum += bitcnt(arbi[i+6]);
case 6: sum += bitcnt(arbi[i+5]);
case 5: sum += bitcnt(arbi[i+4]);
case 4: sum += bitcnt(arbi[i+3]);
case 3: sum += bitcnt(arbi[i+2]);
case 2: sum += bitcnt(arbi[i+1]);
case 1: sum += bitcnt(arbi[i]);
case 0:;
}
return sum;
}
答案 3 :(得分:0)
你提到了一系列的int,所以像这样: int array [...];
如果您使用64位os + cpu,您可能希望将其强制转换为long long(或__int64,具体取决于您的平台) - 基本上是8位整数。 所以你这样做:
int array[...];
...
unsigned long long *longArray;
unsigned long long sum;
for (longArray = &array[number of elements in array]; longArray != array;)
{
--longArray;
sum += *longArray;
}
if (the number of elements in original array % 2 == 1)
sum += array[number of elements in original array - 1];
sum = (sum >> 32) + (sum & 0xFFFFFFFF); // basically add up the first 4 bytes and second 4 bytes
return sum;
但试试看,我并不完全确定它会更快。
答案 4 :(得分:0)
如果通过求和每个值的总和,基本上,计算有多少1,那么我认为唯一的方法是从数组/块中添加每个值...用位运算符重新实现add指令,我认为它可能比使用cpu的添加速度慢;但也许它可能取决于cpu。
另外,跳过0并不比添加它们快(跳跃很慢)......
我想到的唯一可能加快速度的事情就是以一种可以利用目标CPU特殊指令的方式打包(从头开始)数据。有些CPU有一些指令可以很容易(而且我认为很快)获得寄存器的总数。 32位寄存器可以容纳32位(数组的32个元素),你可以"求和"它们只有一条指令(特定的CPU ......)。那么你当然必须将结果总结为"全局部分"线程的结果;无论如何这样你减少了添加指令的数量(32加成为1单指令)。这应该与Novelocrat的答案完全一致(例如,向量寄存器的元素是人口计数的结果)。
最近" x86" cpus有人口统计指令,请参阅this link on wikipedia。
答案 5 :(得分:0)
在大多数处理器上,add指令是最快的指令之一。计算元素地址,获取元素,根据需要加宽元素等的逻辑将使实际的加法值淹没4-10倍(如果编译器插入数组边界检查,中断点等,甚至更多)。
当然,首先要做的是将数组索引转换为指针增量。接下来,可以将循环展开20次。一个好的优化编译器可能会做同等的事情,但在这种情况下,你可能(或可能不)能够做得更好。
另一个技巧,特别是如果你有一个字节数组,就是做一些类似于Novelocrat建议的东西(显然是Alex建议的) - 强制指向long的指针的数组指针并获取多个数组元素一次,然后在一次操作的同时添加多个元素(在字节的情况下为4)。当然,对于字节,你必须至少每255次迭代停止并分解,以防止一个字节溢出到下一个字节。
你需要注意在一个线程中“在空中”保留太多的值。只有很多处理器寄存器,并且您希望在寄存器中保留所有值(元素指针,迭代计数器,累加器,抓取元素的临时寄存器等)。
但很快存储访问将成为瓶颈。
答案 6 :(得分:0)
您可能会发现数组索引生成的代码比指针索引更好。看看编译器生成的汇编程序是否确定。使用gcc,这是-S选项。在我的iMac上使用gcc v4.2.1,我看到索引生成更短的代码,虽然我不知道x86汇编程序我不能说它是否实际上更快。
BTW,是由硬件或外部约束强制要求的int
数组吗?