C#中那些[举手之劳]的性能优化

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒......

其实这篇很早就想写了

工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率

其实这对大多数程序猿来说都是没有问题的

不过作为一只有理想的CodeMonkey,我还是希望给大家分享一些性能优化心得

曾经在网上听过这样一句话

程序的可读性和性能是成反比的

我非常赞同这句话,所以对于那些极度影响阅读的性能优化我就不在这里赘述了

今天主要说的就是一些举手之劳即可完成的性能优化

减少重复代码这是最基本的优化方案,尽可能减少那些重复做的事,让他们只做一次

比较常见是这种代码,同样的Math.Cos(angle) 和Math.Sin(angle)都做了2次

PRivatePoint RotatePt(doubleangle, Point pt)

{

Point pRet=newPoint();

angle= -angle;

pRet.X= (int)((double)pt.X * Math.Cos(angle) - (double)pt.Y *Math.Sin(angle));

pRet.Y= (int)((double)pt.X * Math.Sin(angle) + (double)pt.Y *Math.Cos(angle));returnpRet;

}

优化后

privatePoint RotatePt3(doubleangle, Point pt)

{

Point pRet=newPoint();

angle= -angle;doubleSIN_ANGLE =Math.Sin(angle);doubleCOS_ANGLE =Math.Cos(angle);

pRet.X=(int)(pt.X * COS_ANGLE - pt.Y *SIN_ANGLE);

pRet.Y= (int)(pt.X * SIN_ANGLE + pt.Y *COS_ANGLE);returnpRet;

}

还有另一种 ,在方法中实例化一个对象, 但是这个对象其实是可以复用的

publicstaticstringConvertQuot(stringhtml)

{

Regex regex=newRegex("&(quot|#34);", RegexOptions.IgnoreCase);returnregex.Replace(html,"\"");

}

优化后

readonly staticRegex ReplaceQuot =newRegex("&(quot|#34);", RegexOptions.IgnoreCase |RegexOptions.Compiled);publicstaticstringConvertQuot(stringhtml)

{returnReplaceQuot.Replace(html,"\"");

}

还有一种是不必要的初始化,比如调用out参数之前,是不需要初始化的

publicboolCheck(intuserid)

{varuser =newUser();if(GetUser(userid,outuser))

{returnuser.Level >1;

}returnfalse;

}

这里的new User()就是不必要的操作,

优化后

publicboolCheck(intuserid)

{

User user;if(GetUser(userid,outuser))

{returnuser.Level >1;

}returnfalse;

}

不要迷信正则表达式正好在第一个栗子里说到了正在表达式(Regex)对象就顺便一起说了

很多人以为正则表达式很快,非常快,超级的快

虽然正则表达式是挺快的,不过千万不要迷信他,不信你看下面的栗子

//方法1publicstaticstringConvertQuot1(stringhtml)

{returnhtml.Replace(""","\"").Replace(""","\"");

}readonlystaticRegex ReplaceQuot =newRegex("&(quot|#34);", RegexOptions.IgnoreCase |RegexOptions.Compiled);//方法2publicstaticstringConvertQuot2(stringhtml)

{returnReplaceQuot.Replace(html,"\"");

}

有多少人认为正则表达式比较快的,举个手??

结果为10w次循环的时间,即使是10个Replace连用,也比Regex好,所以不要迷信他

//方法1publicstaticstringConvertQuot1(stringhtml)

{returnhtml.Replace("0","").Replace("1","").Replace("2","").Replace("3","").Replace("4","").Replace("5","").Replace("6","").Replace("7","").Replace("8","").Replace("9","");

}readonlystaticRegex ReplaceQuot =newRegex("[1234567890]", RegexOptions.IgnoreCase |RegexOptions.Compiled);//方法2publicstaticstringConvertQuot2(stringhtml)

{returnReplaceQuot.Replace(html,"");

}

ConvertQuot1:3518

ConvertQuot2:12479

最后给你们看一个真实的,杯具的栗子

Htmlstring = Regex.Replace(Htmlstring,@"<(.[^>]*)>","", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"([\r\n])[\s]+","", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"-->","", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"<!--.*","", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(quot|#34);","\"", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(amp|#38);","&", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(lt|#60);","<", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(gt|#62);",">", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(nbsp|#160);","", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(iexcl|#161);","\xa1", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(cent|#162);","\xa2", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(pound|#163);","\xa3", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&(copy|#169);","\xa9", RegexOptions.IgnoreCase);

Htmlstring= Regex.Replace(Htmlstring,@"&#(\d+);","", RegexOptions.IgnoreCase);

View Code

合理使用正则表达式上面说了正则表达式的效率不高,并不是说就不要用他了,至少正则表达式的作用不仅仅如此而已

如果一定要用正则表达式的话也需要注意,能静态全局公用的尽量全局公用

readonlystaticRegex regex =newRegex("[1234567890]", RegexOptions.Compiled);

注意他的第二个参数RegexOptions.Compiled 注释是 指定将正则表达式编译为程序集。这会产生更快的执行速度,但会增加启动时间。

通俗的说就是加了这个枚举,会使得初始化Regex对象变慢,但是执行字符串查找的时候更快, 不使用的话,初始化很多,查询比较慢

之前测过相差蛮大的 ,代码就不比较了,有兴趣的可以自己试试相差多少

另外还有一些枚举项,不确定是否对性能有影响,不过还是按规则使用会比较好

RegexOptions.IgnoreCase // 指定不区分大小写的匹配, 如果表达式中没有字母,则不需要设定RegexOptions.Multiline // 多行模式。更改 ^ 和 $ 的含义.... 如果表达式中没有^和$,则不需要设定RegexOptions.Singleline // 指定单行模式。更改点 (.) 的含义.... 如果表达式中没有.,则不需要设定让编译器预处理常量的计算编译器在编译程序段的时候 如果发现有一些运算是常量对常量的,那么他会在编译期间就计算完成,这样可以使程序在执行时不用重复计算了

比如

不过编译器有的时候也不是那么聪明的

这个时候就需要我们帮助一下了

给他加一个括号,让他知道应该先计算常量,这样就可以在编译期间进行运算了

字符串比较这个可能很多人知道了,但还是提一下

strings ="";1)if(s ==""){}2)if(s ==string.Empty){}3)if(string.IsNullOrEmpty(s)) { }4)if(s !=null&& s.Length ==0) {}5)if((s+"").Length ==0){}

1,2最慢 3较快 4,5最快

1,2几乎没区别 4,5几乎没区别

不过这个只适用于比较null和空字符串,如果是连续的空白就是string.IsNullOrWhiteSpace最快了,不过这个方法2.0里面没有

所以2.0可以这样 (s+"").trim() == 0

这里的关键就是 s + "" 这个操作可以把null转换为""

注意第二个参数只能是""或string.Empty这样的累加几乎是不消耗时间的,如果第二个参数是" "(一个空格)这个时间就远远不止了

字符串拼接字符串累加,这个道理和Regex一样,不要盲目崇拜StringBuilder

在大量(或不确定的)string拼接的时候,StringBuilder确实可以起到提速的作用

而少数几个固定的string累加的时候就不需要StringBuilder 了,毕竟StringBuilder 的初始化也是需要时间的

感谢#5楼 2014-03-24 16:45 残蛹 博友提供的说明

ps: 这段我确实记得我是写过的来着,不知道怎么的,发出来的时候就不见了.....

此外还有一个string.Concat方法,该方法可以小幅度的优化程序的速度,幅度很小

他和string.Join的区别在于没有间隔符号(我之前常用string.Join("",a,b,c,d),不要告诉我只有我一个人这么干)

另一种经常遇到的字符串拼接

publicstringJoinIds(List<User>users)

{

StringBuilder sb=newStringBuilder();foreach(varuserinusers)

{

sb.Append("'");

sb.Append(user.Id);

sb.Append("',");

}

sb.Length= sb.Length -1;returnsb.ToString();

}

对于这种情况有2中优化的方案

对于3.5以上可以直接使用Linq辅助,这种方案代码少,但是性能相对差一些

publicstringJoinIds(List<User>users)

{return"'"+string.Join("','", users.Select(it => it.Id)) +"'";

}

对于非3.5或对性能要求极高的场合

publicstringJoinIds(List<User>users)

{varee =users.GetEnumerator();

StringBuilder sb=newStringBuilder();if(ee.MoveNext())

{

sb.Append("'");

sb.Append(ee.Current.Id);

sb.Append("'");while(ee.MoveNext())

{

sb.Append(",'");

sb.Append(ee.Current.Id);

sb.Append("'");

}

}returnsb.ToString();

}

bool类型的判断返回这种现象常见于新手程序员中

//写法1if(state ==1)

{returntrue;

}else{returnfalse;

}//写法2returnstate ==1?true:false;//优化后returnstate ==1;

类型的判断一般类型的判断有2种形式

1,这种属于代码比较好写,但是性能比较低, 原因就是GetType()的时候消耗了很多时间

Type type =obj.GetType();switch(type.Name)

{case"Int32":

break;case"String":

break;case"Boolean":

break;case"DateTime":

break;

...

...default:

break;

}

2,这种属性写代码麻烦,但是性能很高的类型

if(objisstring)

{

}elseif(objisint)

{

}elseif(objisDateTime)

{

}

...

...else{

}

其实有个中间之道,既可以保证性能又可以比较好写

IConvertible conv = objasIConvertible;if(conv !=null)

{switch(conv.GetTypeCode())

{caseTypeCode.Boolean:

break;caseTypeCode.Byte:

break;caseTypeCode.Char:

break;caseTypeCode.DBNull:

break;caseTypeCode.DateTime:

break;caseTypeCode.Decimal:

break;caseTypeCode.Double:

break;caseTypeCode.Empty:

break;caseTypeCode.Int16:

break;caseTypeCode.Int32:

break;

...

...default:

break;

}

}else{//处理其他类型}

大部分情况下 这个是可以用的 如果你自己有个类型实现了IConvertible,然后返回TypeCode.Int32 就不再这个讨论范围之内了

使用枚举作为索引下面这个是一个真实的例子,为了突出重点,做了部分修改,删除了多余的分支,源代码中不只4个

enumTemplateCode

{

None=0,

Head=1,

Menu=2,

Foot=3,

Welcome=4,

}publicstringGetHtml(TemplateCode tc)

{switch(tc)

{caseTemplateCode.Head:returnGetHead();caseTemplateCode.Menu:returnGetMenu();caseTemplateCode.Foot:returnGetFoot();caseTemplateCode.Welcome:returnGetWelcome();default:thrownewArgumentOutOfRangeException("tc");

}

}

优化后

readonly staticFunc<string>[] GetTemplate =InitTemplateFunction();privatestaticFunc<string>[] InitTemplateFunction()

{vararr =newFunc<string>[5];

arr[1] =GetHead;

arr[2] =GetMenu;

arr[3] =GetFoot;

arr[4] =GetWelcome;

returnarr;

}publicstringGetHtml(TemplateCode tc)

{varindex = (int)tc;if(index >=1&& index <=4)

{returnGetTemplate[index]();

}thrownewArgumentOutOfRangeException("tc");

}

不过有的时候,枚举不一定都是连续的数字,那么也可以使用Dictionary

readonly staticDictionary<TemplateCode, Func<string>> TemplateDict =InitTemplateFunction();privatestaticDictionary<TemplateCode, Func<string>>InitTemplateFunction()

{varditc =newDictionary<TemplateCode, Func<string>>();

ditc.Add(TemplateCode.Head, GetHead);

ditc.Add(TemplateCode.Menu, GetMenu);

ditc.Add(TemplateCode.Foot, GetFoot);

ditc.Add(TemplateCode.Welcome, GetWelcome);returnditc;

}publicstringGetHtml(TemplateCode tc)

{

Func<string>func;if(TemplateDict.TryGetValue(tc,outfunc))

{returnfunc();

}thrownewArgumentOutOfRangeException("tc");

}

这种优化在分支比较多的时候很好用,少的时候作用有限

字符类型Char,分支判断时的处理技巧这部分内容比较复杂,而且适用范围有限,如果平时用不到的就可以忽略了

在处理字符串对象的时候,有时会需要判断char的值然后做进一步的操作

publicstringShow(charc)

{if(c >= '0'&& c <= '9')

{return"数字";

}elseif(c >='a'&& c <='z')

{return"小写字母";

}elseif(c >='A'&& c <='Z')

{return"大写字母";

}elseif(c =='/'|| c =='\\'|| c =='|'|| c =='$'|| c =='#'|| c =='+'|| c =='%'|| c =='&'|| c =='-'|| c =='^'|| c =='*'|| c =='=')

{return"特殊符号";

}elseif(c ==','|| c =='.'|| c =='!'|| c ==':'|| c ==';'|| c =='?'|| c =='"'|| c =='\'')

{return"标点符号";

}else{return"其他";

}

}

这里有一种空间换时间的优化方式, 虽说是空间换时间,但是实际浪费的空间不会很多,因为char最多只有65536长度

readonlystaticbyte[] CharMap =InitCharMap();privatestaticbyte[] InitCharMap()

{vararr =newbyte[char.MaxValue];for(chari ='0'; i <='9'; i++)

{

arr[i]=1;

}for(chari ='a'; i <='z'; i++)

{

arr[i]=2;

}for(chari ='A'; i <='Z'; i++)

{

arr[i]=3;

}

arr['/'] =4;

arr['\\'] =4;

arr['|'] =4;

arr['$'] =4;

arr['#'] =4;

arr['+'] =4;

arr['%'] =4;

arr['&'] =4;

arr['-'] =4;

arr['^'] =4;

arr['*'] =4;

arr['='] =4;

arr[','] =5;

arr['.'] =5;

arr['!'] =5;

arr[':'] =5;

arr[';'] =5;

arr['?'] =5;

arr['"'] =5;

arr['\''] =5;returnarr;

}publicstringShow(charc)

{switch(CharMap[c])

{case0:return"其他";case1:return"数字";case2:return"小写字母";case3:return"大写字母";case4:return"特殊符号";case5:return"标点符号";default:return"其他";

}

}

原先仅特殊符号一部分就需要判断12次,修改过后只判断一次就可以得到结果了

这方面的栗子在我的Json组件(代码)(文章1,2,3)中也有使用

///<summary>///<para>包含1: 可以为头的字符</para>///<para>包含2: 可以为单词的字符</para>///<para>包含4: 可以为数字的字符</para>///<para>等于8: 空白字符</para>///<para>包含16:转义字符</para>///<para></para>///</summary>privatereadonlystaticbyte[] _WordChars =newbyte[char.MaxValue];privatereadonlystaticsbyte[] _UnicodeFlags =newsbyte[123];privatereadonlystaticsbyte[, ,] _DateTimeWords;staticUnsafeJsonReader()

{for(inti =0; i <123; i++)

{

_UnicodeFlags[i]= -1;

}

_WordChars['-'] =1|4;

_WordChars['+'] =1|4;

_WordChars['$'] =1|2;

_WordChars['_'] =1|2;for(charc ='a'; c <='z'; c++)

{

_WordChars[c]=1|2;

_UnicodeFlags[c]= (sbyte)(c -'a'+10);

}for(charc ='A'; c <='Z'; c++)

{

_WordChars[c]=1|2;

_UnicodeFlags[c]= (sbyte)(c -'A'+10);

}

_WordChars['.'] =1|2|4;for(charc ='0'; c <='9'; c++)

{

_WordChars[c]=4;

_UnicodeFlags[c]= (sbyte)(c -'0');

}//科学计数法_WordChars['e'] |=4;

_WordChars['E'] |=4;

_WordChars[''] =8;

_WordChars['\t'] =8;

_WordChars['\r'] =8;

_WordChars['\n'] =8;

_WordChars['t'] |=16;

_WordChars['r'] |=16;

_WordChars['n'] |=16;

_WordChars['f'] |=16;

_WordChars['0'] |=16;

_WordChars['"'] |=16;

_WordChars['\''] |=16;

_WordChars['\\'] |=16;

_WordChars['/'] |=16;string[] a = {"jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"};string[] b = {"mon","tue","wed","thu","fri","sat","sun"};

_DateTimeWords=newsbyte[23,21,25];for(sbytei =0; i < a.Length; i++)

{vard =a[i];

_DateTimeWords[d[0] -97, d[1] -97, d[2] -97] = (sbyte)(i +1);

}for(sbytei =0; i < b.Length; i++)

{vard =b[i];

_DateTimeWords[d[0] -97, d[1] -97, d[2] -97] = (sbyte)-(i +1);

}

_DateTimeWords['g'-97,'m'-97,'t'-97] =sbyte.MaxValue;

}

摘取部分blqw.Json的代码

结束了...还要后续吗?...貌似我又要懒一段时间

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航