【发布时间】:2012-11-10 23:30:12
【问题描述】:
有两个非常大的元素系列,第二个比第一个大 100 倍。对于第一个系列的每个元素,第二个系列中有 0 个或多个元素。这可以通过 2 个嵌套循环进行遍历和处理。但是第一个数组的每个成员的匹配元素数量的不可预测性使得事情变得非常非常缓慢。
第二系列元素的实际处理涉及逻辑与 (&) 和人口计数。
我找不到使用 C 的良好优化,但我正在考虑对第一个系列的每个元素进行内联 asm、rep* mov* 或类似操作,然后对第二个系列的匹配字节进行批处理,也许在 1MB 或其他的缓冲区中。但是代码会变得很乱。
有人知道更好的方法吗?首选 C,但 x86 ASM 也可以。非常感谢!
为清楚起见,带有简化问题的示例/演示代码,第一个系列是“人”,第二个系列是“事件”。 (原来的问题其实是100m和10000m条目!)
#include <stdio.h>
#include <stdint.h>
#define PEOPLE 1000000 // 1m
struct Person {
uint8_t age; // Filtering condition
uint8_t cnt; // Number of events for this person in E
} P[PEOPLE]; // Each has 0 or more bytes with bit flags
#define EVENTS 100000000 // 100m
uint8_t P1[EVENTS]; // Property 1 flags
uint8_t P2[EVENTS]; // Property 2 flags
void init_arrays() {
for (int i = 0; i < PEOPLE; i++) { // just some stuff
P[i].age = i & 0x07;
P[i].cnt = i % 220; // assert( sum < EVENTS );
}
for (int i = 0; i < EVENTS; i++) {
P1[i] = i % 7; // just some stuff
P2[i] = i % 9; // just some other stuff
}
}
int main(int argc, char *argv[])
{
uint64_t sum = 0, fcur = 0;
int age_filter = 7; // just some
init_arrays(); // Init P, P1, P2
for (int64_t p = 0; p < PEOPLE ; p++)
if (P[p].age < age_filter)
for (int64_t e = 0; e < P[p].cnt ; e++, fcur++)
sum += __builtin_popcount( P1[fcur] & P2[fcur] );
else
fcur += P[p].cnt; // skip this person's events
printf("(dummy %ld %ld)\n", sum, fcur );
return 0;
}
gcc -O5 -march=native -std=c99 test.c -o test
【问题讨论】:
-
你可能会更受记忆的束缚...
-
是的。有固定尺寸,是的,这就是我的目标。但是对于可变大小的匹配,由于内部循环(根据 valgrind),它会慢得多。条件上也存在分支错误预测,但我认为这更容易摆脱。
-
看看Loop Tiling。这可能会给你带来最大的进步。
-
只是一个猜测,但您可以尝试在循环之前进行 popcount,因为看起来每个元素对 P1[i] 和 P2[i] 都已完成。这将允许一次完成所有弹出计数,这可能会给您一些时间收益
-
@alecco 您确定示例代码不需要
else分支来处理此人未通过年龄过滤器的情况吗?不应该有fcur += P[p].cnt吗?否则,内部循环将消耗其他人的事件...
标签: c performance optimization assembly