ループ内で変数を宣言しない方が良いのか
要するに、Javaで下記コードmyMethod1の変数iやtwiceみたいに、ループの中で変数を宣言するとmyMethod2のようにループの外で宣言するよりコストがかかるんじゃなかろうか、という疑問です。
public void myMethod() {
for (int i = 0; i < 10; i++) {
int twice;
twice = i * 2;
System.out.println(twice);
}
}
public void myMethod2() {
int i;
int twice;
for (i = 0; i < 10; i++) {
twice = i * 2;
System.out.println(twice);
}
}
私のイメージは、
- (言語は違いますが)こちらに書かれているように、変数宣言するのに何らかの命令が発生するので、わずかだがコストは増える。
- ただし、そのコストは微小で、それよりも変数のスコープを限定しメンテナンス性/可読性等を保つためにループ内で宣言した方が良い。
というものでした。
で、今回2つのメソッドをjavap -cコマンドでバイトコードに変換して、実際にどういう差異があるのか見てみました。
結果、双方同一のバイトコードが生成されました。
Code:
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 25
8: iload_1
9: iconst_2
10: imul
11: istore_2
12: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_2
16: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
19: iinc 1, 1
22: goto 2
25: return
命令の意味はJava Virtual Machine Specificationにあるそうなのですが…取り敢えず概要はひしだまさんのサイト”ひしだま's 技術メモページ”やWikipediaで理解しました。
変数の宣言に対応する命令は記述されておらず、初めて変数へ値が代入される際にその変数に関連する命令が登場しています。
以上より、(処理コスト低減を目的として)変数宣言を本来のスコープより外で行うことは、全く意味が無い、コストは完全に同一である、ということのようです。
EclipseやNetBeansで、変数宣言の行にブレークポイントが設定できないのはそういう理由なんですね、と言われて、なるほど、と思いました。
[追記 2011/3/11]
最近このページを訪れる方が多くいらっしゃるようなので、前述のサイトのC言語版についてもアセンブリを見てみました。
1: 良い例:
2: void test(){
3: int a, i, j;
4: for(i = 0;i < 100;i++){
5: for(j = 0;j < 100;j++){
6: a = i*j;
7: }
8: }
9: }
10:
11: 悪い例:
12: void test(){
13: for(int i = 0;i < 100;i++){
14: for(int j = 0;j < 100;j++){
15: int a = i*j;
16: }
17: }
18: }
Ubuntu10.04のgcc4.4.3で、-O0オプションをつけてみても同一のコードとなりました。0xffff * 0xffff回のループでも実行速度に差異はありません。また、上記Javaで試してみたのと同様デバッガ(GDB)でステップ実行したところ、変数宣言だけの行ではストップしませんでした。
つまり、Cにおいても変数宣言をループの外で行うことはコスト的に全く意味が無いということになります(リンク先にあるとおり、Microsoft製コンパイラのデバッグオプションを付けた場合、という限定された状況では意味があるのでしょうが。そういえばMicrosoftコンパイラはデバッグ用に変数宣言時特定の初期値を代入していたような…その分のコストかもしれません)。
結論として、現在ではループ外で変数宣言を行えばパフォーマンス改善につながる、というのは都市伝説である、と言い切ってしまってもよいのではないでしょうか。
« ハードディスク運が悪い その3 | トップページ | ScalaスケーラブルプログラミングをScala2.8RC1で読む その1 »
コメント
この記事へのコメントは終了しました。
« ハードディスク運が悪い その3 | トップページ | ScalaスケーラブルプログラミングをScala2.8RC1で読む その1 »
ソース保守の観点から見ても、あまりいい習慣ではないと思いました。例えば、以下のようなコードがあった場合、プログラマは最後の行で20が出力されるコードを書いたつもりでも、ミスタイプで10と出力されてしまいます。
int n = 0;
for (n = 0; n < 10; n++)
{
// do something
}
printf("n = %d\n", n);
for (int n = 11; n < 20; n++) // この変数nはfor文のスコープ内のみ。
{
// do something
}
printf("n = %d\n", n); // 本当は20と出力してほしい。が、10と出力される。
私の職場ではひとつのスコープ内に上記のようなループが何箇所のあり、これが原因でバグが出そうになったことがあります。このブログポストは”コスト的な観点な話”でしたが、保守性に関しても出羽さんのご意見を聞いてみたくて、コメントさせて頂きました。説明がわかりにくかったらすみません・・。
投稿: yuki | 2011/06/07 17:54
yukiさんこんばんは。
保守性の観点からは議論の余地無くスコープは可能な限り小さくとるべきだと私も考えています。
一応この意見は既に大勢を占めていると考えましたので、これを前提として置き、
にも拘らずスコープを小さくすべきでないとして挙げられる理由「パフォーマンスが低下する」が本当に妥当性のある意見なのかどうかを検証する、というのが本エントリの目的でした。
そして、検証の結果、パフォーマンス低下は全く無いことが分かったので大手を振ってスコープを縮められますね、というのが結論になります。
東京証券取引所システムarrowheadに対するエントリ
http://feather.cocolog-nifty.com/weblog/2010/09/arrowhead-7ec0.html
で別の実例も記載しているのですが、根拠のない(そして効果も全くない)パフォーマンス向上似非テクニックには、私もずいぶん手こずらされた経験があります…
これらは『時期尚早な最適化は諸悪の根源だ』
http://ja.wikipedia.org/wiki/%E6%9C%80%E9%81%A9%E5%8C%96_(%E6%83%85%E5%A0%B1%E5%B7%A5%E5%AD%A6)#.E6.9C.80.E9.81.A9.E5.8C.96.E3.81.99.E3.82.8B.E6.99.82.E6.9C.9F
というのが正に当てはまるケースだと思います(そして最適化に何の寄与もしない、つまりこのような提案を行う人は実際に検証した上で主張しているわけではない、のでより質が悪いと考えます)。
投稿: 雪羽 | 2011/06/08 01:48
うう・・・・これやってた。気をつけよう。
投稿: | 2014/05/25 20:29