尋找性能更優秀的不可變小字典

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dictionary 是一個很常用的鍵值對管理數據結構。但是在性能要求嚴苛的情況下,字典的查找速度並不高。所以,我們需要更快的方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"需求說明"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏,我們需要一個 PropertyInfo 和委託對應的映射關係,這樣我們就可以存儲《"},{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Better-Performance-Getter-Setter/","title":null},"content":[{"type":"text","text":"尋找性能更優秀的動態 Getter 和 Setter 方案"}]},{"type":"text","text":"》提到的委託。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,這個字典有這些特點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"這個字典一旦創建就不需要修改。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"字典項目並不多,因爲通常一個 class 不會有太多屬性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"方案說明"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方案 1,Switch 表達式法。使用表達式生成一個包含 switch case 語句的委託。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方案 2,數組跳錶。我們知道,switch case 之所以比連續的 if else 要快的原因是因爲其生成的 IL 中包含一個跳錶算法。因此,如果我們有辦法使用連續數字作爲下標,以及一個數組。就可以在 C# 中自己實現跳錶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"知識要點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用表達式創建委託"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"PropertyInfo 有一個 int MetadataToken 屬性,根據目前的觀察,可以知道在一個類型中的屬性其 MetadataToken 似乎是連續的,因此可以取模後作爲跳錶的 key。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"所謂的跳錶,可以簡單理解爲,使用數組的下標來定位數組中的特定元素。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"實現代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏,我們直接給出基準測試中使用的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Directly 直接讀,沒有任何查找"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ArrayIndex 數組跳錶"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SwitchExp 表達式生成 Switch 方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dic 傳統字典方案"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"csharp"},"content":[{"type":"text","text":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing BenchmarkDotNet.Attributes;\n\nnamespace Newbe.ObjectVisitor.BenchmarkTest\n{\n [Config(typeof(Config))]\n public class FuncSearchTest\n {\n private Func[] _target;\n private readonly Yueluo _yueluo;\n private readonly Func _func;\n private readonly PropertyInfo _nameP;\n private readonly Func> _switcher;\n private readonly Dictionary> _dic;\n\n public FuncSearchTest()\n {\n _yueluo = Yueluo.Create();\n var propertyInfos = typeof(Yueluo).GetProperties().ToArray();\n\n CreateCacheArrayD(propertyInfos);\n\n _switcher = ValueGetter.CreateGetter(propertyInfos,\n info => Expression.SwitchCase(Expression.Constant(CreateFunc(info)), Expression.Constant(info)));\n _dic = propertyInfos.ToDictionary(x => x, CreateFunc);\n\n _nameP = typeof(Yueluo).GetProperty(nameof(Yueluo.Name));\n _func = x => x.Name;\n }\n\n private void CreateCacheArrayD(IReadOnlyCollection propertyInfos)\n {\n _target = new Func[propertyInfos.Count];\n foreach (var info in propertyInfos)\n {\n var key = GetKey(info);\n var index = key % propertyInfos.Count;\n _target[index] = CreateFunc(info);\n }\n }\n\n private static Func CreateFunc(PropertyInfo info)\n {\n var pExp = Expression.Parameter(typeof(Yueluo), \"x\");\n var bodyExp = Expression.Property(pExp, info);\n var finalExp =\n Expression.Lambda>(Expression.Convert(bodyExp, typeof(object)), pExp);\n return finalExp.Compile();\n }\n\n private static int GetKey(MemberInfo info)\n {\n var token = info.MetadataToken;\n return token;\n }\n\n [Benchmark(Baseline = true)]\n public string Directly() => _func(_yueluo);\n\n [Benchmark]\n public string ArrayIndex() => (string) _target[_nameP.MetadataToken % _target.Length](_yueluo);\n\n [Benchmark]\n public string SwitchExp() => (string) _switcher(_nameP)(_yueluo);\n\n [Benchmark]\n public string Dic() => (string) _dic[_nameP](_yueluo);\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基準測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)\nIntel Xeon CPU E5-2678 v3 2.50GHz, 1 CPU, 24 logical and 12 physical cores\n.NET Core SDK=5.0.100-rc.2.20479.15\n [Host] : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT\n net461 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT\n net48 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT\n netcoreapp21 : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT\n netcoreapp31 : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT\n netcoreapp5 : .NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"結論"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"字典真拉胯。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Framework 真拉胯。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Net 5 簡直太強了。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"數組跳錶是非直接方案中最快的。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"圖表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9e/9e02126fc21282fe892c07571757fc56.png","alt":"FuncSearch","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不論是數組跳錶還是表達式 Switch 方案都可以解決這個問題,而且都要比使用字典要快。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是這裏有一個問題,就是目前作者還沒有找到任何有關 MetadataToken 是否真的具備同 class 連續的性質。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此建議還是使用 Switch 方案實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"我只是知識的搬運工"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://tyrrrz.me/blog/expression-trees","title":null},"content":[{"type":"text","text":"Working with Expression Trees in C#"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"發佈說明"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Release-Note-0-2-10/","title":null},"content":[{"type":"text","text":"Newbe.ObjectVisitor 0.2.10 發佈,更花裏胡哨"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Release-Note-0-1-4/","title":null},"content":[{"type":"text","text":"Newbe.ObjectVisitor 0.1.4 發佈,初始版本"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用樣例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Example-1/","title":null},"content":[{"type":"text","text":"Newbe.ObjectVisitor 樣例 1"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"番外分享"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Better-Performance-Getter-Setter/","title":null},"content":[{"type":"text","text":"尋找性能更優秀的動態 Getter 和 Setter 方案"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Better-Performance-Small-Map/","title":null},"content":[{"type":"text","text":"尋找性能更優秀的不可變小字典"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GitHub 項目地址:"},{"type":"link","attrs":{"href":"https://github.com/newbe36524/Newbe.ObjectVisitor","title":null},"content":[{"type":"text","text":"https://github.com/newbe36524/Newbe.ObjectVisitor"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gitee 項目地址:"},{"type":"link","attrs":{"href":"https://gitee.com/yks/Newbe.ObjectVisitor","title":null},"content":[{"type":"text","text":"https://gitee.com/yks/Newbe.ObjectVisitor"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文作者: "},{"type":"text","text":"newbe36524"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文鏈接:"},{"type":"text","text":" "},{"type":"link","attrs":{"href":"https://www.newbe.pro/Newbe.ObjectVisitor/Better-Performance-Small-Map/","title":null},"content":[{"type":"text","text":"https://www.newbe.pro/Newbe.ObjectVisitor/Better-Performance-Small-Map/"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"版權聲明: "},{"type":"text","text":"本博客所有文章除特別聲明外,均採用 "},{"type":"link","attrs":{"href":"https://creativecommons.org/licenses/by-nc-sa/4.0/","title":null},"content":[{"type":"text","text":"BY-NC-SA"}]},{"type":"text","text":" 許可協議。轉載請註明出處!"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章