已编译的 lambda 表达式用作 属性 getter 和 setter:错误的基准测试方法或错误的 lambda 表达式构造?
Compiled lambda expressions used as property getters and setters: wrong benchmarking method or wrong lambda expression construction?
您好,我正在尝试使用(缓存的)已编译的 lambda 表达式进行属性访问,与使用 PropertyInfo.GetValue()/SetValue() 方法调用相比,我得到的结果肯定要好得多(即更快)。然而,我觉得它离“原生”属性速度还有很远的距离。是否是基准测试方法使结果与其他方法如此不同?
- https://www.codeproject.com/Articles/584720/ExpressionplusbasedplusPropertyplusGettersplusandp
- http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision
- Compiled C# Lambda Expressions Performance
下面是我在 运行 下面的代码之后得到的结果:
Native: Elapsed = 00:00:00.0995876 (99.5876 ms); Step = 1.992E-005 ms
Lambda Expression: Elapsed = 00:00:00.5369273 (536.9273 ms); Step = 1.074E-004 ms
Property Info: Elapsed = 00:00:01.9187312 (1918.7312 ms); Step = 3.837E-004 ms
1.000 < 5.392 < 19.267
老实说,我觉得根据其他基准,编译的 lambda 表达式应该比使用常规属性慢两倍,而不是慢 5 到 6 倍。
有什么想法吗?基准测试方法?编译后的lambda表达式的计算方式?
public static class Program
{
public static void Main(params string[] args)
{
var stepCount = 5000000UL;
var dummy = new Dummy();
const string propertyName = "Soother";
const bool propertyValue = true;
var propertyInfo = typeof(Dummy).GetProperty(propertyName);
var nativeBenchmark = Benchmark.Run("Native", stepCount, () => dummy.Soother = propertyValue);
var lambdaExpressionBenchmark = Benchmark.Run("Lambda Expression", stepCount, () => dummy.Set(propertyName, propertyValue));
var propertyInfoBenchmark = Benchmark.Run("Property Info", stepCount, () => propertyInfo.SetValue(dummy, propertyValue, null));
var benchmarkReports = new[] { nativeBenchmark, lambdaExpressionBenchmark, propertyInfoBenchmark }.OrderBy(item => item.ElapsedMilliseconds);
benchmarkReports.Join(Environment.NewLine).WriteLineToConsole();
var fastest = benchmarkReports.First().ElapsedMilliseconds;
benchmarkReports.Select(report => (report.ElapsedMilliseconds / fastest).ToString("0.000")).Join(" < ").WriteLineToConsole();
Console.ReadKey();
}
}
public class Dummy
{
public bool? Soother { get; set; } = true;
}
public class BenchMarkReport
{
#region Fields & Properties
public string Name { get; }
public TimeSpan ElapsedTime { get; }
public double ElapsedMilliseconds
{
get
{
return ElapsedTime.TotalMilliseconds;
}
}
public ulong StepCount { get; }
public double StepElapsedMilliseconds
{
get
{
return ElapsedMilliseconds / StepCount;
}
}
#endregion
#region Constructors
internal BenchMarkReport(string name, TimeSpan elapsedTime, ulong stepCount)
{
Name = name;
ElapsedTime = elapsedTime;
StepCount = stepCount;
}
#endregion
#region Methods
public override string ToString()
{
return $"{Name}: Elapsed = {ElapsedTime} ({ElapsedMilliseconds} ms); Step = {StepElapsedMilliseconds:0.###E+000} ms";
}
#endregion
}
public class Benchmark
{
#region Fields & Properties
private readonly Action _stepAction;
public string Name { get; }
public ulong StepCount { get; }
public Benchmark(string name, ulong stepCount, Action stepAction)
{
Name = name;
StepCount = stepCount;
_stepAction = stepAction;
}
#endregion
#region Constructors
#endregion
#region Methods
public static BenchMarkReport Run(string name, ulong stepCount, Action stepAction)
{
var benchmark = new Benchmark(name, stepCount, stepAction);
var benchmarkReport = benchmark.Run();
return benchmarkReport;
}
public BenchMarkReport Run()
{
return Run(StepCount);
}
public BenchMarkReport Run(ulong stepCountOverride)
{
var stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < StepCount; i++)
{
_stepAction();
}
stopwatch.Stop();
var benchmarkReport = new BenchMarkReport(Name, stopwatch.Elapsed, stepCountOverride);
return benchmarkReport;
}
#endregion
}
public static class ObjectExtensions
{
public static void WriteToConsole<TInstance>(this TInstance instance)
{
Console.Write(instance);
}
public static void WriteLineToConsole<TInstance>(this TInstance instance)
{
Console.WriteLine(instance);
}
// Goodies: add name inference from property lambda expression
// e.g. "instance => instance.PropertyName" redirected using "PropertyName"
public static TProperty Get<TInstance, TProperty>(this TInstance instance, string propertyName)
{
return FastPropertyRepository<TInstance, TProperty>.GetGetter(propertyName)(instance);
}
public static void Set<TInstance, TProperty>(this TInstance instance, string propertyName, TProperty propertyValue)
{
FastPropertyRepository<TInstance, TProperty>.GetSetter(propertyName)(instance, propertyValue);
}
}
public static class EnumerableExtensions
{
public static string Join<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source);
}
}
internal static class FastPropertyRepository<TInstance, TProperty>
{
private static readonly IDictionary<string, Action<TInstance, TProperty>> Setters;
private static readonly IDictionary<string, Func<TInstance, TProperty>> Getters;
static FastPropertyRepository()
{
Getters = new ConcurrentDictionary<string, Func<TInstance, TProperty>>();
Setters = new ConcurrentDictionary<string, Action<TInstance, TProperty>>();
}
public static Func<TInstance, TProperty> GetGetter(string propertyName)
{
Func<TInstance, TProperty> getter;
if (!Getters.TryGetValue(propertyName, out getter))
{
getter = FastPropertyFactory.GeneratePropertyGetter<TInstance, TProperty>(propertyName);
Getters[propertyName] = getter;
}
return getter;
}
public static Action<TInstance, TProperty> GetSetter(string propertyName)
{
Action<TInstance, TProperty> setter;
if (!Setters.TryGetValue(propertyName, out setter))
{
setter = FastPropertyFactory.GeneratePropertySetter<TInstance, TProperty>(propertyName);
Setters[propertyName] = setter;
}
return setter;
}
}
internal static class FastPropertyFactory
{
public static Func<TInstance, TProperty> GeneratePropertyGetter<TInstance, TProperty>(string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(TInstance), "value");
var propertyValueExpression = Expression.Property(parameterExpression, propertyName);
var expression = propertyValueExpression.Type == typeof(TProperty) ? propertyValueExpression : (Expression)Expression.Convert(propertyValueExpression, typeof(TProperty));
var propertyGetter = Expression.Lambda<Func<TInstance, TProperty>>(expression, parameterExpression).Compile();
return propertyGetter;
}
public static Action<TInstance, TProperty> GeneratePropertySetter<TInstance, TProperty>(string propertyName)
{
var instanceParameterExpression = Expression.Parameter(typeof(TInstance));
var parameterExpression = Expression.Parameter(typeof(TProperty), propertyName);
var propertyValueExpression = Expression.Property(instanceParameterExpression, propertyName);
var conversionExpression = propertyValueExpression.Type == typeof(TProperty) ? parameterExpression : (Expression)Expression.Convert(parameterExpression, propertyValueExpression.Type);
var propertySetter = Expression.Lambda<Action<TInstance, TProperty>>(Expression.Assign(propertyValueExpression, conversionExpression), instanceParameterExpression, parameterExpression).Compile();
return propertySetter;
}
}
我将您的工作简化为更小的方法。它提高了整体性能,但也拉大了差距。
Native : 00:00:00.0029713 ( 2.9713ms) 5.9426E-07
Lambda Expression : 00:00:00.4356385 ( 435.6385ms) 8.71277E-05
Property Info : 00:00:01.3436626 ( 1343.6626ms) 0.00026873252
以下是使用的方法
public class Dummy
{
public bool? Soother { get; set; } = true;
}
public class Lab
{
Dummy _dummy = new Dummy();
ulong _iterations = 5000000UL;
const bool _propertyValue = true;
const string _propertyName = "Soother";
public BenchmarkReport RunNative()
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
_dummy.Soother = _propertyValue;
}
stopwatch.Stop();
return new BenchmarkReport("Native", stopwatch.Elapsed, _iterations);
}
public BenchmarkReport RunLambdaExpression()
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
_dummy.Set(_propertyName, _propertyValue);
}
stopwatch.Stop();
return new BenchmarkReport("Lambda Expression", stopwatch.Elapsed, _iterations);
}
public BenchmarkReport RunPropertyInfo()
{
PropertyInfo propertyInfo = typeof(Dummy).GetProperty(_propertyName);
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
propertyInfo.SetValue(_dummy, _propertyValue);
}
stopwatch.Stop();
return new BenchmarkReport("Property Info", stopwatch.Elapsed, _iterations);
}
}
public class BenchmarkReport
{
public string Name { get; set; }
public TimeSpan ElapsedTime { get; set; }
public ulong Iterations { get; set; }
public BenchmarkReport(string name, TimeSpan elapsedTime, ulong iterations)
{
Name = name;
ElapsedTime = elapsedTime;
Iterations = iterations;
}
}
和程序运行它
public static class Program
{
public static void Main(params string[] args)
{
Lab lab = new Lab();
List<BenchmarkReport> benchmarkReports = new List<BenchmarkReport>()
{
lab.RunNative(),
lab.RunLambdaExpression(),
lab.RunPropertyInfo()
};
foreach (var report in benchmarkReports)
{
Console.WriteLine("{0}: {1} ({2}ms) {3}",
report.Name.PadRight(20),
report.ElapsedTime,
report.ElapsedTime.TotalMilliseconds.ToString().PadLeft(10),
(double)report.ElapsedTime.TotalMilliseconds / report.Iterations);
}
Console.ReadKey();
}
}
正如我在问题下方的评论交流中所述,问题在于实现基准的方式。实际上底线 performance-wise 都是关于帮助器和扩展方法的,尤其是字典查找操作。
与执行已编译 lambda 本身的结果相比,我显然低估了字典查找操作所需的时间(即使它是常量 O(1)),也就是说它仍然慢了 lo0o0ot (是的,这就是为什么我最初在编译的 lambda 表演之后那么多的原因)。
如问题评论中所述,是的,我可以缓存结果,在这种情况下,我获得的性能非常接近本机 属性 访问。扩展方法真的很方便,但是它确实隐藏了一个非常重要的细节,因此我说 bench-marking 的方式不太好。
下面是完整代码,阐明了我的问题的一些问题:
public static class Program
{
public static void Main(params string[] args)
{
var stepCount = 5000000UL;
var dummy = new Dummy();
const string propertyName = "Soother";
const bool propertyValue = true;
var propertyInfo = typeof(Dummy).GetProperty(propertyName);
var lambdaExpression = FastPropertyFactory.GeneratePropertySetter<Dummy, bool>(propertyName);
var nativeBenchmark = Benchmark.Run("Native", stepCount, () => dummy.Soother = propertyValue);
var lambdaExpressionBenchmark = Benchmark.Run("Lambda Expression", stepCount, () => lambdaExpression(dummy, propertyValue));
var dictionaryLambdaExpressionBenchmark = Benchmark.Run("Dictionary Access + Lambda Expression", stepCount, () => dummy.Set(propertyName, propertyValue));
var propertyInfoBenchmark = Benchmark.Run("Property Info", stepCount, () => propertyInfo.SetValue(dummy, propertyValue, null));
var benchmarkReports = new[]
{
nativeBenchmark,
lambdaExpressionBenchmark,
dictionaryLambdaExpressionBenchmark,
propertyInfoBenchmark
}.OrderBy(item => item.ElapsedMilliseconds);
benchmarkReports.Join(Environment.NewLine).WriteLineToConsole();
var fastest = benchmarkReports.First().ElapsedMilliseconds;
benchmarkReports.Select(report => (report.ElapsedMilliseconds / fastest).ToString("0.000")).Join(" < ").WriteLineToConsole();
var dictionaryAccess = (dictionaryLambdaExpressionBenchmark.ElapsedMilliseconds / lambdaExpressionBenchmark.ElapsedMilliseconds * 100);
("Dictionary Access: " + dictionaryAccess + " %").WriteLineToConsole();
Console.ReadKey();
}
}
public class Dummy
{
public Dummy(bool soother = true)
{
Soother = soother;
}
public bool? Soother { get; set; }
}
public class BenchMarkReport
{
#region Fields & Properties
public string Name { get; }
public TimeSpan ElapsedTime { get; }
public double ElapsedMilliseconds => ElapsedTime.TotalMilliseconds;
public ulong StepCount { get; }
public double StepElapsedMilliseconds => ElapsedMilliseconds / StepCount;
#endregion
#region Constructors
internal BenchMarkReport(string name, TimeSpan elapsedTime, ulong stepCount)
{
Name = name;
ElapsedTime = elapsedTime;
StepCount = stepCount;
}
#endregion
#region Methods
public override string ToString()
{
return $"{Name}: Elapsed = {ElapsedTime} ({ElapsedMilliseconds} ms); Step = {StepElapsedMilliseconds:0.###E+000} ms";
}
#endregion
}
public class Benchmark
{
#region Fields & Properties
private readonly Action _stepAction;
public string Name { get; }
public ulong StepCount { get; }
public Benchmark(string name, ulong stepCount, Action stepAction)
{
Name = name;
StepCount = stepCount;
_stepAction = stepAction;
}
#endregion
#region Constructors
#endregion
#region Methods
public static BenchMarkReport Run(string name, ulong stepCount, Action stepAction)
{
var benchmark = new Benchmark(name, stepCount, stepAction);
var benchmarkReport = benchmark.Run();
return benchmarkReport;
}
public BenchMarkReport Run()
{
return Run(StepCount);
}
public BenchMarkReport Run(ulong stepCountOverride)
{
var stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < StepCount; i++)
{
_stepAction();
}
stopwatch.Stop();
var benchmarkReport = new BenchMarkReport(Name, stopwatch.Elapsed, stepCountOverride);
return benchmarkReport;
}
#endregion
}
public static class ObjectExtensions
{
public static void WriteToConsole<TInstance>(this TInstance instance)
{
Console.Write(instance);
}
public static void WriteLineToConsole<TInstance>(this TInstance instance)
{
Console.WriteLine(instance);
}
// Goodies: add name inference from property lambda expression
// e.g. "instance => instance.PropertyName" redirected using "PropertyName"
public static TProperty Get<TInstance, TProperty>(this TInstance instance, string propertyName)
{
return FastPropertyRepository<TInstance, TProperty>.GetGetter(propertyName)(instance);
}
public static void Set<TInstance, TProperty>(this TInstance instance, string propertyName, TProperty propertyValue)
{
FastPropertyRepository<TInstance, TProperty>.GetSetter(propertyName)(instance, propertyValue);
}
}
public static class EnumerableExtensions
{
public static string Join<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source);
}
}
internal static class FastPropertyRepository<TInstance, TProperty>
{
private static readonly IDictionary<string, Action<TInstance, TProperty>> Setters;
private static readonly IDictionary<string, Func<TInstance, TProperty>> Getters;
static FastPropertyRepository()
{
Getters = new ConcurrentDictionary<string, Func<TInstance, TProperty>>();
Setters = new ConcurrentDictionary<string, Action<TInstance, TProperty>>();
}
public static Func<TInstance, TProperty> GetGetter(string propertyName)
{
if (!Getters.TryGetValue(propertyName, out Func<TInstance, TProperty> getter))
{
getter = FastPropertyFactory.GeneratePropertyGetter<TInstance, TProperty>(propertyName);
Getters[propertyName] = getter;
}
return getter;
}
public static Action<TInstance, TProperty> GetSetter(string propertyName)
{
if (!Setters.TryGetValue(propertyName, out Action<TInstance, TProperty> setter))
{
setter = FastPropertyFactory.GeneratePropertySetter<TInstance, TProperty>(propertyName);
Setters[propertyName] = setter;
}
return setter;
}
}
internal static class FastPropertyFactory
{
public static Func<TInstance, TProperty> GeneratePropertyGetter<TInstance, TProperty>(string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(TInstance), "value");
var propertyValueExpression = Expression.Property(parameterExpression, propertyName);
var expression = propertyValueExpression.Type == typeof(TProperty) ? propertyValueExpression : (Expression)Expression.Convert(propertyValueExpression, typeof(TProperty));
var propertyGetter = Expression.Lambda<Func<TInstance, TProperty>>(expression, parameterExpression).Compile();
return propertyGetter;
}
public static Action<TInstance, TProperty> GeneratePropertySetter<TInstance, TProperty>(string propertyName)
{
var instanceParameterExpression = Expression.Parameter(typeof(TInstance));
var parameterExpression = Expression.Parameter(typeof(TProperty), propertyName);
var propertyValueExpression = Expression.Property(instanceParameterExpression, propertyName);
var conversionExpression = propertyValueExpression.Type == typeof(TProperty) ? parameterExpression : (Expression)Expression.Convert(parameterExpression, propertyValueExpression.Type);
var propertySetter = Expression.Lambda<Action<TInstance, TProperty>>(Expression.Assign(propertyValueExpression, conversionExpression), instanceParameterExpression, parameterExpression).Compile();
return propertySetter;
}
}
为了举例,下面是我机器上的结果:
Native: Elapsed = 00:00:00.1346658 (134.6658 ms); Step = 2.693E-005 ms
Lambda Expression: Elapsed = 00:00:00.1578168 (157.8168 ms); Step = 3.156E-005 ms
Dictionary Access + Lambda Expression: Elapsed = 00:00:00.8092977 (809.2977 ms); Step = 1.619E-004 ms
Property Info: Elapsed = 00:00:02.2420732 (2242.0732 ms); Step = 4.484E-004 ms
1.000 < 1.172 < 6.010 < 16.649
Dictionary Access: 512.80833219277 %
您好,我正在尝试使用(缓存的)已编译的 lambda 表达式进行属性访问,与使用 PropertyInfo.GetValue()/SetValue() 方法调用相比,我得到的结果肯定要好得多(即更快)。然而,我觉得它离“原生”属性速度还有很远的距离。是否是基准测试方法使结果与其他方法如此不同?
- https://www.codeproject.com/Articles/584720/ExpressionplusbasedplusPropertyplusGettersplusandp
- http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision
- Compiled C# Lambda Expressions Performance
下面是我在 运行 下面的代码之后得到的结果:
Native: Elapsed = 00:00:00.0995876 (99.5876 ms); Step = 1.992E-005 ms Lambda Expression: Elapsed = 00:00:00.5369273 (536.9273 ms); Step = 1.074E-004 ms Property Info: Elapsed = 00:00:01.9187312 (1918.7312 ms); Step = 3.837E-004 ms 1.000 < 5.392 < 19.267
老实说,我觉得根据其他基准,编译的 lambda 表达式应该比使用常规属性慢两倍,而不是慢 5 到 6 倍。
有什么想法吗?基准测试方法?编译后的lambda表达式的计算方式?
public static class Program
{
public static void Main(params string[] args)
{
var stepCount = 5000000UL;
var dummy = new Dummy();
const string propertyName = "Soother";
const bool propertyValue = true;
var propertyInfo = typeof(Dummy).GetProperty(propertyName);
var nativeBenchmark = Benchmark.Run("Native", stepCount, () => dummy.Soother = propertyValue);
var lambdaExpressionBenchmark = Benchmark.Run("Lambda Expression", stepCount, () => dummy.Set(propertyName, propertyValue));
var propertyInfoBenchmark = Benchmark.Run("Property Info", stepCount, () => propertyInfo.SetValue(dummy, propertyValue, null));
var benchmarkReports = new[] { nativeBenchmark, lambdaExpressionBenchmark, propertyInfoBenchmark }.OrderBy(item => item.ElapsedMilliseconds);
benchmarkReports.Join(Environment.NewLine).WriteLineToConsole();
var fastest = benchmarkReports.First().ElapsedMilliseconds;
benchmarkReports.Select(report => (report.ElapsedMilliseconds / fastest).ToString("0.000")).Join(" < ").WriteLineToConsole();
Console.ReadKey();
}
}
public class Dummy
{
public bool? Soother { get; set; } = true;
}
public class BenchMarkReport
{
#region Fields & Properties
public string Name { get; }
public TimeSpan ElapsedTime { get; }
public double ElapsedMilliseconds
{
get
{
return ElapsedTime.TotalMilliseconds;
}
}
public ulong StepCount { get; }
public double StepElapsedMilliseconds
{
get
{
return ElapsedMilliseconds / StepCount;
}
}
#endregion
#region Constructors
internal BenchMarkReport(string name, TimeSpan elapsedTime, ulong stepCount)
{
Name = name;
ElapsedTime = elapsedTime;
StepCount = stepCount;
}
#endregion
#region Methods
public override string ToString()
{
return $"{Name}: Elapsed = {ElapsedTime} ({ElapsedMilliseconds} ms); Step = {StepElapsedMilliseconds:0.###E+000} ms";
}
#endregion
}
public class Benchmark
{
#region Fields & Properties
private readonly Action _stepAction;
public string Name { get; }
public ulong StepCount { get; }
public Benchmark(string name, ulong stepCount, Action stepAction)
{
Name = name;
StepCount = stepCount;
_stepAction = stepAction;
}
#endregion
#region Constructors
#endregion
#region Methods
public static BenchMarkReport Run(string name, ulong stepCount, Action stepAction)
{
var benchmark = new Benchmark(name, stepCount, stepAction);
var benchmarkReport = benchmark.Run();
return benchmarkReport;
}
public BenchMarkReport Run()
{
return Run(StepCount);
}
public BenchMarkReport Run(ulong stepCountOverride)
{
var stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < StepCount; i++)
{
_stepAction();
}
stopwatch.Stop();
var benchmarkReport = new BenchMarkReport(Name, stopwatch.Elapsed, stepCountOverride);
return benchmarkReport;
}
#endregion
}
public static class ObjectExtensions
{
public static void WriteToConsole<TInstance>(this TInstance instance)
{
Console.Write(instance);
}
public static void WriteLineToConsole<TInstance>(this TInstance instance)
{
Console.WriteLine(instance);
}
// Goodies: add name inference from property lambda expression
// e.g. "instance => instance.PropertyName" redirected using "PropertyName"
public static TProperty Get<TInstance, TProperty>(this TInstance instance, string propertyName)
{
return FastPropertyRepository<TInstance, TProperty>.GetGetter(propertyName)(instance);
}
public static void Set<TInstance, TProperty>(this TInstance instance, string propertyName, TProperty propertyValue)
{
FastPropertyRepository<TInstance, TProperty>.GetSetter(propertyName)(instance, propertyValue);
}
}
public static class EnumerableExtensions
{
public static string Join<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source);
}
}
internal static class FastPropertyRepository<TInstance, TProperty>
{
private static readonly IDictionary<string, Action<TInstance, TProperty>> Setters;
private static readonly IDictionary<string, Func<TInstance, TProperty>> Getters;
static FastPropertyRepository()
{
Getters = new ConcurrentDictionary<string, Func<TInstance, TProperty>>();
Setters = new ConcurrentDictionary<string, Action<TInstance, TProperty>>();
}
public static Func<TInstance, TProperty> GetGetter(string propertyName)
{
Func<TInstance, TProperty> getter;
if (!Getters.TryGetValue(propertyName, out getter))
{
getter = FastPropertyFactory.GeneratePropertyGetter<TInstance, TProperty>(propertyName);
Getters[propertyName] = getter;
}
return getter;
}
public static Action<TInstance, TProperty> GetSetter(string propertyName)
{
Action<TInstance, TProperty> setter;
if (!Setters.TryGetValue(propertyName, out setter))
{
setter = FastPropertyFactory.GeneratePropertySetter<TInstance, TProperty>(propertyName);
Setters[propertyName] = setter;
}
return setter;
}
}
internal static class FastPropertyFactory
{
public static Func<TInstance, TProperty> GeneratePropertyGetter<TInstance, TProperty>(string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(TInstance), "value");
var propertyValueExpression = Expression.Property(parameterExpression, propertyName);
var expression = propertyValueExpression.Type == typeof(TProperty) ? propertyValueExpression : (Expression)Expression.Convert(propertyValueExpression, typeof(TProperty));
var propertyGetter = Expression.Lambda<Func<TInstance, TProperty>>(expression, parameterExpression).Compile();
return propertyGetter;
}
public static Action<TInstance, TProperty> GeneratePropertySetter<TInstance, TProperty>(string propertyName)
{
var instanceParameterExpression = Expression.Parameter(typeof(TInstance));
var parameterExpression = Expression.Parameter(typeof(TProperty), propertyName);
var propertyValueExpression = Expression.Property(instanceParameterExpression, propertyName);
var conversionExpression = propertyValueExpression.Type == typeof(TProperty) ? parameterExpression : (Expression)Expression.Convert(parameterExpression, propertyValueExpression.Type);
var propertySetter = Expression.Lambda<Action<TInstance, TProperty>>(Expression.Assign(propertyValueExpression, conversionExpression), instanceParameterExpression, parameterExpression).Compile();
return propertySetter;
}
}
我将您的工作简化为更小的方法。它提高了整体性能,但也拉大了差距。
Native : 00:00:00.0029713 ( 2.9713ms) 5.9426E-07 Lambda Expression : 00:00:00.4356385 ( 435.6385ms) 8.71277E-05 Property Info : 00:00:01.3436626 ( 1343.6626ms) 0.00026873252
以下是使用的方法
public class Dummy
{
public bool? Soother { get; set; } = true;
}
public class Lab
{
Dummy _dummy = new Dummy();
ulong _iterations = 5000000UL;
const bool _propertyValue = true;
const string _propertyName = "Soother";
public BenchmarkReport RunNative()
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
_dummy.Soother = _propertyValue;
}
stopwatch.Stop();
return new BenchmarkReport("Native", stopwatch.Elapsed, _iterations);
}
public BenchmarkReport RunLambdaExpression()
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
_dummy.Set(_propertyName, _propertyValue);
}
stopwatch.Stop();
return new BenchmarkReport("Lambda Expression", stopwatch.Elapsed, _iterations);
}
public BenchmarkReport RunPropertyInfo()
{
PropertyInfo propertyInfo = typeof(Dummy).GetProperty(_propertyName);
Stopwatch stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < _iterations; i++)
{
propertyInfo.SetValue(_dummy, _propertyValue);
}
stopwatch.Stop();
return new BenchmarkReport("Property Info", stopwatch.Elapsed, _iterations);
}
}
public class BenchmarkReport
{
public string Name { get; set; }
public TimeSpan ElapsedTime { get; set; }
public ulong Iterations { get; set; }
public BenchmarkReport(string name, TimeSpan elapsedTime, ulong iterations)
{
Name = name;
ElapsedTime = elapsedTime;
Iterations = iterations;
}
}
和程序运行它
public static class Program
{
public static void Main(params string[] args)
{
Lab lab = new Lab();
List<BenchmarkReport> benchmarkReports = new List<BenchmarkReport>()
{
lab.RunNative(),
lab.RunLambdaExpression(),
lab.RunPropertyInfo()
};
foreach (var report in benchmarkReports)
{
Console.WriteLine("{0}: {1} ({2}ms) {3}",
report.Name.PadRight(20),
report.ElapsedTime,
report.ElapsedTime.TotalMilliseconds.ToString().PadLeft(10),
(double)report.ElapsedTime.TotalMilliseconds / report.Iterations);
}
Console.ReadKey();
}
}
正如我在问题下方的评论交流中所述,问题在于实现基准的方式。实际上底线 performance-wise 都是关于帮助器和扩展方法的,尤其是字典查找操作。
与执行已编译 lambda 本身的结果相比,我显然低估了字典查找操作所需的时间(即使它是常量 O(1)),也就是说它仍然慢了 lo0o0ot (是的,这就是为什么我最初在编译的 lambda 表演之后那么多的原因)。
如问题评论中所述,是的,我可以缓存结果,在这种情况下,我获得的性能非常接近本机 属性 访问。扩展方法真的很方便,但是它确实隐藏了一个非常重要的细节,因此我说 bench-marking 的方式不太好。
下面是完整代码,阐明了我的问题的一些问题:
public static class Program
{
public static void Main(params string[] args)
{
var stepCount = 5000000UL;
var dummy = new Dummy();
const string propertyName = "Soother";
const bool propertyValue = true;
var propertyInfo = typeof(Dummy).GetProperty(propertyName);
var lambdaExpression = FastPropertyFactory.GeneratePropertySetter<Dummy, bool>(propertyName);
var nativeBenchmark = Benchmark.Run("Native", stepCount, () => dummy.Soother = propertyValue);
var lambdaExpressionBenchmark = Benchmark.Run("Lambda Expression", stepCount, () => lambdaExpression(dummy, propertyValue));
var dictionaryLambdaExpressionBenchmark = Benchmark.Run("Dictionary Access + Lambda Expression", stepCount, () => dummy.Set(propertyName, propertyValue));
var propertyInfoBenchmark = Benchmark.Run("Property Info", stepCount, () => propertyInfo.SetValue(dummy, propertyValue, null));
var benchmarkReports = new[]
{
nativeBenchmark,
lambdaExpressionBenchmark,
dictionaryLambdaExpressionBenchmark,
propertyInfoBenchmark
}.OrderBy(item => item.ElapsedMilliseconds);
benchmarkReports.Join(Environment.NewLine).WriteLineToConsole();
var fastest = benchmarkReports.First().ElapsedMilliseconds;
benchmarkReports.Select(report => (report.ElapsedMilliseconds / fastest).ToString("0.000")).Join(" < ").WriteLineToConsole();
var dictionaryAccess = (dictionaryLambdaExpressionBenchmark.ElapsedMilliseconds / lambdaExpressionBenchmark.ElapsedMilliseconds * 100);
("Dictionary Access: " + dictionaryAccess + " %").WriteLineToConsole();
Console.ReadKey();
}
}
public class Dummy
{
public Dummy(bool soother = true)
{
Soother = soother;
}
public bool? Soother { get; set; }
}
public class BenchMarkReport
{
#region Fields & Properties
public string Name { get; }
public TimeSpan ElapsedTime { get; }
public double ElapsedMilliseconds => ElapsedTime.TotalMilliseconds;
public ulong StepCount { get; }
public double StepElapsedMilliseconds => ElapsedMilliseconds / StepCount;
#endregion
#region Constructors
internal BenchMarkReport(string name, TimeSpan elapsedTime, ulong stepCount)
{
Name = name;
ElapsedTime = elapsedTime;
StepCount = stepCount;
}
#endregion
#region Methods
public override string ToString()
{
return $"{Name}: Elapsed = {ElapsedTime} ({ElapsedMilliseconds} ms); Step = {StepElapsedMilliseconds:0.###E+000} ms";
}
#endregion
}
public class Benchmark
{
#region Fields & Properties
private readonly Action _stepAction;
public string Name { get; }
public ulong StepCount { get; }
public Benchmark(string name, ulong stepCount, Action stepAction)
{
Name = name;
StepCount = stepCount;
_stepAction = stepAction;
}
#endregion
#region Constructors
#endregion
#region Methods
public static BenchMarkReport Run(string name, ulong stepCount, Action stepAction)
{
var benchmark = new Benchmark(name, stepCount, stepAction);
var benchmarkReport = benchmark.Run();
return benchmarkReport;
}
public BenchMarkReport Run()
{
return Run(StepCount);
}
public BenchMarkReport Run(ulong stepCountOverride)
{
var stopwatch = Stopwatch.StartNew();
for (ulong i = 0; i < StepCount; i++)
{
_stepAction();
}
stopwatch.Stop();
var benchmarkReport = new BenchMarkReport(Name, stopwatch.Elapsed, stepCountOverride);
return benchmarkReport;
}
#endregion
}
public static class ObjectExtensions
{
public static void WriteToConsole<TInstance>(this TInstance instance)
{
Console.Write(instance);
}
public static void WriteLineToConsole<TInstance>(this TInstance instance)
{
Console.WriteLine(instance);
}
// Goodies: add name inference from property lambda expression
// e.g. "instance => instance.PropertyName" redirected using "PropertyName"
public static TProperty Get<TInstance, TProperty>(this TInstance instance, string propertyName)
{
return FastPropertyRepository<TInstance, TProperty>.GetGetter(propertyName)(instance);
}
public static void Set<TInstance, TProperty>(this TInstance instance, string propertyName, TProperty propertyValue)
{
FastPropertyRepository<TInstance, TProperty>.GetSetter(propertyName)(instance, propertyValue);
}
}
public static class EnumerableExtensions
{
public static string Join<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source);
}
}
internal static class FastPropertyRepository<TInstance, TProperty>
{
private static readonly IDictionary<string, Action<TInstance, TProperty>> Setters;
private static readonly IDictionary<string, Func<TInstance, TProperty>> Getters;
static FastPropertyRepository()
{
Getters = new ConcurrentDictionary<string, Func<TInstance, TProperty>>();
Setters = new ConcurrentDictionary<string, Action<TInstance, TProperty>>();
}
public static Func<TInstance, TProperty> GetGetter(string propertyName)
{
if (!Getters.TryGetValue(propertyName, out Func<TInstance, TProperty> getter))
{
getter = FastPropertyFactory.GeneratePropertyGetter<TInstance, TProperty>(propertyName);
Getters[propertyName] = getter;
}
return getter;
}
public static Action<TInstance, TProperty> GetSetter(string propertyName)
{
if (!Setters.TryGetValue(propertyName, out Action<TInstance, TProperty> setter))
{
setter = FastPropertyFactory.GeneratePropertySetter<TInstance, TProperty>(propertyName);
Setters[propertyName] = setter;
}
return setter;
}
}
internal static class FastPropertyFactory
{
public static Func<TInstance, TProperty> GeneratePropertyGetter<TInstance, TProperty>(string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(TInstance), "value");
var propertyValueExpression = Expression.Property(parameterExpression, propertyName);
var expression = propertyValueExpression.Type == typeof(TProperty) ? propertyValueExpression : (Expression)Expression.Convert(propertyValueExpression, typeof(TProperty));
var propertyGetter = Expression.Lambda<Func<TInstance, TProperty>>(expression, parameterExpression).Compile();
return propertyGetter;
}
public static Action<TInstance, TProperty> GeneratePropertySetter<TInstance, TProperty>(string propertyName)
{
var instanceParameterExpression = Expression.Parameter(typeof(TInstance));
var parameterExpression = Expression.Parameter(typeof(TProperty), propertyName);
var propertyValueExpression = Expression.Property(instanceParameterExpression, propertyName);
var conversionExpression = propertyValueExpression.Type == typeof(TProperty) ? parameterExpression : (Expression)Expression.Convert(parameterExpression, propertyValueExpression.Type);
var propertySetter = Expression.Lambda<Action<TInstance, TProperty>>(Expression.Assign(propertyValueExpression, conversionExpression), instanceParameterExpression, parameterExpression).Compile();
return propertySetter;
}
}
为了举例,下面是我机器上的结果:
Native: Elapsed = 00:00:00.1346658 (134.6658 ms); Step = 2.693E-005 ms
Lambda Expression: Elapsed = 00:00:00.1578168 (157.8168 ms); Step = 3.156E-005 ms
Dictionary Access + Lambda Expression: Elapsed = 00:00:00.8092977 (809.2977 ms); Step = 1.619E-004 ms
Property Info: Elapsed = 00:00:02.2420732 (2242.0732 ms); Step = 4.484E-004 ms
1.000 < 1.172 < 6.010 < 16.649
Dictionary Access: 512.80833219277 %