通过对 .Net6 和 .Net7 反射性能的简要测试,为性能优化提供依据。本文测试了以下项目:
名称 | 内容 |
---|---|
ReadPub |
读取公开属性 |
ReadPriByReflection |
通过反射读取私有属性 |
ReadPriByCachedReflection |
通过缓存的反射读取私有属性 |
ReadPriByDelegate |
通过委托读取私有属性 |
ReadPriByCachedDelegate |
通过缓存的委托读取私有属性 |
ReadPriByExpression |
通过表达式树读取私有属性 |
ReadPriByCachedExpression |
通过缓存的表达式树读取私有属性 |
源代码:DotVast.Benchmark.Reflection/ReadProperty
测试代码片段
C#
using System.Linq.Expressions;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
namespace DotVast.Benchmark.Reflection;
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<ReadProperty>();
}
}
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net70, baseline: true)]
[MemoryDiagnoser]
public class ReadProperty
{
private readonly User _user = new();
[Benchmark]
public string ReadPub()
{
return _user.Pub;
}
///////////////////////////////////////////////////////////////////////////////////
/// Reflection
[Benchmark]
public string ReadPriByReflection()
{
var propertyInfo = typeof(User).GetProperty("Pri", BindingFlags.Instance | BindingFlags.NonPublic)!;
return (string)propertyInfo.GetValue(_user)!;
}
static readonly PropertyInfo _propInfo = typeof(User).GetProperty("Pri", BindingFlags.Instance | BindingFlags.NonPublic)!;
[Benchmark]
public string ReadPriByCachedReflection()
{
return (string)_propInfo.GetValue(_user)!;
}
///////////////////////////////////////////////////////////////////////////////////
/// Delegate
[Benchmark]
public string ReadPriByDelegate()
{
var propertyInfo = typeof(User).GetProperty("Pri", BindingFlags.Instance | BindingFlags.NonPublic)!;
var delFunc = (Func<User, string>)Delegate.CreateDelegate(typeof(Func<User, string>), propertyInfo.GetMethod!);
return delFunc(_user);
}
static readonly Func<User, string> _delFunc = (Func<User, string>)Delegate.CreateDelegate(typeof(Func<User, string>), _propInfo.GetMethod!);
[Benchmark]
public string ReadPriByCachedDelegate()
{
return _delFunc(_user);
}
///////////////////////////////////////////////////////////////////////////////////
/// Expression
[Benchmark]
public string ReadPriByExpression()
{
var propertyInfo = typeof(User).GetProperty("Pri", BindingFlags.Instance | BindingFlags.NonPublic)!;
var parameter = Expression.Parameter(typeof(User));
var propertyExpression = Expression.Property(parameter, propertyInfo);
var expFunc = Expression.Lambda<Func<User, string>>(propertyExpression, parameter).Compile();
return expFunc(_user);
}
static readonly ParameterExpression _parameter = Expression.Parameter(typeof(User));
static readonly MemberExpression _propExpression = Expression.Property(_parameter, _propInfo);
static readonly Func<User, string> _expFunc = Expression.Lambda<Func<User, string>>(_propExpression, _parameter).Compile();
[Benchmark]
public string ReadPriByCachedExpression()
{
return _expFunc(_user);
}
}
public sealed class User
{
public string Pub => "Public";
private string Pri => "Private";
}
结果
BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19045.2251)
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100
[Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|-------------------------- |--------- |--------- |---------------:|------------:|------------:|------:|-------:|-------:|----------:|------------:|
| ReadPub | .NET 6.0 | .NET 6.0 | 0.4791 ns | 0.0086 ns | 0.0080 ns | 0.34 | - | - | - | NA |
| ReadPub | .NET 7.0 | .NET 7.0 | 1.3930 ns | 0.0069 ns | 0.0061 ns | 1.00 | - | - | - | NA |
| | | | | | | | | | | |
| ReadPriByReflection | .NET 6.0 | .NET 6.0 | 94.9653 ns | 0.4266 ns | 0.3782 ns | 2.13 | - | - | - | NA |
| ReadPriByReflection | .NET 7.0 | .NET 7.0 | 44.5251 ns | 0.0936 ns | 0.0876 ns | 1.00 | - | - | - | NA |
| | | | | | | | | | | |
| ReadPriByCachedReflection | .NET 6.0 | .NET 6.0 | 61.6544 ns | 0.1326 ns | 0.1240 ns | 4.21 | - | - | - | NA |
| ReadPriByCachedReflection | .NET 7.0 | .NET 7.0 | 14.6585 ns | 0.0218 ns | 0.0204 ns | 1.00 | - | - | - | NA |
| | | | | | | | | | | |
| ReadPriByDelegate | .NET 6.0 | .NET 6.0 | 452.9171 ns | 2.9943 ns | 2.8008 ns | 1.07 | 0.0200 | - | 64 B | 1.00 |
| ReadPriByDelegate | .NET 7.0 | .NET 7.0 | 422.1861 ns | 0.7520 ns | 0.6666 ns | 1.00 | 0.0200 | - | 64 B | 1.00 |
| | | | | | | | | | | |
| ReadPriByCachedDelegate | .NET 6.0 | .NET 6.0 | 1.8512 ns | 0.0082 ns | 0.0077 ns | 0.61 | - | - | - | NA |
| ReadPriByCachedDelegate | .NET 7.0 | .NET 7.0 | 3.0252 ns | 0.0102 ns | 0.0090 ns | 1.00 | - | - | - | NA |
| | | | | | | | | |
| ReadPriByExpression | .NET 6.0 | .NET 6.0 | 47,972.7743 ns | 273.5695 ns | 255.8971 ns | 0.84 | 1.4038 | 0.6714 | 4551 B | 0.99 |
| ReadPriByExpression | .NET 7.0 | .NET 7.0 | 57,030.6131 ns | 192.3719 ns | 170.5327 ns | 1.00 | 1.4648 | 1.0376 | 4583 B | 1.00 |
| | | | | | | | | | | |
| ReadPriByCachedExpression | .NET 6.0 | .NET 6.0 | 1.1444 ns | 0.0163 ns | 0.0136 ns | 0.62 | - | - | - | NA |
| ReadPriByCachedExpression | .NET 7.0 | .NET 7.0 | 1.8552 ns | 0.0073 ns | 0.0068 ns | 1.00 | - | - | - | NA |
结果总结如下:
- 通过表达式树访问属性,除直接访问属性外,其性能最佳,但是开销较大,且编写繁琐。
- 通过反射访问属性,在 .Net7 中优化显著,同时利用缓存也可以获得较好的性能。
- 通过委托访问属性,其性能和开销较为均衡,适合大多数性能优化使用。