向Dictionary<dynamic, object>添加string/Guid对象存在性能问题的原因
动态绑定的额外开销
Dictionary<TKey, TValue>执行Add操作时,需要先后调用TKey的GetHashCode()计算哈希值、调用Equals()校验键是否重复。当你把TKey指定为dynamic时,这两个方法的调用不会在编译期确定,每次执行都要走DLR(动态语言运行时)的类型解析、方法绑定流程;尤其是你的键类型会在string和Guid之间随机切换,DLR无法对调用站点做有效缓存,千万次调用的解析开销会被大幅放大。
而List<dynamic>的Add操作只需要把元素追加到内部数组,不需要调用任何和元素类型相关的业务方法,动态绑定的开销极低,也不需要做额外的键校验,所以速度会快很多。
值类型装箱开销
Guid是值类型,作为dynamic参数传递时会触发装箱操作,你测试的1000万次调用里,约有一半操作需要对Guid做装箱,这部分时间和内存开销也会被累加。
字典的固有结构开销
Dictionary本身需要维护哈希表结构(包括桶数组、entry结构体数组),还要处理哈希冲突、扩容时的重哈希操作,这些固有开销本身就远高于只需要维护线性数组的List,这也是两者性能差距明显的基础原因。
如果你既不想用Dictionary<string, object>带来额外的内存开销,又要降低动态键的性能损失,可以尝试把字典的键类型改为object而非dynamic:
var dictionary = new Dictionary<object, object>();
这种方案下GetHashCode和Equals的调用会走编译期确定的虚方法调用,完全省去DLR的动态绑定开销,同时内存占用和Dictionary<dynamic, object>完全一致,性能可以提升数倍。
内容的提问来源于stack exchange,提问作者Denis P.

