こんにちは、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
正直言って、私は他のベンチマークに基づいて、コンパイルされたラムダ式は通常のプロパティを使用するよりも2倍遅くなければならず、5〜6倍遅くならないと感じています。
ベンチマーク方法は?コンパイルされたラムダ式が計算される方法は?
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;
}
}
リリースを使用して結果を実行しましたか?結果はあなたを驚かせるかもしれません。 – Svek
私はまた、アクションを回避するのではなく、ベンチマークを個々のメソッドに単純化したいと思うかもしれません。私はJITの専門家ではありませんが、それはそれが何かを持っていると思われます。 – Svek
@Svek実際にはリリースビルドを試してみましたが、直感的には直感的でしたが、比は約15倍遅くなりました。ネイティブアクセスのステップを実行するのに費やされた平均時間は、キャッシュされたコンパイル済みのラムダ式のwasterは少し遅くなります。デリゲートでラップアップすることについては、少し遅くなりますが、比率に関しては、このようにしてもしなくても一貫していなければなりません。 – Ehouarn