曾經在網上聽過這樣一句話
程序的可讀性和性能是成反比的
我非常贊同這句話,所以對于那些極度影響閱讀的性能優化我就不在這里贅述了
今天主要說的就是一些舉手之勞即可完成的性能優化
減少重復代碼
這是最基本的優化方案,盡可能減少那些重復做的事,讓他們只做一次
比較常見是這種代碼,同樣的math.cos(angle) 和math.sin(angle)都做了2次
優化前
1
2
3
4
5
6
7
8
|
private point rotatept( double angle, point pt) { point pret = new point(); 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)); return pret; } |
優化后
1
2
3
4
5
6
7
8
9
10
|
private point rotatept3( double angle, point pt) { point pret = new point(); angle = -angle; double sin_angle = math.sin(angle); double cos_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); return pret; } |
還有另一種 ,在方法中實例化一個對象, 但是這個對象其實是可以復用的
1
2
3
4
5
|
public static string convertquot( string html) { regex regex = new regex( "&(quot|#34);" , regexoptions.ignorecase); return regex.replace(html, "\"" ); } |
優化后
1
2
3
4
5
|
readonly static regex replacequot = new regex( "&(quot|#34);" , regexoptions.ignorecase | regexoptions.compiled); public static string convertquot( string html) { return replacequot.replace(html, "\"" ); } |
還有一種是不必要的初始化,比如調用out參數之前,是不需要初始化的
1
2
3
4
5
6
7
8
9
|
public bool check( int userid) { var user = new user(); if (getuser(userid, out user)) { return user.level > 1; } return false ; } |
這里的new user()就是不必要的操作,
優化后
1
2
3
4
5
6
7
8
9
|
public bool check( int userid) { user user; if (getuser(userid, out user)) { return user.level > 1; } return false ; } |
不要迷信正則表達式
正好在第一個栗子里說到了正在表達式(regex)對象就順便一起說了
很多人以為正則表達式很快,非???超級的快
雖然正則表達式是挺快的,不過千萬不要迷信他,不信你看下面的栗子
1
2
3
4
5
6
7
8
9
10
11
12
|
//方法1 public static string convertquot1( string html) { return html.replace( "" ", " \ "" ).replace( "" ", " \ "" ); } readonly static regex replacequot = new regex( "&(quot|#34);" , regexoptions.ignorecase | regexoptions.compiled); //方法2 public static string convertquot2( string html) { return replacequot.replace(html, "\"" ); } |
有多少人認為正則表達式比較快的,舉個手??
結果為10w次循環的時間 ,即使是10個replace連用,也比regex好,所以不要迷信他
1
2
3
4
5
6
7
8
9
10
11
12
|
//方法1 public static string convertquot1( string html) { return html.replace( "0" , "" ).replace( "1" , "" ).replace( "2" , "" ).replace( "3" , "" ).replace( "4" , "" ).replace( "5" , "" ).replace( "6" , "" ).replace( "7" , "" ).replace( "8" , "" ).replace( "9" , "" ); } readonly static regex replacequot = new regex( "[1234567890]" , regexoptions.ignorecase | regexoptions.compiled); //方法2 public static string convertquot2( string html) { return replacequot.replace(html, "" ); } |
convertquot1:3518
convertquot2:12479
最后給你們看一個真實的,杯具的栗子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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); |
合理使用正則表達式
上面說了正則表達式的效率不高,并不是說就不要用他了,至少正則表達式的作用不僅僅如此而已
如果一定要用正則表達式的話也需要注意,能靜態全局公用的盡量全局公用
1
|
readonly static regex regex = new regex( "[1234567890]" , regexoptions.compiled); |
注意他的第二個參數regexoptions.compiled 注釋是 指定將正則表達式編譯為程序集。這會產生更快的執行速度,但會增加啟動時間。
通俗的說就是加了這個枚舉,會使得初始化regex對象變慢,但是執行字符串查找的時候更快, 不使用的話,初始化很多,查詢比較慢
之前測過相差蠻大的 ,代碼就不比較了,有興趣的可以自己試試相差多少
另外還有一些枚舉項,不確定是否對性能有影響,不過還是按規則使用會比較好
- regexoptions.ignorecase // 指定不區分大小寫的匹配, 如果表達式中沒有字母,則不需要設定
- regexoptions.multiline // 多行模式。更改 ^ 和 $ 的含義…. 如果表達式中沒有^和$,則不需要設定
- regexoptions.singleline // 指定單行模式。更改點 (.) 的含義…. 如果表達式中沒有.,則不需要設定
讓編譯器預處理常量的計算
編譯器在編譯程序段的時候 如果發現有一些運算是常量對常量的,那么他會在編譯期間就計算完成,這樣可以使程序在執行時不用重復計算了
比如
不過編譯器有的時候也不是那么聰明的
這個時候就需要我們幫助一下了
給他加一個括號,讓他知道應該先計算常量,這樣就可以在編譯期間進行運算了
字符串比較
這個可能很多人知道了,但還是提一下
1
2
3
4
5
6
|
string s = "" ; 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 的初始化也是需要時間的
感謝殘蛹 博友提供的說明
ps: 這段我確實記得我是寫過的來著,不知道怎么的,發出來的時候就不見了…..
此外還有一個string.concat方法,該方法可以小幅度的優化程序的速度,幅度很小
他和string.join的區別在于沒有間隔符號(我之前常用string.join(“”,a,b,c,d),不要告訴我只有我一個人這么干)
另一種經常遇到的字符串拼接
1
2
3
4
5
6
7
8
9
10
11
12
|
public string joinids(list<user> users) { stringbuilder sb = new stringbuilder(); foreach (var user in users) { sb.append( "'" ); sb.append(user.id); sb.append( "'," ); } sb.length = sb.length - 1; return sb.tostring(); } |
對于這種情況有2中優化的方案
對于3.5以上可以直接使用linq輔助,這種方案代碼少,但是性能相對差一些
1
2
3
4
|
public string joinids(list<user> users) { return "'" + string .join( "','" , users.select(it => it.id)) + "'" ; } |
對于非3.5或對性能要求極高的場合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public string joinids(list<user> users) { var ee = users.getenumerator(); stringbuilder sb = new stringbuilder(); if (ee.movenext()) { sb.append( "'" ); sb.append(ee.current.id); sb.append( "'" ); while (ee.movenext()) { sb.append( ",'" ); sb.append(ee.current.id); sb.append( "'" ); } } return sb.tostring(); } |
bool類型的判斷返回
這種現象常見于新手程序員中
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//寫法1 if (state == 1) { return true ; } else { return false ; } //寫法2 return state == 1 ? true : false ; //優化后 return state == 1; |
類型的判斷
一般類型的判斷有2種形式
1,這種屬于代碼比較好寫,但是性能比較低, 原因就是gettype()的時候消耗了很多時間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type type = obj.gettype(); switch (type.name) { case "int32" : break ; case "string" : break ; case "boolean" : break ; case "datetime" : break ; ... ... default : break ; } |
2,這種屬性寫代碼麻煩,但是性能很高的類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
if (obj is string ) { } else if (obj is int ) { } else if (obj is datetime) { } ... ... else { } |
其實有個中間之道,既可以保證性能又可以比較好寫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
iconvertible conv = obj as iconvertible; if (conv != null ) { switch (conv.gettypecode()) { case typecode.boolean: break ; case typecode. byte : break ; case typecode. char : break ; case typecode.dbnull: break ; case typecode.datetime: break ; case typecode. decimal : break ; case typecode. double : break ; case typecode.empty: break ; case typecode.int16: break ; case typecode.int32: break ; ... ... default : break ; } } else { //處理其他類型 } |
大部分情況下 這個是可以用的 如果你自己有個類型實現了iconvertible,然后返回typecode.int32 就不再這個討論范圍之內了
使用枚舉作為索引
下面這個是一個真實的例子,為了突出重點,做了部分修改,刪除了多余的分支,源代碼中不只4個
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
enum templatecode { none = 0, head = 1, menu = 2, foot = 3, welcome = 4, } public string gethtml(templatecode tc) { switch (tc) { case templatecode.head: return gethead(); case templatecode.menu: return getmenu(); case templatecode.foot: return getfoot(); case templatecode.welcome: return getwelcome(); default : throw new argumentoutofrangeexception( "tc" ); } } |
優化后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
readonly static func< string >[] gettemplate = inittemplatefunction(); private static func< string >[] inittemplatefunction() { var arr = new func< string >[5]; arr[1] = gethead; arr[2] = getmenu; arr[3] = getfoot; arr[4] = getwelcome; return arr; } public string gethtml(templatecode tc) { var index = ( int )tc; if (index >= 1 && index <= 4) { return gettemplate[index](); } throw new argumentoutofrangeexception( "tc" ); } |
不過有的時候,枚舉不一定都是連續的數字,那么也可以使用dictionary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
readonly static dictionary<templatecode, func< string >> templatedict = inittemplatefunction(); private static dictionary<templatecode, func< string >> inittemplatefunction() { var ditc = new dictionary<templatecode, func< string >>(); ditc.add(templatecode.head, gethead); ditc.add(templatecode.menu, getmenu); ditc.add(templatecode.foot, getfoot); ditc.add(templatecode.welcome, getwelcome); return ditc; } public string gethtml(templatecode tc) { func< string > func; if (templatedict.trygetvalue(tc, out func)) { return func(); } throw new argumentoutofrangeexception( "tc" ); } |
這種優化在分支比較多的時候很好用,少的時候作用有限
字符類型char,分支判斷時的處理技巧
這部分內容比較復雜,而且適用范圍有限,如果平時用不到的就可以忽略了
在處理字符串對象的時候,有時會需要判斷char的值然后做進一步的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public string show( char c) { if (c >= '0' && c <= '9' ) { return "數字" ; } else if (c >= 'a' && c <= 'z' ) { return "小寫字母" ; } else if (c >= 'a' && c <= 'z' ) { return "大寫字母" ; } else if (c == '/' || c == '\\' || c == '|' || c == '$' || c == '#' || c == '+' || c == '%' || c == '&' || c == '-' || c == '^' || c == '*' || c == '=' ) { return "特殊符號" ; } else if (c == ',' || c == '.' || c == '!' || c == ':' || c == ';' || c == '?' || c == '"' || c == '\'' ) { return "標點符號" ; } else { return "其他" ; } } |
這里有一種空間換時間的優化方式, 雖說是空間換時間,但是實際浪費的空間不會很多,因為char最多只有65536長度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
readonly static byte [] charmap = initcharmap(); private static byte [] initcharmap() { var arr = new byte [ char .maxvalue]; for ( char i = '0' ; i <= '9' ; i++) { arr[i] = 1; } for ( char i = 'a' ; i <= 'z' ; i++) { arr[i] = 2; } for ( char i = '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; return arr; } public string show( char c) { switch (charmap[c]) { case 0: return "其他" ; case 1: return "數字" ; case 2: return "小寫字母" ; case 3: return "大寫字母" ; case 4: return "特殊符號" ; case 5: return "標點符號" ; default : return "其他" ; } } |
原先僅特殊符號一部分就需要判斷12次,修改過后只判斷一次就可以得到結果了
這方面的栗子在我的json組件()(文章,,3)中也有使用
摘取部分blqw.json的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
/// <summary> /// <para>包含1: 可以為頭的字符</para> /// <para>包含2: 可以為單詞的字符</para> /// <para>包含4: 可以為數字的字符</para> /// <para>等于8: 空白字符</para> /// <para>包含16:轉義字符</para> /// <para></para> /// </summary> private readonly static byte [] _wordchars = new byte [ char .maxvalue]; private readonly static sbyte [] _unicodeflags = new sbyte [123]; private readonly static sbyte [, ,] _datetimewords; static unsafejsonreader() { for ( int i = 0; i < 123; i++) { _unicodeflags[i] = -1; } _wordchars[ '-' ] = 1 | 4; _wordchars[ '+' ] = 1 | 4; _wordchars[ '$' ] = 1 | 2; _wordchars[ '_' ] = 1 | 2; for ( char c = 'a' ; c <= 'z' ; c++) { _wordchars[c] = 1 | 2; _unicodeflags[c] = ( sbyte )(c - 'a' + 10); } for ( char c = 'a' ; c <= 'z' ; c++) { _wordchars[c] = 1 | 2; _unicodeflags[c] = ( sbyte )(c - 'a' + 10); } _wordchars[ '.' ] = 1 | 2 | 4; for ( char c = '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 = new sbyte [23, 21, 25]; for ( sbyte i = 0; i < a.length; i++) { var d = a[i]; _datetimewords[d[0] - 97, d[1] - 97, d[2] - 97] = ( sbyte )(i + 1); } for ( sbyte i = 0; i < b.length; i++) { var d = 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的代碼 |
結束了…還要后續嗎?…貌似我又要懶一段時間
我寫的文章,除了純代碼,其他的都是想表達一種思想,一種解決方案.希望各位看官不要局限于文章中的現成的代碼,要多關注整個文章的主題思路,謝謝!
原文鏈接:http://www.cnblogs.com/taiyonghai/p/5695666.html