簡體   English   中英

Math.Max vs inline if - 有什么區別?

[英]Math.Max vs inline if - what are the differences?

我今天正在研究一個項目,發現自己在幾個地方使用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#,但我想不同語言的答案可能會有所不同,但我不確定哪些有類似的概念。

我立即注意到的一個主要差異是為了可讀性,據我所知,為了實現/性能,它們幾乎是等價的。

無論以前的編碼知識如何, Math.Max(a,b)都很容易理解。

a>b ? a : b a>b ? a : b至少要求用戶對三元運算符有一定的了解。

如有疑問 - 請尋求可讀性

我認為在這個討論中加入一些數字會很有趣,所以我寫了一些代碼來描述它。 正如所料,它們在所有實際用途中幾乎相同。

代碼完成了十億次循環(是10億)。 減去循環的開銷,你會得到:

  • Math.Max()耗時.0044秒,運行10億次
  • 內聯如果花費.0055秒運行10億次

我通過運行10億次空循環減去了我計算的開銷,開銷是1.2秒。

我在筆記本電腦上運行了這個,64位Windows 7,3.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 Ran上作為控制台應用程序在發布模式下沒有附加調試器。

  • Math.Max() - 0.3194749秒
  • 內聯最大值:0.3465041秒

如果聲明被認為有益

摘要

表格的聲明if (a > max) max = a是確定一組數字最大值的最快方法。 然而,循環基礎結構本身占用了大部分CPU時間,因此這種優化最終是值得懷疑的。

細節

luisperezphd的答案很有意思,因為它提供了數字,但我相信這個方法存在缺陷:編譯器很可能將比較移出循環,所以答案並不能衡量它想要衡量的內容。 這解釋了控制回路和測量回路之間可忽略不計的時序差異。

為了避免這種循環優化,我添加了一個依賴於循環變量的操作,空控制循環以及所有測量循環。 我模擬了在數字列表中找到最大值的常見用例,並使用了三個數據集:

  • 最佳情況:第一個數字是最大值,后面的所有數字都是較小的
  • 最壞的情況:每個數字都比前一個更大,所以最大值會改變每次迭代
  • 平均情況:一組隨機數

請參閱下面的代碼。

結果對我來說相當令人驚訝。 在我的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 max = max > a ? max : a :1.2秒最佳情況/ 1.2秒最差情況/ 1.2秒平均情況
  • if (a > max) max = a :0.2秒最佳情況/ 0.9秒最差情況/ 0.3秒平均情況

因此,盡管CPU流水線很長,並且由此產生了對分支的懲罰,但對於所有模擬數據集而言,良好的舊if語句是明顯的贏家; 在最好的情況下,它比Math.Max快10倍,在最壞的情況下仍然快30%以上。

另一個驚喜是Math.Max的參數順序Math.Max重要。 據推測,這是因為CPU分支預測邏輯對兩種情況的工作方式不同,並且根據參數的順序或多或少地錯誤預測分支。

但是,大部分CPU時間都花在了循環基礎結構上,因此最終這種優化充其量是有問題的。 它提供了可測量但略微減少的總體執行時間。

由luisperezphd更新

我不能把它作為一個評論,在這里寫它更有意義,而不是作為我答案的一部分,所以它在上下文中。

你的理論是有道理的,但我無法重現結果。 首先出於某種原因使用你的代碼我的控制循環比包含工作的循環花費更長的時間。

出於這個原因,我將這里的數字相對於最低時間而不是控制循環。 結果中的秒數是最長時間所花費的時間。 例如,在緊接着最快的時間下面的結果是Math.Max(a,max)的最佳情況,所以每個其他結果表示他們花了多長時間。

以下是我得到的結果:

  • max = Math.Max(max, a) :0.012秒最佳情況/ 0.007秒最差情況/ 0.028秒平均情況
  • max = Math.Max(a, max) :0.000最佳情況max = Math.Max(a, max)最壞情況/ 0.019秒平均情況
  • max = max > a ? max : a max = max > a ? max : a :0.022秒最佳情況/ 0.02秒最差情況/ 0.01秒平均情況
  • if (a > max) max = a :0.015秒最佳情況/ 0.024秒最差情況/ 0.019秒平均情況

我第二次跑它時得到了:

  • 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 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%(也就是說太小了)。

電腦

代碼在Windows 8.1,i7 4810MQ 2.8Ghz上運行,並在.NET 4.0中編譯。

代碼修改

我稍微修改了你的代碼,以上面顯示的格式輸出結果。 在開始考慮運行程序集時.NET可能需要的任何額外加載時間后,我還添加了額外的代碼等待1秒。

此外,我運行了兩次所有測試以解決任何CPU優化問題。 最后,我將iint更改為一個unit這樣我可以運行循環40億次而不是10億次以獲得更長的時間跨度。

這可能都是矯枉過正,但要盡可能確保測試不會受到任何這些因素的影響。

您可以在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();
    }
  }
}

如果JITer選擇內聯Math.Max函數,則可執行代碼將與if語句相同。 如果未內聯Math.Max,它將作為函數調用執行,並且if語句中不存在調用和返回開銷。 因此,if語句在內聯情況下會給Math.Max()提供相同的性能,或者if語句在非內聯情況下可能會快幾個時鍾周期,但除非你運行數十,否則差異不會很明顯數以百萬計的比較。

由於兩者之間的性能差異很小,在大多數情況下可以忽略不計,我更喜歡Math.Max(a,b),因為它更容易閱讀。

我會說更快地理解Math.Max正在做什么,而這應該是這里唯一的決定因素。

但作為一種放縱,有趣的是, Math.Max(a,b)一次評估參數,而a > b ? a : b a > b ? a : b兩次評估其中一個。 局部變量不是問題,但對於有副作用的屬性,副作用可能會發生兩次。

Math.Max(A,B)

不是等於 a > b ? a : b a > b ? a : b在所有情況下。

Math.Max返回兩個參數的更大值,即:

if (a == b) return a; // or b, doesn't matter since they're identical
else if (a > b && b < a) return a;
else if (b > a && a < b) return b;
else return undefined;

例如,在Math.Max的雙重過載的情況下,undefined被映射到double.NaN

a> b? a:b

評估a a是否大於b,這並不一定意味着b小於a。

一個簡單的例子,證明它們不等價:

var a = 0.0/0.0; // or double.NaN
var b = 1.0;
a > b ? a : b // evaluates to 1.0
Math.Max(a, b) // returns double.NaN

關於性能,現代CPU具有內部命令管道,使得每個匯編命令在若干內部步驟中執行。 (例如取,解釋,計算,存儲)

在大多數情況下,CPU足夠智能,可以並行執行這些步驟以實現順序命令,因此整體吞吐量非常高。

這是好的,直到有一個分支( if?:等)。 分支可能會破壞序列並強制CPU廢棄管道。 這會花費很多時鍾周期。

理論上,如果編譯器足夠智能,可以使用內置的CPU命令實現Math.Max ,並且可以避免分支。

在這種情況下, Math.Max實際上比if更快 - 但它取決於編譯器..

如果更復雜的Max - 就像在矢量上工作一樣, double []v; v.Max() double []v; v.Max()編譯器可以使用高度優化的庫代碼,這比常規編譯代碼快得多。

因此最好使用Math.Max,但是如果它足夠重要,還建議檢查您的特定目標系統和編譯器。

做手術; N必須> = 0

一般解決方案

A) N = Math.Max(0, N)
B) if(N < 0){N = 0}

按速度排序:

慢:Math.Max(A)<(B)if-then語句:快速(比解決方案'A'快3%)

但我的解決方案比解決方案'B'快4%:

N *= Math.Sign(1 + Math.Sign(N));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM