不知道何時開始,很多程式員喜歡用ToLower,ToUpper去實現忽略大小寫模式的字元串相等性比較,有可能這個習慣是從別的語言引進的,大膽猜測下是JS,為了不引起爭論,我指的JS是技師的意思~ 一:背景 1. 講故事 在我們一個訂單聚合系統中,每一筆訂單都會標註來源,比如JD,Taobao,Eta ...
不知道何時開始,很多程式員喜歡用ToLower,ToUpper去實現忽略大小寫模式的字元串相等性比較,有可能這個習慣是從別的語言引進的,大膽猜測下是JS,為了不引起爭論,我指的JS是技師的意思~
一:背景
1. 講故事
在我們一個訂單聚合系統中,每一筆訂單都會標註來源,比如JD,Taobao,Etao,Shopex 等等一些渠道,UI上也提供高級配置輸入自定義的訂單來源,後來客戶反饋輸入xxx查詢不出訂單,這裡就拿shopex為例,用戶用小寫的shopex查詢,但系統中標註的是首字母大寫的Shopex,所以自然無法匹配,為瞭解決這個問題開發小哥就統一轉成大寫做比對,用代碼表示如下:
var orderfrom = "shopex".ToUpper();
customerIDList = MemoryOrders.Where(i =>i.OrderFrom.ToUpper()==orderFrom)
.Select(i => i.CustomerId).ToList();
改完後就是這麼牛的上線了,乍一看也沒啥問題,結果一查詢明顯感覺比之前速度慢了好幾秒,乾脆多點幾下,好咯。。。在監控中發現CPU和memory突高突低,異常波動,這位小哥又在寫bug了,查了下代碼問他為什麼這麼寫,小哥說在js中就是這麼比較的~~~
2. string.Compare 改造
其實在C#中面對忽略大小寫形式的比較是有專門的方法,性能高而且還不費記憶體,它就是 string.Compare
,所以把上面代碼改成如下就可以了。
var orderfrom = "shopex";
customerIDList = MemoryOrders.Where(string.Compare(i.TradeFrom, tradefrom,
StringComparison.OrdinalIgnoreCase) == 0)
.Select(i => i.CustomerId).ToList();
這其中的 StringComparison.OrdinalIgnoreCase
枚舉就是用來忽略大小寫的,上線之後除了CPU還是有點波動,其他都沒有問題了。
二:為什麼ToLower,ToUpper會有如此大的影響
為了方便演示,我找了一篇英文小短文,然後通過查詢某一個單詞來演示ToUpper為啥對cpu和memory以及查詢性能都有如此大的影響,代碼如下:
public static void Main(string[] args)
{
var strList = "Hooray! It's snowing! It's time to make a snowman.James runs out. He makes a big pile of snow. He puts a big snowball on top. He adds a scarf and a hat. He adds an orange for the nose. He adds coal for the eyes and buttons.In the evening, James opens the door. What does he see? The snowman is moving! James invites him in. The snowman has never been inside a house. He says hello to the cat. He plays with paper towels.A moment later, the snowman takes James's hand and goes out.They go up, up, up into the air! They are flying! What a wonderful night!The next morning, James jumps out of bed. He runs to the door.He wants to thank the snowman. But he's gone.".Split(' ');
var query = "snowman".ToUpper();
for (int i = 0; i < strList.Length; i++)
{
var str = strList[i].ToUpper();
if (str == query)
Console.WriteLine(str);
}
Console.ReadLine();
}
1. 記憶體波動探究
既然記憶體有波動,說明記憶體里進了髒東西,學C#基礎知識的時候應該知道string是不可變的,一旦有修改就會生成新的string,那就是說ToUpper之後會出現新的string,為了用數據佐證,用windbg演示一下。
0:000> !dumpheap -type System.String -stat
Statistics:
MT Count TotalSize Class Name
00007ff8e7a9a120 1 24 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
00007ff8e7a99e98 1 80 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Globalization.CultureData, mscorlib]]
00007ff8e7a9a378 1 96 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Globalization.CultureData, mscorlib]][]
00007ff8e7a93200 19 2264 System.String[]
00007ff8e7a959c0 429 17894 System.String
Total 451 object
可以看到托管堆上有Count=429
個string對象,那這個429怎麼來的? 組成:短文128個,ToUpper後128個,系統預設165個,query字元串2個,不明字元串6個,最後就是128 +128 + 165 + 2 + 6=429
,然後隨便抽幾個看看。
!dumpheap -mt 00007ff8e7a959c0 > !DumpObj 000002244282a1f8
0:000> !DumpObj /d 0000017800008010
Name: System.String
MethodTable: 00007ff8e7a959c0
EEClass: 00007ff8e7a72ec0
Size: 38(0x26) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: HOUSE.
Fields:
MT Field Offset Type VT Attr Value Name
00007ff8e7a985a0 4000281 8 System.Int32 1 instance 6 m_stringLength
00007ff8e7a96838 4000282 c System.Char 1 instance 48 m_firstChar
00007ff8e7a959c0 4000286 d8 System.String 0 shared static Empty
>> Domain:Value 0000017878943bb0:NotInit <<
0:000> !DumpObj /d 0000017800008248
Name: System.String
MethodTable: 00007ff8e7a959c0
EEClass: 00007ff8e7a72ec0
Size: 40(0x28) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: SNOWMAN
Fields:
MT Field Offset Type VT Attr Value Name
00007ff8e7a985a0 4000281 8 System.Int32 1 instance 7 m_stringLength
00007ff8e7a96838 4000282 c System.Char 1 instance 53 m_firstChar
00007ff8e7a959c0 4000286 d8 System.String 0 shared static Empty
>> Domain:Value 0000017878943bb0:NotInit <<
查了兩個全是大寫的“HOUSE”,“SNOWMAN”,再回到我的場景有小百萬訂單,也就會在托管堆上生成小百萬個string,如果再點一次又會生成小百萬個,記憶體怎麼會不突增呢。。。
2.cpu和查詢時間探究
現在大家知道了堆上可能有幾百萬個string對象,這些對象的分配和釋放給cpu造成了不小的壓力,本身toUpper之後速度變慢,更慘的是還會造成gc顫抖式觸發,一顫抖所有的thread都會被暫停開啟回收,速度就更慢了。。。
三:string.Compare解析
再回過頭來看一下string.Compare為什麼這麼