2015-04-18 50 views
1

我已經搜索了* operatorMath.BigMul method之間的差異,但什麼都沒發現。所以我決定我會試着互相測試他們的效率。請看下面的代碼:爲什麼常規乘法運算符比BigMul方法效率更高?

public class Program 
{ 
    static void Main() 
    { 

     Stopwatch MulOperatorWatch = new Stopwatch(); 
     Stopwatch MulMethodWatch = new Stopwatch(); 

     MulOperatorWatch.Start(); 
     // Creates a new MulOperatorClass to perform the start method 100 times. 
     for (int i = 0; i < 100; i++) 
     { 
      MulOperatorClass mOperator = new MulOperatorClass(); 
      mOperator.start(); 

     } 
     MulOperatorWatch.Stop(); 

     MulMethodWatch.Start(); 
     for (int i = 0; i < 100; i++) 
     { 
      MulMethodClass mMethod = new MulMethodClass(); 
      mMethod.start(); 
     } 
     MulMethodWatch.Stop(); 

     Console.WriteLine("Operator = " + MulOperatorWatch.ElapsedMilliseconds.ToString()); 
     Console.WriteLine("Method = " + MulMethodWatch.ElapsedMilliseconds.ToString()); 
     Console.ReadLine(); 
    } 

    public class MulOperatorClass 
    { 
     public void start() 
     { 
      List<long> MulOperatorList = new List<long>(); 
      for (int i = 0; i < 15000000; i++) 
      { 
       MulOperatorList.Add(i * i); 
      } 
     } 
    } 

    public class MulMethodClass 
    { 
     public void start() 
     { 
      List<long> MulMethodList = new List<long>(); 
      for (int i = 0; i < 15000000; i++) 
      { 
       MulMethodList.Add(Math.BigMul(i,i)); 
      } 
     } 
    } 
} 

概括起來:我創建了兩個類 - MulMethodClassMulOperatorClass執行兩個start方法,填補List<long型與i multiply by i多次值的varible。這些方法之間的唯一區別是在操作符類中使用* operator,並且在方法類中使用Math.BigMul

我創建了這些類中的每一個的100個實例,只是爲了防止和溢出列表(我無法創建1000000000個項目列表)。

然後我測量100個類中每個類執行所花費的時間。結果是相當奇特:我已經做了這個過程大約15倍,平均結果(以毫秒爲單位):

操作= 20357

方法= 24579

即約4.5秒差,我認爲是很多。我查看了BigMul method的源代碼 - 它使用了* operator,並且幾乎做了同樣的事情。

所以,我quesitons:

  • 爲什麼這樣的方法甚至存在嗎?它完全一樣。
  • 如果它完全一樣,爲什麼這兩者之間存在巨大的效率差異?

我只是好奇:)

+0

嗨!它可以通過'int'連接到''long' BigMul –

+0

@ agent5566我嘗試將'start'方法中的'i'轉換爲'long',並得到了相同的結果。 – Eminem

+0

另外,您在調用'BigMul'方法時有一些開銷,請嘗試將運算符封裝到方法 –

回答

2

Microbenchmarking是藝術。你說得對,這個方法在x86上慢10%左右。在x64上的速度相同。請注意,您必須乘以兩個長整數,因此((long)i) * ((long)i),因爲它是BigMul

現在,一些簡單的規則,如果你想微基準:

A)不要在基準比較的代碼分配內存......你不想在GC運行(您正在擴大List<>

B)預分配定時區之外的存儲器(運行代碼之前創建List<>與右容量)

C)運行基準之前的方法中的至少一次或兩次。 D)儘量不要做除了基準測試以外的任何事情,而是強制編譯器運行你的代碼。例如,根據操作的結果檢查始終爲真的條件,如果爲false則拋出異常通常足以欺騙編譯器。

static void Main() 
{ 
    // Check x86 or x64 
    Console.WriteLine(IntPtr.Size == 4 ? "x86" : "x64"); 

    // Check Debug/Release 
    Console.WriteLine(IsDebug() ? "Debug, USELESS BENCHMARK" : "Release"); 

    // Check if debugger is attached 
    Console.WriteLine(System.Diagnostics.Debugger.IsAttached ? "Debugger attached, USELESS BENCHMARK!" : "Debugger not attached"); 

    // High priority 
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; 

    Stopwatch MulOperatorWatch = new Stopwatch(); 
    Stopwatch MulMethodWatch = new Stopwatch(); 

    // Prerunning of the benchmarked methods 
    MulMethodClass.start(); 
    MulOperatorClass.start(); 

    { 
     // No useless method allocation here 
     MulMethodWatch.Start(); 

     for (int i = 0; i < 100; i++) 
     { 
      MulMethodClass.start(); 
     } 

     MulMethodWatch.Stop(); 
    } 

    { 
     // No useless method allocation here 
     MulOperatorWatch.Start(); 

     for (int i = 0; i < 100; i++) 
     { 
      MulOperatorClass.start(); 
     } 

     MulOperatorWatch.Stop(); 
    } 


    Console.WriteLine("Operator = " + MulOperatorWatch.ElapsedMilliseconds.ToString()); 
    Console.WriteLine("Method = " + MulMethodWatch.ElapsedMilliseconds.ToString()); 
    Console.ReadLine(); 
} 

public class MulOperatorClass 
{ 
    // The method is static. No useless memory allocation 
    public static void start() 
    { 
     for (int i = 2; i < 15000000; i++) 
     { 
      // This condition will always be false, but the compiler 
      // won't be able to remove the code 
      if (((long)i) * ((long)i) == ((long)i)) 
      { 
       throw new Exception(); 
      } 
     } 
    } 
} 

public class MulMethodClass 
{ 
    public static void start() 
    { 
     // The method is static. No useless memory allocation 
     for (int i = 2; i < 15000000; i++) 
     { 
      // This condition will always be false, but the compiler 
      // won't be able to remove the code 
      if (Math.BigMul(i, i) == i) 
      { 
       throw new Exception(); 
      } 
     } 
    } 
} 

private static bool IsDebug() 
{ 
    // Taken from http://stackoverflow.com/questions/2104099/c-sharp-if-then-directives-for-debug-vs-release 
    object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(DebuggableAttribute), false); 

    if ((customAttributes != null) && (customAttributes.Length == 1)) 
    { 
     DebuggableAttribute attribute = customAttributes[0] as DebuggableAttribute; 
     return (attribute.IsJITOptimizerDisabled && attribute.IsJITTrackingEnabled); 
    } 

    return false; 
} 

E)如果你真的確定你的代碼是好的,請嘗試更改)測試的順序

f將你的程序在更高的優先級

但很高興:-)

至少另一個人有同樣的問題,並寫了一篇博客文章:http://reflectivecode.com/2008/10/mathbigmul-exposed/

他做了同樣的錯誤。

+0

啊,很好的答案。你從來沒有這樣稱呼過這種'Microbenchmarking'。感謝您的有用信息和有用的文章。只是一些問題 - 是否有你從'int i = 2'而不是'int i = 1'循環的原因?我不確定我明白爲什麼在主循環之前運行'start'方法一次?你在C中提到它,但爲什麼?好日子先生:) – Eminem

+0

那麼,我現在得到'int i = 2' - 以防止異常被拋出:)尼斯。 – Eminem

+0

@Eminem你正對'int i = 2'。對於預運行:有時代碼在第一次運行時由CLR懶惰地編譯爲機器代碼。顯然這個「編譯時間」不是你想要的基準。你運行一次或兩次,所以你確定你的代碼已經準備好運行。 – xanatos