再談 BigInteger – 最佳化

來源:互聯網
上載者:User
文章目錄
  • 參考資料:

在上篇隨筆“淺談 GetHashCode”中,我實現了一個新的 Skyiv.Numeric.BigInteger.GetHashCode 方法:

public override int GetHashCode(){  int n = sign;  for (int i = data.Length - 1; i >= 0; i -= 4)  {    int m = data[i];    if (i > 0) m |= (data[i - 1] << 8);    if (i > 1) m |= (data[i - 2] << 16);    if (i > 2) m |= (data[i - 3] << 24);    n = m ^ (n + (n << 5) + (n >> 0x1b));  }  return n * 0x5d588b65;}

上述代碼比較優雅,但是在 for 迴圈中的三個 if 語句其實可以提到 for 迴圈外面的,如下所示:

public override int GetHashCode(){  int n = sign, i;  for (i = data.Length - 1; i >= 4; i -= 4)    n = (data[i] | (data[i - 1] << 8) | (data[i - 2] << 16) | (data[i - 3] << 24)) ^ (n + (n << 5) + (n >> 27));  if (i >= 0)  {    int m = data[i];    if (i > 0) m |= (data[i - 1] << 8);    if (i > 1) m |= (data[i - 2] << 16);    if (i > 2) m |= (data[i - 3] << 24);    n = m ^ (n + (n << 5) + (n >> 27));  }  return n * 0x5d588b65;}

雖然以上代碼不那麼優雅,有重複的“壞味道”,但是效率更高了。我們來實際測試一下吧:

using System;using System.Diagnostics;using BigInteger = Skyiv.Numeric.BigInteger;namespace Skyiv{  class TestMain  {    static void Main(string[] args)    {      try      {        var stopwatch = Stopwatch.StartNew();        var s = new string('7', (args.Length > 0) ? int.Parse(args[0]) : 1000000);        var n = BigInteger.Parse(s);        stopwatch.Stop();        WriteLine("Parse Digits", s.Length, stopwatch.Elapsed);        TestGetHashCode(n, true);        TestGetHashCode(n, false);      }      catch (Exception ex)      {        Console.WriteLine(ex);      }    }    static void TestGetHashCode(BigInteger n, bool isFast)    {      var stopwatch = Stopwatch.StartNew();      var hash = n.GetHashCode(isFast);      stopwatch.Stop();      WriteLine(isFast ? "Fast HashCode" : "Simple HashCode", hash, stopwatch.Elapsed);    }    static void WriteLine(string item, int value, TimeSpan span)    {      Console.WriteLine("{0,15}: {1,14:N0}  Elapsed: {2,11:F7}", item, value, span.TotalSeconds);    }  }}

測試結果如下所示:

E:\CS\BigInteger> BigInteger 600000000   Parse Digits:    600,000,000  Elapsed: 254.8271638  Fast HashCode:    353,915,569  Elapsed:   1.5341352Simple HashCode:    353,915,569  Elapsed:   1.7766916

首先使用 BigInteger.Parse 方法產生一個有六億位元字的整數(用時 254.83 秒),然後:

  • 調用新的 GetHashCode 方法,傳回值是 353,915,569,用時 1.53 秒。
  • 調用舊的 GetHashCode 方法,傳回值是 353,915,569,用時 1.77 秒。

可見,新的 GetHashCode 方法的效率的確更高,但是提高的幅度也不多,而且還損失了代碼的優雅性。

 

怎麼 BigInteger.Parse 方法耗時這麼多?去看看來源程式:

public static BigInteger Parse(string s){  if (s == null) return null;  if (s.Length == 0) return 0;  BigInteger z = new BigInteger();  z.sign = (sbyte)((s[0] == '-') ? -1 : 1);  if (s[0] == '-' || s[0] == '+') s = s.Substring(1);  int r = s.Length % Len;  z.data = new byte[s.Length / Len + ((r != 0) ? 1 : 0)];  int i = 0;  if (r != 0) z.data[i++] = byte.Parse(s.Substring(0, r));  for (; i < z.data.Length; i++, r += Len) z.data[i] = byte.Parse(s.Substring(r, Len));  z.Shrink();  return z;}

這個 Parse 方法中耗時最多的就是 for 迴圈,於是,改寫這個 for 迴圈,用簡單的數學計算代替費時的 byte.Parse 方法,如下所示:

  for (; i < z.data.Length; i++, r += Len) z.data[i] = (byte)((s[r] - '0') * 10 + (s[r + 1] - '0'));

然後重新運行測試程式,結果如下:

E:\CS\BigInteger> BigInteger 600000000   Parse Digits:    600,000,000  Elapsed:  11.0663091  Fast HashCode:    353,915,569  Elapsed:   1.5217181Simple HashCode:    353,915,569  Elapsed:   1.7744348

結果用時從 254.83 秒下降到 11.07 秒,效率得到了極大地提高,而且代碼還保持了優雅。這是一次非常成功的最佳化。

 

這兩次測試的 CPU 佔用和記憶體使用量情況如下所示:

 

接著,我們來看看 ToString 方法:

public override string ToString(){  var sb = new StringBuilder();  if (sign < 0) sb.Append('-');  sb.Append((data.Length == 0) ? 0 : (int)data[0]);  for (var i = 1; i < data.Length; i++) sb.Append(data[i].ToString("D" + Len));  return sb.ToString();}

這也可以最佳化如下:

public override string ToString(){  if (data.Length == 0) return "0";  var sb = new StringBuilder(Length, Length);  sb.Length = Length;  var k = 0;  if (sign < 0) sb[k++] = '-';  if (data[0] >= 10) sb[k++] = (char)(data[0] / 10 + '0');  sb[k++] = (char)(data[0] % 10 + '0');  for (var i = 1; i < data.Length; i++)  {    sb[k++] = (char)(data[i] / 10 + '0');    sb[k++] = (char)(data[i] % 10 + '0');  }  return sb.ToString();}public int Length{  get { return (data.Length == 0) ? 1 : (((sign < 0) ? 1 : 0) + ((data[0] < 10) ? -1 : 0) + data.Length * Len); }}

下面是最佳化之前測試結果:

E:\CS\BigInteger> BigInteger 100000000ToString Digits:    100,000,000  Elapsed:  99.4138987  Fast HashCode:  1,470,973,525  Elapsed:   0.2504019Simple HashCode:  1,470,973,525  Elapsed:   0.2944836

最佳化之後:

E:\CS\BigInteger> BigInteger 100000000ToString Digits:    100,000,000  Elapsed:  10.1196895  Fast HashCode:  1,470,973,525  Elapsed:   0.2530704Simple HashCode:  1,470,973,525  Elapsed:   0.2957576

可以看出,調用 ToString 方法將一個有一億位元字的整數輸出,耗時從原來的 99.41 秒下降到最佳化後的 10.12 秒,效果很是顯著。不但如此,最佳化後的 ToString 方法記憶體佔用也大為減少。

 

參考資料:
  1. 再談 BigInteger - 使用快速傅裡葉變換
  2. 淺談 BigInteger
  3. 淺談 GetHashCode

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.