我有一个 C++11 应用程序,我通常会针对各种算法迭代几种不同的数组结构。原始 CPU 性能对于这个应用程序很重要。
数组元素是基本类型(int、double、..)或简单结构。该数组通常有数万个元素长。我经常需要在给定的循环中一次迭代多个数组。所以通常我需要一个指针来处理任何类型的每个数组。所以有时我需要增加五个冗长的单独指针。
基于这些关于元组的答案, 为什么 std::pair 比 std::tuple C++11 元组性能更快, 我希望使用元组将指针打包到一个对象中没有开销。
我认为实现一个类似对象的游标来帮助迭代可能会很好,因为缺少特定指针上的增量将是一个烦人的错误。
auto pts = std::make_tuple(p1, p2, p3...);
允许您以类型安全的方式将一堆变量捆绑在一起。然后你可以实现一个可变参数模板函数,以一种类型安全的方式递增元组中的每个指针。
然而...
当我测量性能时,元组版本比使用原始指针要慢。但是当我查看生成的程序集时,我在元组循环增量中看到了额外的 mov 指令。也许是由于std::get<>
返回参考的事实?我曾希望它会被编译掉......
我是否遗漏了某些东西,或者像这样使用原始指针是否会击败元组?这是一个简单的测试工具。我扔掉了花哨的光标代码,只使用 astd::tuple<>
进行这个测试
在我的机器上,对于各种数据大小,元组循环的速度始终是原始指针版本的两倍。
我的系统配置是 Windows 8 上的 Visual C++ 2013 x64,带有发布版本。我确实尝试在 Visual Studio 中打开各种优化,例如内联函数扩展:任何合适的 (/Ob2),但它似乎并没有改变我案例的时间结果。
我确实需要做两件额外的事情来避免 VS 的激进优化
1)我强制测试数据数组分配在堆上,而不是堆栈上。这在我计时时产生了很大的不同,可能是由于内存缓存效应。
2)我通过在最后写入静态变量来强制产生副作用,这样编译器就不会跳过我的循环。
struct forceHeap
{
__declspec(noinline) int* newData(int M)
{
int* data = new int[M];
return data;
}
};
void timeSumCursor()
{
static int gIntStore;
int maxCount = 20;
int M = 10000000;
// compiler might place array on stack which changes the timing
// int* data = new int[N];
forceHeap fh;
int* data = fh.newData(M);
int *front = data;
int *end = data + M;
int j = 0;
for (int* p = front; p < end; ++p)
{
*p = (++j) % 1000;
}
{
BEGIN_TIMING_BLOCK("raw pointer loop", maxCount);
int* p = front;
int sum = 0;
int* cursor = front;
while (++cursor != end)
{
sum += *cursor;
}
gIntStore = sum;// force a side effect
END_TIMING_BLOCK();
}
printf("%d\n", gIntStore);
{
// just use a simple tuple to show the issue
// rather full blown cursor object
BEGIN_TIMING_BLOCK("tuple loop", maxCount);
int sum = 0;
auto cursor = std::make_tuple(front);
while (++std::get<0>(cursor) != end)
{
sum += *std::get<0>(cursor);
}
gIntStore = sum; // force a side effect
END_TIMING_BLOCK();
}
printf("%d\n", gIntStore);
delete[] data;
}