下面以实际例子具体解释相关技巧。

 (1)缘起

bfax@smth.org发了一个字符串转换程序,引起了热烈讨论。原程序如下:




 1dotnet程序优化心得(二)_字符串转换

 2dotnet程序优化心得(二)_初始化_02dotnet程序优化心得(二)_i++_03Function B2G()Function B2G(prestr As String) As String

 3dotnet程序优化心得(二)_i++_04    Dim i, j As Integer

 4dotnet程序优化心得(二)_字符串转换_05    Const GB_Lib = "dotnet程序优化心得(二)_数据结构_06"    //几千个字符吧,因为字符串长度限制,原程序是由GB_lib1,GB_lib2dotnet程序优化心得(二)_数据结构_06GB_lib4四个字符串构成的,为了简化问题,只用一个字符串代替。

 5dotnet程序优化心得(二)_i++_08    Const BIG5_Lib = "dotnet程序优化心得(二)_数据结构_06"    //与GB_Lib中简体字一一对应的繁体字

 6dotnet程序优化心得(二)_字符串转换_10    

 7dotnet程序优化心得(二)_字符串转换_11    For i = 1 To prestr.Length

 8dotnet程序优化心得(二)_字符串转换_12        j= Instr(1, BIG5_Lib1, GetChar(prestr, i)) 

 9dotnet程序优化心得(二)_初始化_13        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib1,j),GetChar(GB_Lib1,j))

10dotnet程序优化心得(二)_数据结构_14        j= Instr(1, BIG5_Lib2, GetChar(prestr, i)) 

11dotnet程序优化心得(二)_字符串转换_15        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib2,j),GetChar(GB_Lib2,j))

12dotnet程序优化心得(二)_字符串_16        j= Instr(1, BIG5_Lib3, GetChar(prestr, i)) 

13dotnet程序优化心得(二)_字符串转换_17        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib3,j),GetChar(GB_Lib3,j))

14dotnet程序优化心得(二)_字符串转换_18        j= Instr(1, BIG5_Lib4, GetChar(prestr, i)) 

15dotnet程序优化心得(二)_i++_19        If j<>0 Then prestr=prestr.Replace(GetChar(BIG5_Lib4,j),GetChar(GB_Lib4,j))

16dotnet程序优化心得(二)_初始化_20    Next

17dotnet程序优化心得(二)_i++_21    Return prestr

18dotnet程序优化心得(二)_初始化_22End Function



(2) 分析问题


写测试程序测试在我的1.5M迅驰本本测试,替换效率为30万字/s,该程序采用Replace,这样对每一个字符都要扫描GB_Lib字符串中的几千个字符,性能自然上不去。需要寻找更好的数据结构和算法,降低每一个字符串的操作时间。


.net类库里有一个很好的东西可以拿来直接用:Hashtable。也就是说,把每一个简体字作为key,每一个繁体字作为value。这样处理每个字符的时候只需要看它在不在Hashtable的key里面,在的话就找出对应的value替换,否则就不做任何操作。这样做的代价是Hashtable初始化的耗时,不过初始化顶多也就一次嘛。程序如下:



 1dotnet程序优化心得(二)_字符串_23public class ConvertDemo

 2dotnet程序优化心得(二)_i++_24dotnet程序优化心得(二)_数据结构_25dotnet程序优化心得(二)_数据结构_06{

 3dotnet程序优化心得(二)_i++_27    private static Hashtable _libTable;

 4dotnet程序优化心得(二)_字符串_28

 5dotnet程序优化心得(二)_字符串转换_29    static ConvertDemo()

 6dotnet程序优化心得(二)_i++_30dotnet程序优化心得(二)_数据结构_31    dotnet程序优化心得(二)_数据结构_06{

 7dotnet程序优化心得(二)_字符串转换_33        InitHashTable();

 8dotnet程序优化心得(二)_数据结构_34    }

 9dotnet程序优化心得(二)_字符串转换_35

10dotnet程序优化心得(二)_字符串转换_36    static string GB_lib="dotnet程序优化心得(二)_数据结构_06";

11dotnet程序优化心得(二)_数据结构_38

12dotnet程序优化心得(二)_数据结构_39    static string BIG5_lib="dotnet程序优化心得(二)_数据结构_06";

13dotnet程序优化心得(二)_字符串转换_41

14dotnet程序优化心得(二)_数据结构_42    private static void InitHashTable()

15dotnet程序优化心得(二)_初始化_43dotnet程序优化心得(二)_初始化_44    dotnet程序优化心得(二)_数据结构_06{

16dotnet程序优化心得(二)_数据结构_46        _libTable = new Hashtable();

17dotnet程序优化心得(二)_字符串转换_47        PushIntoHashtable(_libTable,GB_lib,BIG5_lib);

18dotnet程序优化心得(二)_数据结构_48    }

19dotnet程序优化心得(二)_初始化_49

20dotnet程序优化心得(二)_i++_50    private static void PushIntoHashtable(Hashtable t, string g , string b)

21dotnet程序优化心得(二)_初始化_51dotnet程序优化心得(二)_字符串转换_52    dotnet程序优化心得(二)_数据结构_06{

22dotnet程序优化心得(二)_初始化_54        for (int i=0;i<g.Length;i++)

23dotnet程序优化心得(二)_初始化_55dotnet程序优化心得(二)_字符串_56        dotnet程序优化心得(二)_数据结构_06{

24dotnet程序优化心得(二)_初始化_58            t.Add(g[i],b[i]);

25dotnet程序优化心得(二)_初始化_59        }

26dotnet程序优化心得(二)_数据结构_60    }

27dotnet程序优化心得(二)_初始化_61

28dotnet程序优化心得(二)_数据结构_62    private static char ConvertChar(char input)

29dotnet程序优化心得(二)_i++_63dotnet程序优化心得(二)_字符串转换_64    dotnet程序优化心得(二)_数据结构_06{

30dotnet程序优化心得(二)_数据结构_66        if (_libTable.ContainsKey(input)) return (char)_libTable[input];

31dotnet程序优化心得(二)_字符串_67        else return input;

32dotnet程序优化心得(二)_字符串_68    }

33dotnet程序优化心得(二)_初始化_69

34dotnet程序优化心得(二)_字符串_70    public static string ConvertText(string inputString)

35dotnet程序优化心得(二)_初始化_71dotnet程序优化心得(二)_数据结构_72    dotnet程序优化心得(二)_数据结构_06{

36dotnet程序优化心得(二)_i++_74        StringBuilder sb = new StringBuilder(inputString);

37dotnet程序优化心得(二)_数据结构_75        for (int i=0;i<inputString.Length;i++)

38dotnet程序优化心得(二)_初始化_76dotnet程序优化心得(二)_字符串转换_77        dotnet程序优化心得(二)_数据结构_06{

39dotnet程序优化心得(二)_字符串转换_79            sb[i] = ConvertChar(inputString[i]);

40dotnet程序优化心得(二)_初始化_80        }

41dotnet程序优化心得(二)_i++_81        return sb.ToString();

42dotnet程序优化心得(二)_字符串_82    }

43dotnet程序优化心得(二)_字符串_83}


测试性能,结果为300万字/秒。性能提高了10倍。

(3)用relector看Hashtable源代码,消除无用操作,继续优化


还能不能继续优化呢?ConvertChar (char input)执行次数最多,是对性能最有影响的方法。用reflector反编译Hashtable的get_Item(object key)方法:




 1dotnet程序优化心得(二)_字符串转换_84public virtual object get_Item(object key)

 2dotnet程序优化心得(二)_i++_85dotnet程序优化心得(二)_数据结构_86dotnet程序优化心得(二)_数据结构_06{

 3dotnet程序优化心得(二)_数据结构_88      uint num1;

 4dotnet程序优化心得(二)_i++_89      uint num2;

 5dotnet程序优化心得(二)_初始化_90      Hashtable.bucket bucket1;

 6dotnet程序优化心得(二)_i++_91      if (key == null)

 7dotnet程序优化心得(二)_初始化_92dotnet程序优化心得(二)_数据结构_93      dotnet程序优化心得(二)_数据结构_06{

 8dotnet程序优化心得(二)_数据结构_95            throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));

 9dotnet程序优化心得(二)_数据结构_96      }

10dotnet程序优化心得(二)_i++_97      Hashtable.bucket[] bucketArray1 = this.buckets;

11dotnet程序优化心得(二)_i++_98      uint num3 = this.InitHash(key, bucketArray1.Length, out num1, out num2);

12dotnet程序优化心得(二)_字符串转换_99      int num4 = 0;

13dotnet程序优化心得(二)_数据结构_100      int num5 = (int) (num1 % bucketArray1.Length);

14dotnet程序优化心得(二)_字符串_101      do

15dotnet程序优化心得(二)_i++_102dotnet程序优化心得(二)_初始化_103      dotnet程序优化心得(二)_数据结构_06{

16dotnet程序优化心得(二)_i++_105            bucket1 = bucketArray1[num5];

17dotnet程序优化心得(二)_数据结构_106            if (bucket1.key == null)

18dotnet程序优化心得(二)_i++_107dotnet程序优化心得(二)_字符串_108            dotnet程序优化心得(二)_数据结构_06{

19dotnet程序优化心得(二)_字符串_110                  return null;

20dotnet程序优化心得(二)_字符串_111            }

21dotnet程序优化心得(二)_初始化_112            if (((bucket1.hash_coll & 0x7fffffff) == num3) && this.KeyEquals(key, bucket1.key))

22dotnet程序优化心得(二)_数据结构_113dotnet程序优化心得(二)_字符串_114            dotnet程序优化心得(二)_数据结构_06{

23dotnet程序优化心得(二)_数据结构_116                  return bucket1.val;

24dotnet程序优化心得(二)_初始化_117            }

25dotnet程序优化心得(二)_初始化_118            num5 = (int) ((num5 + num2) % ((ulong) bucketArray1.Length));

26dotnet程序优化心得(二)_i++_119      }

27dotnet程序优化心得(二)_初始化_120      while ((bucket1.hash_coll < 0) && (++num4 < bucketArray1.Length));

28dotnet程序优化心得(二)_数据结构_121      return null;

29dotnet程序优化心得(二)_i++_122}


我的天天天天天天天天天天天天天........好长呀,先不管这个。哦,方法并不抛出异常,如果key不存在就直接返回null。这样的话,采用ContainsKey(...)判断key是否存在就是多次一举了。

把ConvertChar (char input)改为:



1dotnet程序优化心得(二)_初始化_123private static char ConvertChar(char input)

2dotnet程序优化心得(二)_i++_124dotnet程序优化心得(二)_数据结构_125dotnet程序优化心得(二)_数据结构_06{

3dotnet程序优化心得(二)_字符串转换_127object temp = _libTable[input];

4dotnet程序优化心得(二)_数据结构_128return temp == null?input:(char)temp;

5dotnet程序优化心得(二)_初始化_129}


这样大概能节省一半的操作。

测试结果验证了我这一想法。性能一下提高了40%,达到了500万字/s


注:上面程序有小bug,后来发现的。