我为计算量向量的算法编写了优化代码。在尝试将函数中计算的数据从函数中取出之前和之后,我已经对其进行了计时。我认为计算的具体性质或数量向量的性质是不相关的。下面是代码、时间安排和详细信息的概述。
所有代码都使用以下标志编译:
g++ -Wall -Wextra -Werror -std=c++11 -pedantic -O3
我有这样的课:
#ifndef C_H
#define C_H
#include <iostream>
#include <iterator>
#include <vector>
Class c {
public:
void doWork( int param1, int param2 ) const {
std::array<unsigned long,40> counts = {{0}};
// LOTS of branches and inexpensive operations:
// additions, subtractions, incrementations, and dereferences
for( /* loop 1 */ ) {
// LOTS MORE branches and inexpensive operations
counts[ /* data dependent */ ] += /* data dependent */;
for( /* loop 2 */ ) {
// YET MORE branches inexpensive operations
counts[ /* data dependent */ ] += /* data dependent */;
}
}
counts [ /* data dependent */ ] = /* data dependent */;
/* exclude for profiling
std::copy( &counts[0], &counts[40], std::ostream_iterator( std::cout, "," ) );
std::cout << "\n";
*/
}
private:
// there is private data here that is processed above
// the results get added into the array/vector as they are computed
};
#endif
像这样的主要内容:
#include <iostream>
#include "c.h"
int main( int argc, char * argv ) {
Class c( //set the private data of c by passing data in );
int param1;
int param2;
while( std::cin >> param1 >> param2 ) {
c.doWork( int param1, int param2 );
}
}
以下是有关数据的一些相关细节:
- 标准输入读取 2000 万对(从文件重定向)
- 2000 万次调用 c.doWork
- 通过 c.doWork 中的外循环进行了 6000 万次 TOTAL 迭代
- 通过 c.doWork 中的内部循环进行了 1.8 亿次 TOTAL 迭代
所有这些都需要运行 5 分 48 秒。当然,我可以在类函数中打印数组,这就是我一直在做的事情,但是我将公开发布代码,并且一些用例可能包括想要做一些除了打印向量之外的事情。在这种情况下,我需要更改函数签名以实际将数据发送给用户。这就是问题出现的地方。我尝试过的事情:
在 main 中创建一个向量并通过引用传递它:
std::vector<unsigned long> counts( 40 ); while( std::cin >> param1 >> param2 ) { c.doWork( param1, param2, counts ); std::fill( counts.begin(), counts.end(), 0 ); }
这需要 7 分 30 秒。删除对 std::fill 的调用只会减少 15 秒,因此这并不能解释差异。
在 doWork 函数中创建一个向量并返回它,利用移动语义。由于这需要为每个结果动态分配,我没想到这会很快。奇怪的是,它并没有慢很多。7分40秒。
按值返回当前在 doWork 中的 std::array。自然,这必须在返回时复制数据,因为堆栈数组不支持移动语义。7分30秒
通过引用传递一个 std::array 。
while( std::cin >> param1 >> param2 ) { std::array<unsigned long,40> counts = {{0}}; c.doWork( param1, param2, counts ) }
我希望这与原始版本大致相同。数据放在主函数的栈中,并通过引用传递给doWork,doWork填充它。7分20秒。这个真的让我很困扰。
我没有尝试将指针传递给 doWork,因为这应该等同于通过引用传递。
一种解决方案自然是有两个版本的函数:一个在本地打印,一个返回。障碍是我必须复制所有代码,因为这里的整个问题是我无法有效地从函数中获取结果。
所以我很迷惑。我知道这些解决方案中的任何一个都需要对 doWork 中的数组/向量的每次访问都进行额外的取消引用,但是与大量其他快速操作和更麻烦的数据相关分支相比,这些额外的取消引用非常微不足道。
我欢迎任何想法来解释这一点。我唯一的想法是编译器正在优化代码,因此在原始情况下省略了一些其他必要的计算组件,因为编译器意识到这是不必要的。但这似乎在几个方面是禁忌的:
- 对循环内的代码进行更改确实会更改时间。
- 最初的时间是 5 分 50 秒,而仅仅从文件中读取对需要 12 秒,所以我们做了很多工作。
- 也许只有涉及计数的操作被优化掉,但这似乎是一种奇怪的选择性优化,因为如果这些被优化掉,编译器可能会意识到在 doWork 中支持计算也是不必要的。
- 如果涉及计数的操作正在被优化掉,为什么在其他情况下它们没有被优化。我实际上并没有在 main 中使用它们。
doWork 是否独立于 main 进行编译和优化,因此如果该函数有义务以任何形式返回数据,则无法确定是否会使用它?
我为了避免打印成本以强调各种方法的相对差异而没有打印的分析方法有缺陷吗?
我很感激你能散发出的任何光芒。