对象,引用对象,引用带函数和不带函数的向量元素-观察到的性能差异
C++: objects, reference to objects, reference to vector elements with and without functions - observed performance difference
一个c++初学者的问题,在清晨感到头痛。如果您想看一下,请跳到页面底部的代码。我正在对具有不同标识符但相同类型(即double
)的几个变量应用一些操作。这些操作可以通过外部函数调用完成,也可以在main中完成。
我考虑6场景
(1)本地对象不调用函数
(2)不调用函数的引用对象
(3)引用vector中不调用函数的元素
(4)本地对象调用函数
(5)引用对象不调用函数
(6)对vector调用函数
元素的引用我得到了一些有趣的结果(至少对我来说)。(1)和(2)平均耗时574ms,而(3)、(4)、(5)和(6)均耗时约2.77秒。
我承认(4),(5)和(6)可能是由于传入元素的函数调用引起的开销。我想到了一些问题,
为什么对vector元素(即(3))的引用的计算也需要与调用函数相同的时间?这是否意味着在调用向量元素的引用和向函数提供类似的值之间存在某种开销?(注意,在这种情况下,函数不取
double&
,而是取double
)。如果我将功能参数全部更改为
&double
,为什么(1)和(2)需要2.7秒??我的意思是,我甚至没有调用函数到(1)和(2)!是否有任何特殊的方法来优化这些,如果有的话?
CODE: compile with g++ 4.7.2
with g++ -std=c++11 -O3
on Windows MinGW.
#include <iostream> // c++ input/output libraries
#include <stdio.h>
#include <vector>
#include "timer.h"
void do_some_calc(double aa, double bb, double cc, double dd, double ee)
{
double total{0}, add{0};
for(int tests=0; tests<5; ++tests) {
Timer Time;
Time.start();
for(int i=0; i<100000; ++i)
{
for(int j=0; j<2000; ++j)
{
add = aa*bb/cc*dd/ee;
total += add;
aa=aa/2;
bb=bb/2;
cc=cc/2;
dd=dd/2;
ee=ee/2;
aa=aa*2;
bb=bb*2;
cc=cc*2;
dd=dd*2;
ee=ee*2;
}
}
cout << total << " with " << add << endl;
Time.finish("func call");
}
}
int main()
{
// the numbers 12, 13,14,13 and 12 tied to a vector
std::vector<double> ch{12,13,14,13,12};
// the numbers 12, 13,14,13 and 12 tied to independent objects
double a = 12;
double b = 13;
double c = 14;
double d = 13;
double e = 12;
// reference to objects
double& a_ref = a;
double& b_ref = b;
double& c_ref = c;
double& d_ref = d;
double& e_ref = e;
// reference to vector elements
double& a_vref = ch[0];
double& b_vref = ch[1];
double& c_vref = ch[2];
double& d_vref = ch[3];
double& e_vref = ch[4];
cout << "1) normal without function (i.e. local):" << endl;
double total{0}, add{0};
for(int tests=0; tests<5; ++tests) {
Timer Time;
Time.start();
for(int i=0; i<100000; ++i)
{
for(int j=0; j<2000; ++j)
{
add = a*b/c*d/e;
total += add;
a=a/2;
b=b/2;
c=c/2;
d=d/2;
e=e/2;
a=a*2;
b=b*2;
c=c*2;
d=d*2;
e=e*2;
}
}
cout << total << " with " << add << endl;
Time.finish("obj");
}
cout << "nn2) reference to double obj without function (i.e. local):" << endl;
total=0, add=0;
for(int tests=0; tests<5; ++tests) {
Timer Time;
Time.start();
for(int i=0; i<100000; ++i)
{
for(int j=0; j<2000; ++j)
{
add = a_ref*b_ref/c_ref*d_ref/e_ref;
total += add;
a_ref=a_ref/2;
b_ref=b_ref/2;
c_ref=c_ref/2;
d_ref=d_ref/2;
e_ref=e_ref/2;
a_ref=a_ref*2;
b_ref=b_ref*2;
c_ref=c_ref*2;
d_ref=d_ref*2;
e_ref=e_ref*2;
}
}
cout << total << " with " << add << endl;
Time.finish("ref obj");
}
cout << "nn3) reference to double obj from vector without function (i.e. local):" << endl;
total=0, add=0;
for(int tests=0; tests<5; ++tests) {
Timer Time;
Time.start();
for(int i=0; i<100000; ++i)
{
for(int j=0; j<2000; ++j)
{
add = a_vref*b_vref/c_vref*d_vref/e_vref;
total += add;
a_vref=a_vref/2;
b_vref=b_vref/2;
c_vref=c_vref/2;
d_vref=d_vref/2;
e_vref=e_vref/2;
a_vref=a_vref*2;
b_vref=b_vref*2;
c_vref=c_vref*2;
d_vref=d_vref*2;
e_vref=e_vref*2;
}
}
cout << total << " with " << add << endl;
Time.finish("ref vec");
}
//cout << "nnreference to obj from vector without function (i.e. local):" << endl;
cout << "nn4) normal with function:" << endl;
do_some_calc(a,b,c,d,e);
cout << "nn5) reference to double obj with function:" << endl;
do_some_calc(a_ref,b_ref,c_ref,d_ref,e_ref);
cout << "nn6) reference to double obj from vector with function:" << endl;
do_some_calc(a_vref,b_vref,c_vref,d_vref,e_vref);
return 0;
}
这是我创建的自定义#include "Timer.h"
,我在这里使用它来计算时间
/*
Timer class for c++11 and pre c++11 (i.e. c++03 and c++99 etc) [version 0.1]
This is currently static and does not include multiple starts
Author:
currently tested on GCC only
*/
#ifndef TIMER_H
#define TIMER_H
#include <string>
#include <iostream>
#if (__cplusplus >= 201103L)
#include <chrono> // include new c++11 object for timer
#include <ratio>
#else
#include <ctime> // include pre c++11 object for timer
#endif
class Timer {
private:
#if __cplusplus >= 201103L
typedef std::chrono::high_resolution_clock::time_point hiResClock;
typedef std::chrono::duration<long double,std::micro> micro_t;
hiResClock store;
#else
long double store;
#endif
public:
void start(void); // [c++11] method: start timer
void finish(const std::string& disp); // [both] method: finish timer
}; // end of class Timer
inline void Timer::start(void)
{
#if __cplusplus >= 201103L
store = std::chrono::high_resolution_clock::now();
#else
store = (long double)std::clock()/CLOCKS_PER_SEC;
#endif
}
void Timer::finish(const std::string& disp)
{
std::cout << "Time taken: ";
#if __cplusplus >= 201103L
Timer::micro_t out = std::chrono::duration_cast<Timer::micro_t> (std::chrono::high_resolution_clock::now()-store);
long double temp = out.count();
if(temp<1000)
std::cout << out.count() << " micro-seconds" << std::endl;
else if(temp<1000000)
std::cout << out.count()/1000 << " milli-seconds" << std::endl;
else if(temp<1000000000)
std::cout << out.count()/1000000 << " seconds" << std::endl;
else if(temp<60000000000)
std::cout << out.count()/60000000L << " minutes" << std::endl;
else
std::cout << out.count()/3600000000ULL << " hours" << std::endl;
#else
std::cout << ((long double)std::clock()/CLOCKS_PER_SEC-store) << " seconds" << std::endl;
#endif
std::cout << " For: " << disp << std::endl;
}
#endif // instantiate Timer.h once
虽然这不是技术上的答案,但我建议在进行性能测量时不要使用时钟,因为在运行测试的那一刻,CPU可能处于SpeedStep模式(即以较低的频率运行以节省电源)。
不如试试这个x86特有的东西:
http://en.wikipedia.org/wiki/Time_Stamp_Counter你可以这样使用:
#include <cstdint>
// Read the CPU Time Stamp Counter
::uint64_t getTicks() noexcept
{
register ::uint32_t lo, hi;
#ifdef SUPPORTS_RDTSCP
__asm__ __volatile__ ("rdtscp" // On i7 we can remove cpuid and use rdtscp
: "=a"(lo), "=d"(hi)
:
: );
#else
__asm__ __volatile__ ("cpuid nt rdtsc" // On lesser chips there is no RDTSCP instruction
: "=a"(lo), "=d"(hi) // Works in 32- or 64-bit modes (don't use "=A"!!!)
:
: "ebx", "ecx"); // Because of cpuid
#endif
return (::uint64_t)hi<<32 | lo;
}
可以看到,您需要根据您拥有的芯片类型来定义SUPPORTS_RDTSCP。
无论CPU的运行速度是多少,当测量给定指令序列的节拍数时,节拍数应该是相同的。请记住,流水线和乱序执行,所有这些东西会使它略有不同,但它比使用时钟的东西要接近得多。
- 如何使用单独文件中的派生类访问友元函数对象
- 当使用透明的std函数对象时,我们还需要写空的尖括号吗
- 有没有办法将重载的类函数绑定到函数对象?
- 将指针传递到成员的指针,从模板参数包到函数对象
- 如何在类模板的成员函数中正确调用函数对象?正在生成 Visual Studio 编译器错误 C2440
- 隐式转换为比较函数对象(函子)用于 std::sort 而不是 std::map?
- C++使用函数对象的线程,如何调用多个析构函数而不是构造函数?
- 如何通过接口将函子分配给函数对象
- 对std::函数对象的调用不匹配,该对象是指向成员函数的指针
- 指向std::invoke中成员函数对象的指针
- 如何成功地将函数对象(或lambda)传递给trackbar回调的第二个参数(void*)
- "std::function"的简单版本:函数对象的生存期?
- C++ 将函数对象作为左值和/或右值传递
- SFINAE 用于具有默认参数的函数对象
- 构造函数对象赋值是否泄漏内存
- 如何发送通过绑定到函数/方法创建的函数对象?
- std::for_each 与函数对象
- 将函数对象传递给 std::function
- 访问执行策略for_each函数对象中的迭代器
- 通过C++函数对象类访问参数