35

私は今日プロジェクトに取り組んでいて、いくつかの場所でMath.Maxを使用し、他の場所でifステートメントをインラインで使用していることに気付きました。それで、私は誰かがどちらが「より良い」かを知っているかどうか疑問に思いました...というより、本当の違いは何ですか。

たとえば、次のように、c1 = c2

Random rand = new Random();
int a = rand.next(0,10000);
int b = rand.next(0,10000);

int c1 = Math.Max(a, b);
int c2 = a>b ? a : b;

具体的にはC#について質問していますが、どの言語が同じような概念を持っているかはわかりませんが、言語によって答えが異なる可能性があると思います。

4

8 に答える 8

33

私がすぐに気付く主な違いの 1 つは、読みやすさのためです。実装/パフォーマンスのために知っている限り、それらはほぼ同等です。

Math.Max(a,b)以前のコーディング知識に関係なく、理解するのは非常に簡単です。

a>b ? a : b少なくとも、ユーザーは三項演算子についてある程度の知識を持っている必要があります。

"疑わしい場合 - 読みやすさを求めてください"

于 2011-03-29T21:07:38.793 に答える
27

この議論にいくつかの数値を投入するのは楽しいだろうと思ったので、それをプロファイリングするためのコードを書きました。予想どおり、それらはすべての実用的な目的でほぼ同じです。

このコードは 10 億回 (そう、10 億回) のループを実行します。得られるループのオーバーヘッドを差し引くと、次のようになります。

  • Math.Max() は、10 億回実行するのに .0044 秒かかりました
  • 10 億回の実行に 0.0055 秒かかったインライン if

空のループを 10 億回実行して計算したオーバーヘッドを差し引くと、オーバーヘッドは 1.2 秒でした。

これをラップトップ、64 ビット Windows 7、1.3 Ghz Intel Core i5 (U470) で実行しました。コードはリリース モードでコンパイルされ、デバッガーを接続せずに実行されました。

コードは次のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestMathMax {
    class Program {
        static int Main(string[] args) {
            var num1 = 10;
            var num2 = 100;
            var maxValue = 0;
            var LoopCount = 1000000000;
            double controlTotalSeconds;
            { 
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < LoopCount; i++) {
                    // do nothing
                }
                stopwatch.Stop();
                controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
                Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = Math.Max(num1, num2);
                }
                stopwatch.Stop();
                Console.WriteLine("Math.Max() - " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = num1 > num2 ? num1 : num2;
                }
                stopwatch.Stop();
                Console.WriteLine("Inline Max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }

            Console.ReadLine();

            return maxValue;
        }
    }
}

更新された結果 2015 年 2 月 7 日

Windows 8.1、Surface 3 Pro、i7 4650U 2.3Ghz では、デバッガーが接続されていないリリース モードでコンソール アプリケーションとして実行されました。

  • Math.Max() - 0.3194749 秒
  • インライン最大: 0.3465041 秒
于 2012-06-02T17:04:47.660 に答える
16

if ステートメントが有益と見なされる

概要

形式のステートメントはif (a > max) max = a、一連の数値の最大値を決定する最速の方法です。ただし、ループ インフラストラクチャ自体が CPU 時間の大部分を占めるため、この最適化は最終的には疑わしいものになります。

詳細

luisperezphd による回答は、数値を提供するので興味深いものですが、方法に欠陥があると思います。コンパイラは比較をループの外に移動する可能性が高いため、回答は測定したいものを測定しません。これは、制御ループと測定ループの間の無視できるタイミングの違いを説明しています。

このループの最適化を回避するために、ループ変数に依存する操作を空の制御ループとすべての測定ループに追加しました。数値のリストで最大値を見つける一般的なユース ケースをシミュレートし、3 つのデータ セットを使用しました。

  • 最良のケース: 最初の数値が最大で、それ以降の数値はすべて小さい
  • 最悪の場合: すべての数値が前の数値よりも大きいため、反復ごとに最大値が変化します
  • 平均的なケース: 一連の乱数

コードについては、以下を参照してください。

その結果は、私にとってかなり驚くべきものでした。私の Core i5 2520M ラップトップでは、10 億回の反復で次の結果が得られました (すべてのケースで空のコントロールに約 2.6 秒かかりました)。

  • max = Math.Max(max, a): ベストケース2.0秒 / ワーストケース1.3秒 / 平均ケース2.0秒
  • max = Math.Max(a, max): ベストケース 1.6 秒 / ワーストケース 2.0 秒 / 平均ケース 1.5 秒
  • max = max > a ? max : a: ベストケース1.2秒 / ワーストケース1.2秒 / 平均ケース1.2秒
  • if (a > max) max = a: ベスト ケース 0.2 秒 / ワースト ケース 0.9 秒 / 平均ケース 0.3 秒

したがって、CPU パイプラインが長くなり、結果として分岐にペナルティが発生するにもかかわらず、ifシミュレートされたすべてのデータ セットでは、古き良きステートメントが明らかに勝者となります。最良の場合は よりも 10 倍速くMath.Max、最悪の場合でも 30% 以上高速です。

もう 1 つの驚きは、引数の順序が重要であることMath.Maxです。おそらくこれは、CPU 分岐予測ロジックが 2 つのケースで異なる動作をしており、引数の順序に応じて多かれ少なかれ分岐の予測を誤っているためです。

ただし、CPU 時間の大部分はループ インフラストラクチャで費やされるため、最終的にはこの最適化には問題があります。これにより、全体的な実行時間が測定可能ですがわずかに短縮されます。

luisperezphd によって更新されました

これをコメントとして収めることができなかったので、回答の一部としてではなく、文脈に沿ってここに書く方が理にかなっています。

あなたの理論は理にかなっていますが、結果を再現できませんでした。まず、何らかの理由でコードを使用すると、制御ループが作業を含むループよりも長くかかっていました。

そのため、ここでは、制御ループではなく、最低時間に対する相対的な数値を作成しました。結果の秒数は、最速の時間よりもどれだけ長くかかったかです。たとえば、最速時間のすぐ下の結果では、Math.Max(a, max) が最良のケースであったため、他のすべての結果は、それよりどれだけ時間がかかったかを表しています。

以下は私が得た結果です:

  • max = Math.Max(max, a): ベスト ケース 0.012 秒 / ワースト ケース 0.007 秒 / 平均ケース 0.028 秒
  • max = Math.Max(a, max): 0.000 ベスト ケース / 0.021 ワースト ケース / 0.019 秒 平均ケース
  • max = max > a ? max : a: ベスト ケース 0.022 秒 / ワースト ケース 0.02 秒 / 平均ケース 0.01 秒
  • if (a > max) max = a: ベスト ケース 0.015 秒 / ワースト ケース 0.024 秒 / 平均ケース 0.019 秒

2回目に実行したときは、次のようになりました。

  • max = Math.Max(max, a): 0.024 秒のベスト ケース / 0.010 秒のワースト ケース / 0.009 秒の平均ケース
  • max = Math.Max(a, max): ベスト ケース 0.001 秒 / ワースト ケース 0.000 秒 / 平均ケース 0.018 秒
  • max = max > a ? max : a: ベスト ケース 0.011 秒 / ワースト ケース 0.005 秒 / 平均ケース 0.018 秒
  • if (a > max) max = a: ベスト ケース 0.000 秒 / ワースト ケース 0.005 秒 / 平均ケース 0.039 秒

これらのテストには十分なボリュームがあり、異常は一掃されているはずです。それにもかかわらず、結果はかなり異なります。おそらく、配列の大量のメモリ割り当てが関係しているのでしょう。または、違いが非常に小さいため、その時点でコンピューターで発生しているその他のことが原因である可能性があります。

上記の結果で 0.000 で表される最速の時間は約 8 秒であることに注意してください。したがって、当時の最長ランが 8.039 であったと考えると、時間の変動は約 0.5% (0.5%) であり、小さすぎて問題になりません。

コンピュータ

コードは Windows 8.1、i7 4810MQ 2.8Ghz で実行され、.NET 4.0 でコンパイルされました。

コードの変更

上記の形式で結果を出力するように、コードを少し変更しました。また、アセンブリの実行時に .NET で追加の読み込み時間が必要になる可能性があるため、開始後 1 秒待機するコードを追加しました。

また、CPU の最適化を考慮して、すべてのテストを 2 回実行しました。最後に、intforiを aに変更してunit、ループを 10 億回ではなく 40 億回実行して、より長いタイムスパンを取得できるようにしました。

これはおそらくやり過ぎですが、テストがこれらの要因のいずれにも影響されないようにするためです。

コードはhttp://pastebin.com/84qi2cbDにあります。

コード

using System;
using System.Diagnostics;

namespace ProfileMathMax
{
  class Program
  {
    static double controlTotalSeconds;
    const int InnerLoopCount = 100000;
    const int OuterLoopCount = 1000000000 / InnerLoopCount;
    static int[] values = new int[InnerLoopCount];
    static int total = 0;

    static void ProfileBase()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int maxValue;
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                // baseline
                total += values[i];
            }
        }
        stopwatch.Stop();
        controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
        Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
    }

    static void ProfileMathMax()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(values[i], maxValue);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(a, max) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileMathMaxReverse()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(maxValue, values[i]);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(max, a) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileInline()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = maxValue > values[i] ? values[i] : maxValue;
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("max = max > a ? a : max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileIf()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                if (values[i] > maxValue)
                    maxValue = values[i];
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("if (a > max) max = a: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void Main(string[] args)
    {
        Random rnd = new Random();
        for (int i = 0; i < InnerLoopCount; i++)
        {
            //values[i] = i;  // worst case: every new number biggest than the previous
            //values[i] = i == 0 ? 1 : 0;  // best case: first number is the maximum
            values[i] = rnd.Next(int.MaxValue);  // average case: random numbers
        }

        ProfileBase();
        Console.WriteLine();
        ProfileMathMax();
        Console.WriteLine();
        ProfileMathMaxReverse();
        Console.WriteLine();
        ProfileInline();
        Console.WriteLine();
        ProfileIf();
        Console.ReadLine();
    }
  }
}
于 2014-05-06T10:12:59.360 に答える
6

JITer が Math.Max 関数をインライン化することを選択した場合、実行可能コードは if ステートメントと同じになります。Math.Max がインライン化されていない場合、if ステートメントにない call と return のオーバーヘッドを伴う関数呼び出しとして実行されます。そのため、if ステートメントは、インライン化の場合は Math.Max() と同じパフォーマンスを提供します。または、非インライン化の場合、if ステートメントは数クロック サイクル速くなる可能性がありますが、10 を実行していない限り、違いは目立ちません。何百万もの比較。

2 つのパフォーマンスの違いは、ほとんどの状況で無視できるほど小さいため、Math.Max(a,b) の方が読みやすいのでお勧めします。

于 2011-03-29T21:09:12.520 に答える
6

Math.Max が何をしているかを理解する方が早いと思います。それが、ここでの唯一の決定要因になるはずです。

Math.Max(a,b)しかし、おまけとして、が引数を 1 回評価し、そのうちの 1 つを 2 回評価すると考えるのは興味深いことa > b ? a : bです。ローカル変数では問題ありませんが、副作用のあるプロパティの場合、副作用が 2 回発生することがあります。

于 2011-03-29T23:09:54.707 に答える