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

同カテゴリーの次の記事

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

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


1. コンパイラーの仕事をしない

MySQL* プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio Analyzer は、次の診断を出力します。

V525 The code containing the collection of similar blocks. Check items ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘1’, ‘6’ in lines 680, 682, 684, 689, 691, 693, 695. (V525 コードに類似ブロックのコレクションが含まれています。行 680、682、684、689、691、693、695 の項目 ‘0’、’1’、’2’、’3’、’4’、’1’、’6′ を確認してください。)

static int rr_cmp(uchar *a,uchar *b)
{
  if (a[0] != b[0])
    return (int) a[0] - (int) b[0];
  if (a[1] != b[1])
    return (int) a[1] - (int) b[1];
  if (a[2] != b[2])
    return (int) a[2] - (int) b[2];
  if (a[3] != b[3])
    return (int) a[3] - (int) b[3];
  if (a[4] != b[4])
    return (int) a[4] - (int) b[4];
  if (a[5] != b[5])
    return (int) a[1] - (int) b[5];     <<<<====
  if (a[6] != b[6])
    return (int) a[6] - (int) b[6];
  return (int) a[7] - (int) b[7];
}

説明

これは、コードブロックのコピーに起因する古典的なエラーです (コピー & ペースト (英語))。プログラマーは、コードブロック “if (a[1] != b[1]) return (int) a[1] – (int) b[1];” をコピーして、インデックスを変更しましたが、”1″ を “5” に変更するのを忘れてしまったようです。そのため、比較関数が不正な値を返すことがあります。この問題は、気付くのも、検出するのも困難です。実際、PVS-Studio で MySQL* をスキャンするまで、すべてのテストでこの問題は検出されませんでした。

正しいコード

if (a[5] != b[5])
  return (int) a[5] - (int) b[5];

推奨事項

簡潔で読みやすいコードですが、プログラマーがエラーを見落とすのを防ぐことはできませんでした。このように類似ブロックが続く場合、コードの細部に集中することは困難です。

このような類似したブロックは、通常、コードをできるだけ最適化したいというプログラマーの願望によるものです。上記の例では、手動でループをアンロールしていますが、このケースでは良い選択肢であるとは言えません。

1 つ目の理由は、そうすることでプログラマーが何か達成できたとは思えないからです。現代のコンパイラーは非常に洗練されており、プログラムのパフォーマンスを向上できる場合、自動でループをアンロールします。

2 つ目の理由は、コードを最適化しようとしたため、このバグが発生したことです。単純なループを記述していれば、ミスを犯す可能性は低くなります。

この関数を次のように書き直すことを推奨します。

static int rr_cmp(uchar *a,uchar *b)
{
  for (size_t i = 0; i < 7; ++i)
  {
    if (a[i] != b[i])
      return a[i] - b[i]; 
  }
  return a[7] - b[7];
}

利点:

  • 読みやすく、理解しやすい関数
  • 記述時にミスをする可能性が低い

この関数が、オリジナルのコードよりも遅くなることはないでしょう。

私のアドバイスは、単純で分かりやすいコードを記述することであり、原則として単純なコードは概して正しいコードです。コンパイラーの仕事 (例えば、ループアンロール) は横取りしないことです。コンパイラーは、プログラマーの助けがなくてもその役割を果たすことができます。このような細かい手動による最適化は、プロファイラーが問題 (遅いこと) を推定した、特に重要なコードでのみ意味があります。

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

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • インテル® IPP サンプル – エラーの修正インテル® IPP サンプル – エラーの修正 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel IPP Samples for Windows - error correction」の日本語参考訳です。 この記事は、PVS-Studio を使用することでプログラムがどのように安全になるかを説明した記事の 1 […]
  • インテル® Inspector XE 2013 を使用してアプリケーションのメモリー使用量の増加を検出する方法インテル® Inspector XE 2013 を使用してアプリケーションのメモリー使用量の増加を検出する方法 この記事は、インテル® ソフトウェア・サイトに掲載されている「How to detect application memory growth using Intel® Inspector XE 2013」の日本語参考訳です。 背景 インテル® Inspector XE 2013 […]