更新2
我终于找到了原因并且它完全不同:EnumExCore定义了一个这样的静态构造函数:
public abstract class EnumExCore<T> where T : class
{
// ...
static EnumExCore()
{
if (typeof(T) != typeof(Enum))
throw new InvalidOperationException($"{nameof(T)} must be {typeof(Enum).FullName}.");
}
// ...
}
我在发布原始问题时删除了,以试图简化问题...
显然,静态构造函数在调用静态方法时会对性能产生影响:http://www.codetails.com/2014/10/18/c-static-constructors-and-performance/
我很抱歉浪费你的时间..
更新
为了回应用户的评论,我整理了一个编译代码片段(Console App)。这样做我注意到,在控制台应用程序代码段中无法观察到下面提到的效果。在调试模式下,EnumEx比直接调用委托的速度慢约2倍,在发布模式下,它们花费的时间大致相同(我推测这是由于在此处内联)。
在我的原始测试用例中,帮助程序类(Integer<T>
,Integer
,EnumEx
)位于单独的(可移植类库)程序集中,而测试代码位于Wpf应用程序中&# 39; s Loaded
事件。将所有代码放入wpf应用程序时,结果与控制台应用程序相同。所以这似乎是与使用其他程序集中的类相关的效果。可能是什么原因造成的?为什么EnumEx方法在另一个程序集中定义时如此慢,并且直接委托调用不是? (包含委托的静态字段和调用的方法都在与EnumEx相同的程序集中定义。)
控制台应用代码段:
namespace TestConsole
{
using System;
using System.Reflection;
using System.Diagnostics;
using System.Linq;
class Program
{
static void Main(string[] args)
{
const int Count = 10000000;
Stopwatch watch = new Stopwatch();
int val = 0;
foreach (int loop in Enumerable.Range(0, 4))
{
val = 0;
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += Convert.ToInt32(ConsoleKey.A);
}
watch.Stop();
Console.WriteLine($"Convert.ToInt32: \t\t {watch.ElapsedMilliseconds} ms");
val = 0;
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += Integer<ConsoleKey>.ToInt32(ConsoleKey.A);
}
watch.Stop();
Console.WriteLine($"Integer<TestEnum>.ToInt32: \t {watch.ElapsedMilliseconds} ms");
val = 0;
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += EnumEx.ToInt32(ConsoleKey.A);
}
watch.Stop();
Console.WriteLine($"EnumEx.ToInt32: \t\t {watch.ElapsedMilliseconds} ms");
Console.WriteLine();
}
Console.ReadKey();
}
}
static class Integer
{
static int ToInt32(byte value) { return (int)value; }
static int ToInt32(sbyte value) { return (int)value; }
static int ToInt32(ushort value) { return (int)value; }
static int ToInt32(short value) { return (int)value; }
static int ToInt32(uint value) { return (int)value; }
static int ToInt32(int value) { return (int)value; }
static int ToInt32(ulong value) { return (int)value; }
static int ToInt32(long value) { return (int)value; }
static Type GetType<T>()
{
Type type = typeof(T);
TypeInfo info = type.GetTypeInfo();
if (info.IsPrimitive)
return type;
if (info.IsEnum)
return Enum.GetUnderlyingType(type);
throw new NotSupportedException($"{nameof(T)} is expected to be a primitive integer type or an enum.");
}
internal static Func<T, int> GetToInt32Method<T>()
{
Type type = GetType<T>();
MethodInfo method;
if (false)
{ }
else if (type == typeof(byte))
method = new Func<byte, int>(ToInt32).GetMethodInfo();
else if (type == typeof(sbyte))
method = new Func<sbyte, int>(ToInt32).GetMethodInfo();
else if (type == typeof(ushort))
method = new Func<ushort, int>(ToInt32).GetMethodInfo();
else if (type == typeof(short))
method = new Func<short, int>(ToInt32).GetMethodInfo();
else if (type == typeof(uint))
method = new Func<uint, int>(ToInt32).GetMethodInfo();
else if (type == typeof(int))
method = new Func<int, int>(ToInt32).GetMethodInfo();
else if (type == typeof(ulong))
method = new Func<ulong, int>(ToInt32).GetMethodInfo();
else if (type == typeof(long))
method = new Func<long, int>(ToInt32).GetMethodInfo();
else
throw new InvalidOperationException("T is not supported");
return method.CreateDelegate(typeof(Func<T, int>)) as Func<T, int>;
}
}
static class Integer<T>
{
public static readonly Func<T, int> ToInt32 = Integer.GetToInt32Method<T>();
}
namespace Internal
{
public abstract class EnumExCore<T> where T : class
{
internal EnumExCore() { }
public static int ToInt32<TEnum>(TEnum value)
where TEnum : struct, T
{
return Integer<TEnum>.ToInt32(value);
}
}
}
public sealed class EnumEx : Internal.EnumExCore<Enum>
{
EnumEx() { }
}
}
原始问题
我在辅助类上有静态字段
public static class Integer<T>
{
public static readonly Func<T, int> ToInt32 = Integer.GetToInt32Method<T>();
}
其中ToInt32
指向类似于
static int ToInt32(uint value) { return (int)value; }
和另一个像这样的辅助类
namespace Internal
{
public abstract class EnumExCore<T> where T : class
{
internal EnumExCore() { }
public static int ToInt32<TEnum>(TEnum value)
where TEnum : struct, T
{
return Integer<TEnum>.ToInt32(value);
}
}
}
public sealed class EnumEx : Internal.EnumExCore<Enum>
{
EnumEx() { }
}
现在尝试将性能与我放在一起的快速测试进行比较:
const int Count = 10000000;
Stopwatch watch = new Stopwatch();
int val = 0;
foreach (int loop in Enumerable.Range(0, 4))
{
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += Convert.ToInt32(TestEnum.One);
}
watch.Stop();
this.tbOutput.Text += $"Convert.ToInt32: \t\t\t {watch.ElapsedMilliseconds} ms{System.Environment.NewLine}";
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += Integer<TestEnum>.ToInt32(TestEnum.One);
}
watch.Stop();
this.tbOutput.Text += $"Integer<TestEnum>.ToInt32: \t {watch.ElapsedMilliseconds} ms{System.Environment.NewLine}";
watch.Restart();
for (int i = 0; i < Count; i++)
{
val += EnumEx.ToInt32(TestEnum.One);
}
watch.Stop();
this.tbOutput.Text += $"EnumEx.ToInt32: \t\t\t {watch.ElapsedMilliseconds} ms{System.Environment.NewLine}{System.Environment.NewLine}";
}
产生类似于
的输出Convert.ToInt32: 1041 ms
Integer<TestEnum>.ToInt32: 42 ms
EnumEx.ToInt32: 1364 ms
Convert.ToInt32: 1010 ms
Integer<TestEnum>.ToInt32: 39 ms
EnumEx.ToInt32: 1342 ms
Convert.ToInt32: 1010 ms
Integer<TestEnum>.ToInt32: 41 ms
EnumEx.ToInt32: 1313 ms
Convert.ToInt32: 1020 ms
Integer<TestEnum>.ToInt32: 40 ms
EnumEx.ToInt32: 1292 ms
发布或调试版本,无论是否附带调试器,都没有太大作用。
有人可以向我解释,为什么EnumEx.ToInt32
比直接调用Integer<TestEnum>.ToInt32
代表要慢得多?或者我的测试有问题吗?
修改
GetToInt32Method<T>
只是一个返回委托的辅助方法:
internal static Func<T, int> GetToInt32Method<T>()
{
Type type = GetType<T>();
MethodInfo method;
if (false)
{ }
else if (type == typeof(byte))
method = new Func<byte, int>(ToInt32).GetMethodInfo();
else if (type == typeof(sbyte))
method = new Func<sbyte, int>(ToInt32).GetMethodInfo();
else if (type == typeof(ushort))
method = new Func<ushort, int>(ToInt32).GetMethodInfo();
else if (type == typeof(short))
method = new Func<short, int>(ToInt32).GetMethodInfo();
else if (type == typeof(uint))
method = new Func<uint, int>(ToInt32).GetMethodInfo();
else if (type == typeof(int))
method = new Func<int, int>(ToInt32).GetMethodInfo();
else if (type == typeof(ulong))
method = new Func<ulong, int>(ToInt32).GetMethodInfo();
else if (type == typeof(long))
method = new Func<long, int>(ToInt32).GetMethodInfo();
else
throw new GenericTypeParameterNotSupportetException<T>();
return method.CreateDelegate(typeof(Func<T, int>), target) as Func<T, int>;
}
答案 0 :(得分:1)
您在第一个循环中对Convert.ToInt32
的调用将编译为调用接收Int32
的覆盖,因为enum
的基础类型是`Int32&#39; 。这个具体实现只是将输入参数返回给输出。
其他方法不是直截了当的,因为他们首先必须解析一个函数,然后调用它。
在编译解决方案时,编译器甚至可以将对Convert.ToInt32
的调用短路并将其完全删除。我不确定这是否真的发生了,但是对于一千万次迭代只有40左右的时间,看起来这种方法从未被真正调用过。
另外两个循环依赖于繁重的提升,最终决定调用哪个函数,然后在已解析的函数上放置一个动态调度的调用。对于所有这些工作而言,花费大约一秒钟进行一千万次通话看起来非常好。
然而,这与静态调度可以实现的性能相差甚远,特别是在代码优化器可以内联方法并避免整个调用的情况下,我相信这已经发生了。