我正在尝试实现两个类 A 和 B,它们包含存储在 std::unique_ptr 容器中的数据,并且 A 可以通过一些计算转换为 B。 A类和B类如下所示。 B类构造函数的输入参数被设计为传递一个对象A。为了测量运行时性能,我使用
std::chrono
库,并在B类中添加一个函数“print_construction_time”来显示构造时间。
CPU:英特尔®酷睿™ i7-6700HQ 2.6GHz
内存:16GB
操作系统:Windows 10 1909
IDE:Microsoft Visual Studio Community 2019 版本 16.4.4
c/c++ 优化设置:最大优化(优先速度)(/O2)
class A
{
public:
A(int input_size, int input_value) // constructor
{
this->data = std::make_unique<int[]>(input_size);
this->size = input_size;
for (int loop_number = 0; loop_number < size; loop_number++) {
data[loop_number] = input_value;
}
}
std::unique_ptr<int[]> get_data()
{
// deep copy
auto return_data = std::make_unique<int[]>(size);
for (int loop_number = 0; loop_number < size; loop_number++) {
return_data[loop_number] = data[loop_number];
}
return return_data;
}
int get_size()
{
return this->size;
}
private:
int size;
std::unique_ptr<int[]> data;
};
class B
{
public:
B(A &input_object) // constructor
{
this->size = input_object.get_size();
this->data = std::make_unique<int[]>(this->size);
this->start = std::chrono::high_resolution_clock::now();
// version 1
for (int loop_number = 0; loop_number < input_object.get_size(); loop_number++) {
this->data[loop_number] = transform_from_A(input_object.get_data()[loop_number]);
}
this->stop = std::chrono::high_resolution_clock::now(); // for execution time measurement
}
std::unique_ptr<int[]> get_data()
{
// deep copy
auto return_data = std::make_unique<int[]>(size);
for (int loop_number = 0; loop_number < size; loop_number++) {
return_data[loop_number] = data[loop_number];
}
return return_data;
}
void print_construction_time()
{
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << "Duration: " << duration.count() << "microseconds" << std::endl;
}
private:
int size;
std::unique_ptr<int[]> data;
std::chrono::time_point<std::chrono::steady_clock> start, stop;
int transform_from_A(int input_value)
{
return input_value + 1; // For example
}
};
主要功能就在这里
int main()
{
A a_object(10000, 6);
B b_object(a_object);
b_object.print_construction_time();
return 0;
}
为了测试,我运行了这段代码3次,执行时间分别为123407us、112033us和107586us。 接下来,我将类 B 的构造函数中的 for 循环块修改为使用缓存数据设计的版本 2。
// version 2 <= tremendously faster than version 1
auto data_cached = input_object.get_data();
for (int loop_number = 0; loop_number < input_object.get_size(); loop_number++) {
this->data[loop_number] = transform_from_A(data_cached[loop_number]);
}
版本2的测量结果为27us、32us和43us。我很好奇为什么编译器似乎没有根据相同计算结果的条件自动通过缓存技巧来执行速度最大优化,而需要人工来完成。我认为这种优化也许可以通过编译器自动完成或者通过在编辑器环境中弹出修改建议来完成。
注意:我还修改了测试的优化设置。最大优化(有利速度)(/O2) 和优化(有利速度)(/Ox) 中的优化设置之间的运行时性能相似。
二月27. 2020年更新
我还尝试修改 A 类中 A::get_data() 的返回类型,添加 std::unique_ptr 的
const
关键字。
const std::unique_ptr<int[]> get_data() // <= Add const keyword of std::unique_ptr
{
// deep copy
auto return_data = std::make_unique<int[]>(size);
for (int loop_number = 0; loop_number < size; loop_number++) {
return_data[loop_number] = data[loop_number];
}
return return_data;
}
执行时间的测量结果与上面类似。版本 1 中分别为 101486us、100538us 和 120620us,版本 2 中分别为 55us、30us 和 27us。优化设置为 Optimizations (Favor Speed) (/Ox),IDE 更新为版本 16.4.5 。
此外,“两者都是const”的情况(不仅是std::unique_ptr,还有它的内容),即
const std::unique_ptr<const int[]>
也可以考虑。
const std::unique_ptr<const int[]> get_data()
{
// deep copy
auto return_data = std::make_unique<int[]>(size);
for (int loop_number = 0; loop_number < size; loop_number++) {
return_data[loop_number] = data[loop_number];
}
return return_data;
}
执行时间的测量结果也与上面类似。版本 1 中分别为 114754us、127327us 和 106122us,版本 2 中分别为 32us、34us 和 44us。优化设置也是 Optimizations (Favor Speed) (/Ox)。
“隐藏”问题是您的
version 1
和 version 2
代码并不等效,您不应该对它们不同的运行时间感到惊讶。
// version 1
for (int loop_number = 0; loop_number < input_object.get_size(); loop_number++) {
this->data[loop_number] = transform_from_A(input_object.get_data()[loop_number]);
}
// version 2 <= tremendously faster than version 1
auto data_cached = input_object.get_data();
for (int loop_number = 0; loop_number < input_object.get_size(); loop_number++) {
this->data[loop_number] = transform_from_A(data_cached[loop_number]);
}
您的函数
input_object.get_data()
在 version 1
中被多次调用(在本示例中为 10K),而在 version 2
中仅被调用一次。