++i is better than i++ in C++?

タイトルの答えは条件付きの Yes。その条件は「インクリメントする式の値を無視する場合」というもの。

たとえば次のように単にiをインクリメントしたい場合++iと書くほうが良い。

int i = 0;
while (true) {
    // use i
    i++;
}

何故「良い」のかはGoogleのC++スタイルガイドに書かれている。 要約するとi++の場合iのコピーをつくる必要があり*1、その必要がない++iと比べ*2メモリがお得だからという理由。

さらにC++の場合オペレータのオーバーロードができる。そのため i が整数でない場合、より両者の性能差が広がる可能性がある。ガイドの次の文はおそらくこのことを述べている。

If i is an iterator or other non-scalar type, copying i could be expensive.

それでは Proof Of Concept として次の実験をしてみよう。

実験

前置++と後置++オーバーロードしたクラスを用意し、両者の性能差を計測する。

クラス

クラス 説明
HugeClass 実験対象のクラス
Timer 実行時間の計測用クラス

なお今回のTimerの実装はC++でフリープラットフォームな時間計測を参考にした。

コード

#include <iostream>
#include <chrono>

using namespace std;

class HugeClass {
public:
    // prefix increment
    HugeClass& operator ++() {
        for (int &num : this->nums) {
            ++num;
        }
        return *this;  
    }

    // postfix increment
    HugeClass operator ++(int) {
        HugeClass ret = *this;
        for (int &num : this->nums) {
            num++;
        }
        return ret;
    }

private:
    int nums[100000];
};

class Timer {
private:
    chrono::system_clock::time_point startTime;
    chrono::system_clock::time_point endTime;
public:
    void start(){ this->startTime = chrono::system_clock::now();};
    void end(){ this->endTime = chrono::system_clock::now();};
    chrono::microseconds diff(){ 
        return chrono::duration_cast<chrono::microseconds>(
            this->endTime - this->startTime
        );
    };
};


int main() {
    int REPEAT_NUMBER = 10000;

    // post increment
    HugeClass hc_post = HugeClass();
    Timer t_post = Timer();
    t_post.start();
    for (int i = 0; i < REPEAT_NUMBER; i++) {
        hc_post++;
    }
    t_post.end();
    
    // pre increment
    HugeClass hc_pre = HugeClass();
    Timer t_pre = Timer();
    t_pre.start();
    for (int i = 0; i < REPEAT_NUMBER; i++) {
        ++hc_pre;
    }
    t_pre.end();


    cout << "post inc: " << t_post.diff().count() << " " << endl;
    cout << "pre  inc: " << t_pre.diff().count() << " ticks" << endl;
}

結果

wandbox上で試した結果、期待通り後置インクリメントが遅かった。

post inc: 3606617 ticks
pre  inc: 3338294 ticks

今回はインクリメントに話題を限定したが、デクリメントも同様の議論がなりたつ。

参考資料

*1:i++ の実装は i のコピーをつくり、コピー元をインクリメントした上ででコピーを返す

*2:++i の実装は i の値をインクリメントにより書き換え、それを返す