C ++:对象,对象的引用,对带有和不带函数的向量元素的引用 - 观察到的性能差异

时间:2013-03-27 08:06:45

标签: c++ performance vector reference double

来自C ++初学者的问题,在凌晨时分头疼。如果你想看一下,请跳到页面底部的代码。我正在对具有不同标识符但具有相同类型(即double)的多个变量应用某些操作。操作可以通过外部函数调用完成,也可以在main中完成。

我考虑 6 情景

(1)没有调用函数的本地对象

(2)没有调用函数的引用对象

(3)引用向量中的元素而不是调用函数

(4)调用函数的本地对象

(5)没有调用函数的引用对象

(6)引用向量调用函数中的元素

我得到了一些有趣的结果(无论如何)。 (1)和(2)的平均时间为574ms,而(3),(4),(5)和(6)的时间均为2.77秒。

我承认(4),(5)和(6)可能是由于传递元素而导致函数调用产生的开销。有些问题出现在我身上,

  • 为什么对向量元素(即(3))的引用计算也需要与调用函数相同的时间?这是否意味着在调用向量元素的引用和向函数提供相似的值之间存在某种开销? (请注意,此案例中的函数不是double&而是double)。

  • 如果我将函数参数全部更改为&double,为什么(1)和(2)需要2.7秒?我的意思是,我甚至没有将功能调用到(1)和(2)! (其他人可以尝试这个 - 因为我发现这很奇怪)

  • 是否有任何特殊方法可以优化其中任何一项?

代码:在Windows MinGW上使用g++ 4.7.2编译g++ -std=c++11 -O3

#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 << "\n\n2) 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 << "\n\n3) 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 << "\n\nreference to obj from vector without function (i.e. local):" << endl;

cout << "\n\n4) normal with function:" << endl;
do_some_calc(a,b,c,d,e);

cout << "\n\n5) reference to double obj with function:" << endl;
do_some_calc(a_ref,b_ref,c_ref,d_ref,e_ref);

cout << "\n\n6) 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

1 个答案:

答案 0 :(得分:0)

虽然这在技术上不是一个答案,但我建议您在进行性能测量时不要使用时钟,因为在您运行测试时,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 \n\t 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运行的速度如何,在测量给定指令序列经过的滴答数时,滴答数应大致相同。请记住,流水线操作和乱序执行将会使它略有不同,但它与使用你正在使用的时钟的东西相比非常接近。