再谈 BigInteger - 优化

在上篇随笔“浅谈 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.5341352
Simple 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.5217181
Simple 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 100000000
ToString Digits:    100,000,000  Elapsed:  99.4138987
  Fast HashCode:  1,470,973,525  Elapsed:   0.2504019
Simple HashCode:  1,470,973,525  Elapsed:   0.2944836

优化之后:

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

可以看出,调用 ToString 方法将一个有一亿位数字的整数输出,耗时从原来的 99.41 秒下降到优化后的 10.12 秒,效果很是显著。不但如此,优化后的 ToString 方法内存占用也大为减少。

版权声明:本文为博主http://www.zuiniusn.com原创文章,未经博主允许不得转载。

时间: 2024-11-02 23:30:08

再谈 BigInteger - 优化的相关文章

Unity教程之再谈Unity中的优化技术

这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体 这一步主要是为了针对性能瓶颈中的”顶点处理“一项.这里的几何体就是指组成场景中对象的网格结构. 3D游戏制作都由模型制作开始.而在建模时,有一条我们需要记住:尽可能减少模型中三角形的数目,一些对于模型没有影响.或是肉眼非常难察觉到区别的顶点都要尽可能去掉.例如在下面左图中,正方体内部很多顶点都是不需要的,而把这个模型导入到Unity里就会是

TCP之再谈解决服务器TIMEWAIT过多的问题

http://blog.chinaunix.net/uid-29075379-id-3904985.html 这个问题在网上已经有很多人讨论过了,再谈这个问题,只是根据我处理过的相关业务来谈谈我的看法.至于什么是TIMEWAIT,我想,并不需要多说. TIMEWAIT状态本身和应用层的客户端或者服务器是没有关系的.仅仅是主动关闭的一方,在使用FIN|ACK|FIN|ACK四分组正常关闭TCP连接的时候会出现这个TIMEWAIT.服务器在处理客户端请求的时候,如果你的程序设计为服务器主动关闭,那么

再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

Angular 的数据绑定采用什么机制,详述原理? 脏检查机制.阐释脏检查机制,必须先了解如下问题. 单向绑定(ng-bind) 和 双向绑定(ng-model) 的区别? ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}. 两者的区别在于页面没有加载完毕 {{val}} 会直接显示到页面,直到 Angular 渲染该绑定数据(这种行为有可能将 {{val}} 让用户看到):而 ng-bind 则是在 Angular 渲染完毕后将数据显示. ng-

再谈循环&amp;迭代&amp;回溯&amp;递归&amp;递推这些基本概念

循环:不断重复进行某一运算.操作. 迭代:不断对前一旧值运算得到新值直到达到精度.一般用于得到近似目标值,反复循环同一运算式(函数),并且总是把前一 次运算结果反代会运算式进行下一次运算 递推:从初值出发反复进行某一运算得到所需结果.-----从已知到未知,从小到达(比如每年长高9cm,20年180,30后270) 回溯:递归时经历的一个过程. 递归:从所需结果出发不断回溯前一运算直到回到初值再递推得到所需结果----从未知到已知,从大到小,再从小到大(你想进bat,那么编程就的牛逼,就得卸载玩

C++ Primer 学习笔记_73_面向对象编程 --再谈文本查询示例

面向对象编程 --再谈文本查询示例 引言: 扩展第10.6节的文本查询应用程序,使我们的系统可以支持更复杂的查询. 为了说明问题,将用下面的简单小说来运行查询: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he

C++ Primer 学习笔记_74_面向对象编程 --再谈文本查询示例[续/习题]

面向对象编程 --再谈文本查询示例[续/习题] //P522 习题15.41 //1 in TextQuery.h #ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <set> #include <map&g

再谈MySQL全库备份

再谈MySQL全库备份 简介 Part1:写在最前 在很早之前,我写过一个MySQL生产库全库备份脚本,今天有同事问我是不是要再加一个-R参数来备份存储过程,理由的话是由于mysqldump --help中 关于存储过程的默认备份是false. routines                          FALSE MySQL生产库全库备份脚本 http://suifu.blog.51cto.com/9167728/1758022 实战 Part1:写在最前 我备份一般就三个参数 --s

Android 再谈handler

今天在做http网络事件的响应网络接收处理一般不能放在主线程中使用,目前也只会使用AsyncTask进行处理!之前虽然写过handler处理的一些文章但是发现全不会了!无奈~ 关于handler某位兄弟已经整理的很透彻了!现在引用下原话如下: Handler监听者框架:子线程是事件源,主线程是监听者.Handler作为子线程的监听器出现:主线程中生成Handler的子类,并重写handleMessage(Message msg) 方法,用来对子线程响应.子线程调用Hanlder的sendMess

再谈ORACLE CPROCD进程

罗列一下有关oprocd的知识点 oprocd是oracle在rac中引入用来fencing io的 在unix系统下,如果我们没有采用oracle之外的第三方集群软件,才会存在oprocd进程 在linux系统下,只有在10.2.0.4版本后,才会具有oprocd进程 在window下,不会存在oprocd 进程,但是会存在一个oraFenceService服务,用来实现相同的功能,该服务采用的技术是基于windows的,与oprocd不同 oprocd进程可以运行在两者模式下:fatal和n