Carlton
Carlton

Reputation: 4297

Performance considerations when accessing member variables

I have a number-crunching program where the equations I want to solve are represented by the member functions of different classes. Each class of course has several member variables which are inputs to the equations. The member vars are currently primitives like double and int, but for better integration with a GUI, I want to replace the primitives with managed variables; i.e. I want to use a separate class to hold the variable's name and value, and to handle reading and writing its value. I am concerned about both performance and readability of the code. E.g., I would rather see "natural" looking code like x = y + 2 rather than x.set_value(y.get_value() + 2).

I came up with four different ways of doing this and experimented with how much time each method takes (code below). I compiled this with MSVC 2013 using a debug build. I get nonsense results in release mode because I think my loops get optimized away. The results seem significant; using primitives or accessing member variables directly takes half the time of using getter/setter functions or cast operator overloads.

My questions are: Am I testing these different methods appropriately? And is there a better way to do what I'm trying to do? Thanks.

#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;
}

Upvotes: 1

Views: 101

Answers (2)

rlbond
rlbond

Reputation: 67749

A debug build will not inline those accessor methods, so it will take longer. A release build will not have this problem.

Try using a release build but make your variables volatile, I believe that will disable the loop optimization.

Upvotes: 1

Ben Voigt
Ben Voigt

Reputation: 283614

volatile const double x = 2.71828;
volatile double y = 0;
// ^^ VERY IMPORTANT

There, I fixed your benchmark.

Now enable optimization again, and worry only about timing with optimization enabled.

Upvotes: 3

Related Questions