反射方法调用的性能与委托调用的性能比较

6
我的目标是编写一个弱类型的 TryParse 方法,该方法将支持基本上所有可用的结构类型(int、long、float 等...)
public static bool TryParse(Type type, string s, out object obj)

这个实现将会调用提供的type参数的TryParse方法(如果类型是int,则会调用int.TryParse并将输出值作为对象返回)。

我通过反射来实现它,但有一个严重的性能惩罚(正如我所预期的那样)。

反射实现:

public static class ParserHelper
{
    public delegate bool TryParseDl(string str, out object obj);

    private static readonly HashSet<Type> ParsableStructs = new HashSet<Type>
    {
    typeof(int),
    typeof(uint),
    typeof(decimal),
    typeof(short),
    typeof(ushort),
    typeof(double),
    typeof(long),
    typeof(ulong),
    typeof(float),
    typeof(byte),
    typeof(sbyte)
    };

    public static readonly ReadOnlyDictionary<Type, TryParseDl> StructParsers;

    static ParserHelper()
    {
        StructParsers = new ReadOnlyDictionary<Type, TryParseDl>(CreateParsersForStructs());
    }

    /// Creates parsers for structs
            private static Dictionary<Type, TryParseDl> CreateParsersForStructs()
        {
            var parsers = new Dictionary<Type, TryParseDl>();
            foreach (var t in ParsableStructs)
            {
                parsers[t] = GetParserForStruct(t);
            }
            return parsers;
        }

    private static TryParseDl GetParserForStruct(Type targetType)
        {
            var methodInfo = targetType.GetMethod(
                "TryParse",
                BindingFlags.Public | BindingFlags.Static,
                Type.DefaultBinder,
                new[] { typeof(string), targetType.MakeByRefType() },
                null);

            return (string str, out object obj) =>
                {
                    if (string.IsNullOrEmpty(str))
                    {
                        obj = targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
                        return true;
                    }
                    var inputParameters = new object[] { str, null };
                    var tryParseResult = (bool)methodInfo.Invoke(null, inputParameters);
                    obj = inputParameters[1];
                    return tryParseResult;
                };
        }
}

以下是性能测试结果:

public class Program
{
    public static void Main()
    {
        Stopwatch s = new Stopwatch();
        string str = "100";     
        s.Start();
        for(int j = 0;j<100;j++)
        {
            int i;
            int.TryParse(str,out i);

        }
        s.Stop();
        Console.WriteLine(s.Elapsed);
        s.Reset();
        s.Start();
        var parser = ParserHelper.StructParsers[typeof(int)];   
        for(int j = 0;j<100;j++)
        {                           
            object o;
            parser(str, out o);
        }

        s.Stop();
        Console.WriteLine(s.Elapsed);
    }
}

平均结果是反射调用比直接调用慢大约200倍(100次重试)。这是演示反射测试的Fiddle
我尝试通过使用缓存委托来提高性能:
public static class StructParserExtensions
{
    public static bool IntToObjParse(string str, out object obj)
    {
        int i;
        var result = int.TryParse(str, out i);
        obj = result ? (object)i : null;
        return result;
    }

    public static bool LongToObjParse(string str, out object obj)
    {
        long i;
        var result = long.TryParse(str, out i);
        obj = result ? (object)i : null;
        return result;
    }

    //implementations for other types goes here

}


public static class ParserHelper
{
    public delegate bool TryParseDl(string str, out object obj);

    public static readonly ReadOnlyDictionary<Type, TryParseDl> StructParsers;

    static ParserHelper()
    {
        StructParsers = new ReadOnlyDictionary<Type, TryParseDl>(CreateParsersForStructs());
    }

    /// Creates parsers for structs
    /// </summary>
    /// <returns>Dictionary</returns>
    private static Dictionary<Type, TryParseDl> CreateParsersForStructs()
    {

        var parsers = new Dictionary<Type, TryParseDl>();
        parsers[typeof(int)] = StructParserExtensions.IntToObjParse;                      
        parsers[typeof(long)] = StructParserExtensions.LongToObjParse;           
        return parsers;
    }           
}

我曾认为使用委托会大幅提高性能,使其接近于直接调用,但我错了,即使进行100次尝试,性能仍然慢了约100倍。 这是演示链接 我的问题是:
  1. 虽然我看到了多个将反射调用转换为委托调用的示例,但在这种情况下它并没有起到作用。为什么?
  2. 是否有任何方式可以在这种情况下提高性能?

如果您正在比较此代码与其他代码的性能,那么引用其他代码将非常有用。当我们不知道更快的代码是什么时,很难说“为什么这个执行速度较慢”。性能差异可能有任何数量的原因。我正在查看您的代码并思考:“它使用了反射,当然比不使用反射的版本要慢。” - Chris
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Alex Art.
3个回答

6

你只是在测试100次迭代。大部分时间都在测试启动时的一次性开销。增加迭代次数,直到每个测试花费1秒钟。这样,开销就会消失在噪声中。

目前,你的代码运行时间为0.5毫秒。这远远低于噪声范围。修复后,我得到:

00:00:00.9711365
00:00:01.0958751 //Slightly slower

这个基准测试使用1e7次迭代,而之前的测试只用了1e2次迭代。 同时请确保在所关心的二进制位数上在Release模式下进行测试,不要附加调试器。


首先感谢您的回复。将迭代次数增加到1e7,确实证明了您的观点。同时看起来反射调用比直接调用慢大约10倍。所以现在我确实看到了差异。有趣的是,如果预期的迭代次数相对较小,则反射调用和委托调用之间的差异相对可以忽略不计。 - Alex Art.
你不能得出这样的结论,因为你不仅要测量调用风格,还要考虑一次性开销。反射可能只有很高的启动成本。你可以先运行一个单热身迭代。这将消除一次性开销,你可以少做几次迭代。 - usr

2

以下内容有何问题:

        private object Converter(object inVal, Type t)
    {
        return Convert.ChangeType(inVal, t);
    }

1
在尝试和学习的过程中有什么问题吗? - Stralos
1
虽然它没有回答第一个问题,但似乎回答了如何拥有更高性能代码的第二个问题。 - Chris

1
如前所述。进行100次迭代,您很可能只测量到了开销。
我进一步测试了一下。 我将您的代码合并在一起,并运行了450次迭代以获取一些统计数据。 我将其设置为解析1000万次(10^7)
这是代码:http://pastebin.com/65dhdX9t 以下是最后几次迭代的结果:
m-method, d-delegate, r-reflection Statistics 总结:
- 委托比直接调用慢约1.195倍
- 反射比直接调用慢约6.105倍
希望对您有所帮助!它确实帮助我说服自己从反射转移到委托。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接