プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 29

同カテゴリーの次の記事

プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 30

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


29. イテレーターでは後置インクリメント演算子 (i++) の代わりに前置インクリメント演算子(++i) を使用する

Unreal Engine* 4 プロジェクトから抜粋した以下のコードについて考えてみます。非効率なコードは、次の PVS-Studio 診断によって検出されます。

V803 Decreased performance. In case ‘itr’ is iterator it’s more effective to use prefix form of increment. Replace iterator++ with ++iterator. (V803 パフォーマンス低下。’itr’ がイテレーターの場合、前置インクリメント演算子を使用したほうが効率的です。iterator++ を ++iterator に置き換えてください。)

void FSlateNotificationManager::GetWindows(....) const
{
  for( auto Iter(NotificationLists.CreateConstIterator());
       Iter; Iter++ )
  {
    TSharedPtr<SNotificationList> NotificationList = *Iter;
    ....
  }
}

説明

このセクションのタイトルを見ていなければ、この問題に気付くのは非常に困難でしょう。一見、このコードは完璧ではありませんが、問題なさそうに見えます。しかし、’Iter++’ に問題があります。後置インクリメント演算子 (‘Iter++’) の代わりに、前置インクリメント演算子 (‘++Iter’) を使用すべきです。その理由を以下に示します。

効率良いコード

for( auto Iter(NotificationLists.CreateConstIterator());
     Iter; ++Iter)

推奨事項

前置演算子と後置演算子の違いは、皆さんよくご存じのとおりです。(操作原理を示す) 内部構造の違いについても、演算子の多重定義を行ったことがあれば、ご存知でしょう。ご存じない方のために、簡単に説明します。(すでにご存じの方はこのセクションをスキップして、演算子の多重定義のコード例の後に進んでください。)

前置インクリメント演算子は、オブジェクトの状態を変更し、変更された形式の自身を返します。一時オブジェクトは使用しません。前置インクリメント演算子は、次のように表現できます。

MyOwnClass& operator++()
{
  ++meOwnField;
  return (*this);
}

後置インクリメント演算子もオブジェクトの状態を変更しますが、オブジェクトの以前の状態を返します。一時オブジェクトを作成します。後置インクリメント演算子の多重定義コードは、次のようになります。

MyOwnClass operator++(int)
{
  MyOWnCLass tmp = *this;
  ++(*this);
  return tmp;
}

上記のコード例から、一時オブジェクトの作成という追加処理があることが分かります。実際、これはどれぐらい重要でしょうか?

今日のコンパイラーの最適化は洗練されており、不要な一時オブジェクトは作成しません。そのため、リリースバージョンでは、’it++’ と ‘++it’ の違いに気付くことは困難かもしれません。

しかし、デバッグバージョンでプログラムをデバッグすると、パフォーマンスが大きく異なります。

例えば、こちらの記事 (英語) のように、デバッグバージョンでそれぞれを使用した場合の実行時間を予測するいくつかのコード例があります。後置インクリメント演算子を使用すると、約 4 倍も実行時間が長くなります。

「でもリリースバージョンでは同じになるのだから、問題はないでしょう」という方がいるかもしれません。それは、正しくもあり、間違いでもあります。原則として、ユニットテストやプログラムのデバッグでは、デバッグバージョンで作業することが多いです。つまり、後置インクリメント演算子を使用することは、時間の無駄につながります。

「後置インクリメント演算子 (i++) の代わりに前置インクリメント演算子(++i) を使用すべきか?」という疑問の答えは出たかと思います。そうすべきです。デバッグバージョンをスピードアップすることができます。イテレーターが重い場合は、さらに多くの利点が得られるでしょう。

参考資料:

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • ファクトシート: oneAPIファクトシート: oneAPI この記事は、インテル ニュースルームに公開されている「Fact Sheet: oneAPI」の日本語参考訳です。 この記事の PDF 版はこちらからご利用になれます。 oneAPI とは? oneAPI […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • タスク処理によるゲーム・エンジン・システムのスケーリングタスク処理によるゲーム・エンジン・システムのスケーリング この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Tasking to Scale Game Engine Systems」の日本語参考訳です。 はじめに 6 コア、12 ハードウェア・スレッドのゲーム用デスクトップが市場に登場してからかなり経ち、今ではラップトップでさえも 4 コア CPU […]