跳到主要内容

寻找性能更优秀的动态 Getter 和 Setter 方案

反射获取 PropertyInfo 可以对对象的属性值进行读取或者写入,但是这样性能不好。所以,我们需要更快的方案。

方案说明

就是用表达式编译一个Action<TObj,TValue>作为 Setter,编译一个Func<TObj,TValue>作为 Getter。

然后把这些编译好的委托放在一个泛型类的静态字段中保存起来,需要使用的时候从这里面查找就可以了。

知识要点

  1. 使用表达式创建委托
  2. 泛型类的静态字段是每个闭合类型独立的,因此用于存储和类型相关的内容非常方便

实现代码

由于代码中混合的使用 Switch 作为字典的阴招,所以代码很长,此处不再罗列,仅给出链接:

基准测试


BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
Intel Xeon CPU E5-2678 v3 2.50GHz, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=5.0.100-rc.2.20479.15
[Host] : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
net461 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
net48 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
netcoreapp21 : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
netcoreapp31 : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
netcoreapp5 : .NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT


结论

  1. 使用委托明显比使用 PropertyInfo 要快,这个方案可以。
  2. Framework 真拉胯,Net 5 简直太强了。
  3. 如果属性是明确的,建议把字典中取出来的委托保存在自己的上下文,这可以明显的省去查找的消耗。

图表

从左往右分别是:直接读取属性、缓存委托、不缓存委托和使用 PropertyInfo。

Getter Setter

数据

Getter

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDRank
DirectlyStringnet461.NET 4.6.10.1636 ns0.0822 ns0.1126 ns0.1472 ns??2
DirectlyIntnet461.NET 4.6.10.0318 ns0.0348 ns0.0342 ns0.0217 ns??1
ReflectStringnet461.NET 4.6.1145.8375 ns2.2790 ns2.1317 ns145.6522 ns??7
ReflectIntnet461.NET 4.6.1172.5066 ns1.3206 ns1.1028 ns172.6804 ns??8
GetterStringnet461.NET 4.6.131.4379 ns0.6017 ns0.5334 ns31.6316 ns??4
GetterIntnet461.NET 4.6.133.0642 ns0.4940 ns0.4380 ns33.0557 ns??5
GetterObjectnet461.NET 4.6.133.9174 ns0.5587 ns0.5226 ns33.7326 ns??6
GetterCachednet461.NET 4.6.17.5878 ns0.1223 ns0.1144 ns7.5765 ns??3
DirectlyStringnet48.NET 4.80.0181 ns0.0353 ns0.0313 ns0.0043 ns??1
DirectlyIntnet48.NET 4.80.0050 ns0.0089 ns0.0079 ns0.0000 ns??1
ReflectStringnet48.NET 4.8143.8313 ns2.2501 ns2.1047 ns143.5568 ns??5
ReflectIntnet48.NET 4.8172.1714 ns1.9819 ns1.7569 ns172.3142 ns??6
GetterStringnet48.NET 4.831.5887 ns0.6310 ns0.5902 ns31.5385 ns??3
GetterIntnet48.NET 4.832.7140 ns0.3992 ns0.3734 ns32.7343 ns??4
GetterObjectnet48.NET 4.833.3063 ns0.2069 ns0.1834 ns33.3053 ns??4
GetterCachednet48.NET 4.87.5540 ns0.2201 ns0.1951 ns7.5069 ns??2
DirectlyStringnetcoreapp21.NET Core 2.10.0000 ns0.0000 ns0.0000 ns0.0000 ns??1
DirectlyIntnetcoreapp21.NET Core 2.10.0193 ns0.0111 ns0.0104 ns0.0177 ns??2
ReflectStringnetcoreapp21.NET Core 2.1110.4180 ns2.2159 ns1.8503 ns110.8038 ns??7
ReflectIntnetcoreapp21.NET Core 2.1138.9612 ns0.9694 ns0.8594 ns138.8217 ns??8
GetterStringnetcoreapp21.NET Core 2.116.8958 ns0.2384 ns0.2230 ns16.8103 ns??4
GetterIntnetcoreapp21.NET Core 2.119.4407 ns0.2041 ns0.1809 ns19.4539 ns??6
GetterObjectnetcoreapp21.NET Core 2.118.6922 ns0.2700 ns0.2255 ns18.6582 ns??5
GetterCachednetcoreapp21.NET Core 2.10.9299 ns0.0457 ns0.0427 ns0.9308 ns??3
DirectlyStringnetcoreapp31.NET Core 3.10.0000 ns0.0000 ns0.0000 ns0.0000 ns??1
DirectlyIntnetcoreapp31.NET Core 3.10.0693 ns0.0102 ns0.0091 ns0.0709 ns??2
ReflectStringnetcoreapp31.NET Core 3.198.6735 ns0.8335 ns0.7389 ns98.5319 ns??7
ReflectIntnetcoreapp31.NET Core 3.1130.6941 ns0.9332 ns0.8730 ns130.5376 ns??8
GetterStringnetcoreapp31.NET Core 3.114.8915 ns0.2025 ns0.1795 ns14.8911 ns??4
GetterIntnetcoreapp31.NET Core 3.116.2874 ns0.0789 ns0.0700 ns16.2753 ns??5
GetterObjectnetcoreapp31.NET Core 3.117.6202 ns0.1130 ns0.1057 ns17.6092 ns??6
GetterCachednetcoreapp31.NET Core 3.10.6351 ns0.0244 ns0.0217 ns0.6393 ns??3
DirectlyStringnetcoreapp5.NET Core 5.00.5098 ns0.0328 ns0.0291 ns0.5131 ns1.0000.002
DirectlyIntnetcoreapp5.NET Core 5.00.0000 ns0.0000 ns0.0000 ns0.0000 ns0.0000.001
ReflectStringnetcoreapp5.NET Core 5.088.8937 ns0.9697 ns0.8596 ns88.7457 ns174.8389.267
ReflectIntnetcoreapp5.NET Core 5.0123.4464 ns1.0582 ns0.9898 ns123.3193 ns242.99614.008
GetterStringnetcoreapp5.NET Core 5.07.6628 ns0.0931 ns0.0777 ns7.6703 ns15.0310.955
GetterIntnetcoreapp5.NET Core 5.06.6645 ns0.0825 ns0.0772 ns6.6497 ns13.0850.694
GetterObjectnetcoreapp5.NET Core 5.08.3090 ns0.1685 ns0.1576 ns8.2865 ns16.3440.836
GetterCachednetcoreapp5.NET Core 5.00.9791 ns0.0293 ns0.0245 ns0.9764 ns1.9200.133

Setter

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDRank
DirectlyStringnet461.NET 4.6.12.0161 ns0.0300 ns0.0266 ns2.0045 ns1.0000.002
DirectlyIntnet461.NET 4.6.10.0076 ns0.0094 ns0.0083 ns0.0081 ns0.0040.001
ReflectStringnet461.NET 4.6.1237.5006 ns4.5706 ns4.4890 ns236.5912 ns117.8713.405
ReflectIntnet461.NET 4.6.1249.3627 ns2.1717 ns2.0314 ns249.0283 ns123.6811.946
GetterStringnet461.NET 4.6.132.8621 ns0.2855 ns0.2229 ns32.9189 ns16.3350.224
GetterIntnet461.NET 4.6.133.6103 ns0.4245 ns0.3544 ns33.5499 ns16.6950.264
GetterObjectnet461.NET 4.6.133.2561 ns0.2966 ns0.2629 ns33.1795 ns16.4970.174
GetterCachednet461.NET 4.6.19.1805 ns0.0761 ns0.0674 ns9.1802 ns4.5540.083
DirectlyStringnet48.NET 4.81.9272 ns0.0298 ns0.0264 ns1.9245 ns1.0000.002
DirectlyIntnet48.NET 4.80.0000 ns0.0000 ns0.0000 ns0.0000 ns0.0000.001
ReflectStringnet48.NET 4.8237.7686 ns4.6597 ns5.3661 ns235.8445 ns123.9083.575
ReflectIntnet48.NET 4.8249.6291 ns4.5333 ns4.2404 ns249.5459 ns129.6893.136
GetterStringnet48.NET 4.832.2366 ns0.1941 ns0.1721 ns32.1780 ns16.7310.294
GetterIntnet48.NET 4.832.0081 ns0.1488 ns0.1162 ns32.0270 ns16.5720.234
GetterObjectnet48.NET 4.832.6413 ns0.1417 ns0.1183 ns32.6260 ns16.9070.244
GetterCachednet48.NET 4.89.2589 ns0.0928 ns0.0868 ns9.2564 ns4.7990.073
DirectlyStringnetcoreapp21.NET Core 2.12.4107 ns0.0507 ns0.0475 ns2.3936 ns1.0000.002
DirectlyIntnetcoreapp21.NET Core 2.10.0007 ns0.0028 ns0.0025 ns0.0000 ns0.0000.001
ReflectStringnetcoreapp21.NET Core 2.1203.6637 ns3.6109 ns3.3777 ns203.4793 ns84.5172.317
ReflectIntnetcoreapp21.NET Core 2.1213.8619 ns2.1882 ns1.9398 ns213.7367 ns88.7571.718
GetterStringnetcoreapp21.NET Core 2.119.5240 ns0.0811 ns0.0758 ns19.5149 ns8.1020.155
GetterIntnetcoreapp21.NET Core 2.118.8794 ns0.1193 ns0.1058 ns18.8837 ns7.8360.184
GetterObjectnetcoreapp21.NET Core 2.120.6765 ns0.2709 ns0.2115 ns20.6419 ns8.5840.146
GetterCachednetcoreapp21.NET Core 2.12.7606 ns0.0613 ns0.0512 ns2.7590 ns1.1480.023
DirectlyStringnetcoreapp31.NET Core 3.12.2625 ns0.0647 ns0.0605 ns2.2555 ns1.0000.002
DirectlyIntnetcoreapp31.NET Core 3.10.0072 ns0.0165 ns0.0146 ns0.0000 ns0.0030.011
ReflectStringnetcoreapp31.NET Core 3.1182.7306 ns3.3249 ns3.1101 ns182.3062 ns80.8041.997
ReflectIntnetcoreapp31.NET Core 3.1192.2510 ns2.3691 ns2.1002 ns191.4821 ns85.0612.888
GetterStringnetcoreapp31.NET Core 3.116.9918 ns0.2115 ns0.1979 ns16.9651 ns7.5160.255
GetterIntnetcoreapp31.NET Core 3.116.1168 ns0.3558 ns0.3654 ns15.9822 ns7.1380.194
GetterObjectnetcoreapp31.NET Core 3.119.3060 ns0.4173 ns0.5571 ns19.4856 ns8.4800.456
GetterCachednetcoreapp31.NET Core 3.12.9276 ns0.0156 ns0.0146 ns2.9313 ns1.2950.033
DirectlyStringnetcoreapp5.NET Core 5.02.2455 ns0.0084 ns0.0078 ns2.2460 ns1.0000.002
DirectlyIntnetcoreapp5.NET Core 5.00.0000 ns0.0000 ns0.0000 ns0.0000 ns0.0000.001
ReflectStringnetcoreapp5.NET Core 5.0162.8780 ns0.7135 ns0.6674 ns162.8741 ns72.5380.457
ReflectIntnetcoreapp5.NET Core 5.0171.1380 ns0.4173 ns0.3699 ns171.1414 ns76.2170.318
GetterStringnetcoreapp5.NET Core 5.08.6244 ns0.1891 ns0.1769 ns8.5469 ns3.8410.085
GetterIntnetcoreapp5.NET Core 5.06.5511 ns0.0347 ns0.0325 ns6.5634 ns2.9170.024
GetterObjectnetcoreapp5.NET Core 5.09.0732 ns0.0306 ns0.0272 ns9.0735 ns4.0410.026
GetterCachednetcoreapp5.NET Core 5.02.8223 ns0.0728 ns0.0681 ns2.8190 ns1.2570.033

总结

使用表达式创建委托来取代 PropertyInfo 读取和写入属性效果很好。

开发者也可以直接引用 Newbe.ObjectVisitor 包来使用已经封装好的 ValueGetter 和 ValueSetter。

我只是知识的搬运工


欢迎关注的我微信公众号,第一时间获取我的最新文章。