【问题标题】:Performance considerations when accessing member variables访问成员变量时的性能注意事项
【发布时间】:2015-02-10 19:10:04
【问题描述】:

我有一个数字运算程序,我想求解的方程由不同类的成员函数表示。当然,每个类都有几个成员变量,它们是方程的输入。成员 vars 目前是像 double 和 int 这样的原语,但为了更好地与 GUI 集成,我想用托管变量替换原语;即我想使用一个单独的类来保存变量的名称和值,并处理读取和写入它的值。我担心代码的性能和可读性。例如,我宁愿看到像 x = y + 2 这样“自然”的代码,而不是 x.set_value(y.get_value() + 2)

我想出了四种不同的方法,并试验了每种方法需要多少时间(代码如下)。我使用调试版本使用 MSVC 2013 编译了它。我在发布模式下得到了无意义的结果,因为我认为我的循环被优化掉了。结果似乎很重要;使用原语或直接访问成员变量需要使用 getter/setter 函数或强制转换运算符重载的一半时间。

我的问题是:我是否适当地测试了这些不同的方法?有没有更好的方法来做我想做的事情?谢谢。

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

//Class to manage input parameters
struct Parameter {
    double _value = 0.0;
    double Get_value() const {return _value;}
    void Set_value(double value) {_value = value;}
    operator double(){return _value;}
    void operator=(const double& rhs) {_value = rhs;}
};

int main() {
    const size_t NUM_TESTS = 100;       //Number of tests to run
    const size_t MAX_ITER = 1000000;    //Number of iterations to run in each test
    const double x = 2.71828;           //Variable to read from
    double y = 0;                       //Variable to write to
    Parameter test_parameter;       //managed variable to read/write from/to
    double test_primitive = 0.0;    //primitive variable to read/write from/to
    size_t t_primitive = 0;         //Total time spent on primitive variable (microseconds)
    size_t t_managed_cast = 0;      //Time spent on managed variable using cast and assignment operators
    size_t t_managed_getset = 0;    //Time spent on managed variable using getter/setter functions;
    size_t t_managed_direct = 0;    //Time spent on managed variable using direct access of member var.

    for (size_t n = 0; n < NUM_TESTS; ++n) {
        //Test using a primitive variable.
        auto t0 = high_resolution_clock::now();
        for (size_t i = 0; i < MAX_ITER; ++i) {
            test_primitive = x;
            y = test_primitive;
        }
        auto t1 = high_resolution_clock::now();
        t_primitive += duration_cast<microseconds>(t1-t0).count();

        //Test using a managed variable, using cast operator and assignment operator
        t0 = high_resolution_clock::now();
        for (size_t i = 0; i < MAX_ITER; ++i) {
            test_parameter = x;
            y = test_parameter;
        }
        t1 = high_resolution_clock::now();
        t_managed_cast += duration_cast<microseconds>(t1-t0).count();

        //Test using managed variable, using getter/setter member functions
        t0 = high_resolution_clock::now();
        for (size_t i = 0; i < MAX_ITER; ++i) {
            test_parameter.Set_value(x);
            y = test_parameter.Get_value();
        }
        t1 = high_resolution_clock::now();
        t_managed_getset += duration_cast<microseconds>(t1-t0).count();

        //Test using managed variable, using direct public access
        t0 = high_resolution_clock::now();
        for (size_t i = 0; i < MAX_ITER; ++i) {
            test_parameter._value = x;
            y = test_parameter._value;
        }
        t1 = high_resolution_clock::now();
        t_managed_direct += duration_cast<microseconds>(t1-t0).count();
    }

    cout << "Average time for primitive (microseconds): " << t_primitive / NUM_TESTS << endl;
    cout << "Average time for managed with cast (microseconds): " << t_managed_cast / NUM_TESTS << endl;
    cout << "Average time for managed with get/set (microseconds): " << t_managed_getset / NUM_TESTS << endl;
    cout << "Average time for managed with direct access (microseconds): " << t_managed_direct / NUM_TESTS << endl;

    return 0;
}

【问题讨论】:

  • 我认为我的循环得到了优化 ...请参阅:Loop with a zero execution time
  • 你的直觉是正确的,调用函数有开销。如果你真的很关心性能,直接访问和修改成员变量比使用函数更快。但是请注意,几乎所有编译器都对 getter 和 setter 进行了大量优化
  • @Cyber​​:运行时函数调用有开销,但源代码中的函数调用并不意味着运行时调用。毕竟,这就是内联的重点。 处理无意义的基准测试结果的正确方法不是禁用优化,而是修复基准测试。
  • 如果这些调用存在于您实际要运行的已编译代码中的最终确定性中,则 可能 会产生开销 run (我必须假设不是 debug 代码)。非虚拟 const-getter 很容易成为编译器最常进行的内联优化之一。修复您的发布代码,使其有意义并正确运行。
  • @BenVoigt 我同意,并不是要提出其他建议(如果我这样做了!)。如果您担心优化代码的性能,您应该针对优化代码进行分析,这是毫无疑问的。

标签: c++


【解决方案1】:
volatile const double x = 2.71828;
volatile double y = 0;
// ^^ VERY IMPORTANT

在那里,我修正了你的基准。

现在再次启用优化,只需担心启用优化的时间。

【讨论】:

  • 我怀疑我在某个地方需要一个volatile。奇怪的是,编译器不接受我对 volatile 变量的赋值运算符。我不得不像这样重载它:void operator=(volatile const double&amp; rhs) {_value = rhs;} 编辑:启用优化改变了一切。现在这四种方法几乎没有区别。谢谢本。
  • @Carlton:更好的是double operator=(double rhs) { return _value = rhs; } 基本类型应该几乎总是按值传递,而不是常量引用。
【解决方案2】:

调试版本不会内联这些访问器方法,因此需要更长的时间。发布版本不会有这个问题。

尝试使用发布版本,但将变量设为volatile,我相信这会禁用循环优化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多