21031


次のような状況を想定します。

[code]

    int a;
    void plus_a_v1(int& elem)
      elem += a;
    int main() {
      a = foo(); // aはruntimeで決まると仮定。constexprにならない。
      int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      for(int* ptr=array; ptr != array+10; ++ptr)
        plus_a_v1(*ptr);
      return 0;
    }
    
[/code]

上記のような場合はaがどうしてもruntimeで決まるしかない状況でかつほぼすべての関数で使われるような変数であればある種の「環境」としてaを使っています。しかし、これではaを間違えて変更してしまったりする場合は問題になるので、次のようにプログラムを組み直しました。

[code]

    void plus_a_v2(int& elem, const int a)
      elem += a;
    int main() {
      const int a = foo();
      int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      for(int* ptr=array; ptr != array+10; ++ptr)
        plus_a_v2(*ptr, a);
      return 0;
    }
    
[/code]

plus_a_v1とplus_a_v2の間の性能差はありますか？

そして、後に環境が増えてきて、次のように定数の環境だけのための構造体Envを作った場合、Envの参照を変数として渡すplus_a_v3の実装はplus_a_v2およびplus_a_v1との理論的性能差はありますか？一応plus_a_v3のほうが構造体を通じて参照しているので、性能上少し劣りそうですが…

[code]

    struct Env {
      int a;
      int b;
    };
    void plus_a_v3(int& elem, const Env& e)
      elem += e.a;
    int main() {
      const Env e = foo_Env(); // 環境定数を設定
      int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      for(int* ptr=array; ptr != array+10; ++ptr)
        plus_a_v3(*ptr, e);
      return 0;
    }
    
[/code]

また、次のような場合も追加で考えられそうです。一応、これはaが一度決まった後は定数だということに基づきます。

[code]

    struct plus_a_functor {
      const int a; 
      plus_a_functor (const int a) : a(a) { }
      void operator() (int& elem) const {
        elem += a;
      }
    };
    int main() {
      const int a = foo();
      plus_a_functor plus_a_v4(a);
      int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      for(int* ptr=array; ptr != array+10; ++ptr)
        plus_a_v4(*ptr);
      return 0;
    }
    
[/code]

plus_a_v1, 2, 3, 4 それぞれの性能は理論的にどのように違いがありますか？


CC BY-SA 3.0
2016-01-18T08:37:50.080
21030
2016-01-25T02:41:40.080
2016-01-18T08:55:23.973
10856
10856
question
c++
グローバル変数は極力使わずにそれを関数のargumentとして与えるべきですか？


> グローバル変数は極力使わずにそれを関数のargumentとして与えるべきですか？

ソフトウェア設計の一般論として、YESです。

> plus_a_v1とplus_a_v2の間の性能差はありますか？

このレベルであれば、限りなく性能差はゼロといえます。（性能＝実行速度と解釈しました）

ちなみに、グローバル変数を利用するplus_a_v1の方が"速い"ことを期待されているのであれば、plus_a_v2の方が速くなるケースも存在することを補足しておきます。一般的なC/C++処理系（よくあるCPUとOS）であればplus_a_v2の方がメモリアクセスの局所性が高くなり、ハードウェアが備えるキャッシュ機構を有効に使える可能性があるためです。実際には、観測可能なレベルの性能差が出ることはほぼ無いでしょう…

> Envの参照を変数として渡すplus_a_v3の実装はplus_a_v2およびplus_a_v1との理論的性能差はありますか？

理論的性能差が何を指すかによりますが、 **現実問題としての性能差はない** というのが私の回答です。

まずは **実際のプログラム**
で性能計測（マイクロベンチマークではダメ）を行い、ソースコードの可読性や保守性を犠牲にしてでも、速度性能が必要であると判断されてから、そこで初めてチューニングを行うべきです。

* * *

> plus_a_v1, 2, 3, 4 それぞれの性能は理論的にどのように違いがありますか？

各バージョンに対する机上の説明としては下記の通りです。厳密にはOSやコンパイラによって挙動は異なりますが、大まかな目安と解釈ください。

  * v1：グローバル変数版。設計論の観点から避けるべき。
  * v2：引数値渡し版。スタック上に配置されるため、メモリアクセス局所性が改善。
  * v3：引数参照渡し版。間接参照を行う分の速度ペナルティが存在するが、コンパイラによる最適化で一部は改善見込みあり。
  * v4：本質的にv3と等価。メンバ変数アクセスには`this`ポインタからの間接参照が生じる。

v3とv4では間接参照が必要となるため、若干の速度低下が生じる可能性はたしかにあります。これを抽象化とのトレードオフと捉えるか、許容できない性能劣化と捉えるかは目的によるかと思います。


CC BY-SA 3.0
2016-01-18T09:07:21.390
21031
2016-01-18T09:24:49.317
2016-01-18T09:24:49.317
49
49
21030
answer


補足的ですが、静的変数はコンパイラによる最適化の対象となりません（コンパイラオプションなどによって最適化できるコンパイラもあるかもしれないけど・・・）。そのためplus_a_v1とplus_a_v2ではplus_a_v2の方が早くなる可能性が高いです。


CC BY-SA 3.0
2016-01-24T21:11:08.880
21272
2016-01-24T21:11:08.880
12774
21030
answer


`plus_a_v3`に関しては`b`を伴う`Env`になってしまうので比較できませんが、`plus_a_v1`、
`plus_a_v2`、`plus_a_v4`について手元のVisual Studio 2015
Update1で生成されるアセンブリコードを比較してみました。

その結果、３者に大差なく、それ以前に **SSEを使用して４並列でplus_aを実行** していました。

コンパイラーがインライン展開可能なように簡潔にすることと、副作用を持たないことや他の要素を変更しないことなど **並列化を妨げないこと**
も重要になってくるようです。


CC BY-SA 3.0
2016-01-25T02:41:40.080
21276
2016-01-25T02:41:40.080
4236
21030
answer