静态构造函数

时间:2016-06-29 10:02:23

标签: c# performance

更新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>IntegerEnumEx)位于单独的(可移植类库)程序集中,而测试代码位于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>;
} 

1 个答案:

答案 0 :(得分:1)

您在第一个循环中对Convert.ToInt32的调用将编译为调用接收Int32的覆盖,因为enum的基础类型是`Int32&#39; 。这个具体实现只是将输入参数返回给输出。

其他方法不是直截了当的,因为他们首先必须解析一个函数,然后调用它。

在编译解决方案时,编译器甚至可以将对Convert.ToInt32的调用短路并将其完全删除。我不确定这是否真的发生了,但是对于一千万次迭代只有40左右的时间,看起来这种方法从未被真正调用过。

另外两个循环依赖于繁重的提升,最终决定调用哪个函数,然后在已解析的函数上放置一个动态调度的调用。对于所有这些工作而言,花费大约一秒钟进行一千万次通话看起来非常好。

然而,这与静态调度可以实现的性能相差甚远,特别是在代码优化器可以内联方法并避免整个调用的情况下,我相信这已经发生了。