代表和方法组的性能

时间:2010-03-15 14:57:33

标签: c# asp.net performance delegates

您好我正在研究创建Cachedependency对象的性能问题,因此我编写了一个非常简单的测试程序,如下所示:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Caching;

namespace Test
{
    internal class Program
    {
        private static readonly string[] keys = new[] {"Abc"};
        private static readonly int MaxIteration = 10000000;

        private static void Main(string[] args)
        {
            Debug.Print("first set");
            test7();
            test6();
            test5();
            test4();
            test3();
            test2();
            Debug.Print("second set");
            test2();
            test3();
            test4();
            test5();
            test6();
            test7();
        }

        private static void test2()
        {
            DateTime start = DateTime.Now;
            var list = new List<CacheDependency>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(new CacheDependency(null, keys));
            }

            Debug.Print("test2 Time: " + (DateTime.Now - start));
        }

        private static void test3()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => new CacheDependency(null, keys));
            }

            Debug.Print("test3 Time: " + (DateTime.Now - start));
        }

        private static void test4()
        {
            var p = new Program();
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(p.GetDep);
            }

            Debug.Print("test4 Time: " + (DateTime.Now - start));
        }

        private static void test5()
        {
            var p = new Program();
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => { return p.GetDep(); });
            }

            Debug.Print("test5 Time: " + (DateTime.Now - start));
        }

        private static void test6()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(GetDepSatic);
            }

            Debug.Print("test6 Time: " + (DateTime.Now - start));
        }

        private static void test7()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => { return GetDepSatic(); });
            }

            Debug.Print("test7 Time: " + (DateTime.Now - start));
        }

        private CacheDependency GetDep()
        {
            return new CacheDependency(null, keys);
        }

        private static CacheDependency GetDepSatic()
        {
            return new CacheDependency(null, keys);
        }
    }
}

但是我不明白为什么这些结果看起来像这样:

first set
test7 Time: 00:00:00.4840277
test6 Time: 00:00:02.2041261
test5 Time: 00:00:00.1910109
test4 Time: 00:00:03.1401796
test3 Time: 00:00:00.1820105
test2 Time: 00:00:08.5394884
second set
test2 Time: 00:00:07.7324423
test3 Time: 00:00:00.1830105
test4 Time: 00:00:02.3561347
test5 Time: 00:00:00.1750100
test6 Time: 00:00:03.2941884
test7 Time: 00:00:00.1850106

特别是:

  1. 为什么test4和test6要慢得多 比他们的代表版本?我也 注意到Resharper具体 对代表发表评论 版本建议改变test5和 test7到“隐藏到方法组”。 这与test4和test6相同 但它们实际上更慢?
  2. 我似乎并不一致 呼叫时的性能差异 test4和test6,不应该是静态的 要求总是更快?

4 个答案:

答案 0 :(得分:3)

在使用方法组(4,6)的测试中,C#编译器不会缓存委托(Func)对象。它每次都创造新的。在7和5中,它将Action对象缓存到调用方法的生成方法。因此,从方法组创建Func非常慢(因为堆分配),但调用很快,因为动作直接指向您的方法。从缓存中创建lambdas的动作很快,但它指向生成的方法,因此有一个不必要的方法调用。

请注意,并非所有lambda都可以缓存(闭包会打破这种逻辑)

答案 1 :(得分:2)

我没有看到你的代码太过分,但第一步是切换到使用StopWatch类而不是DateTime.Now等。

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

答案 2 :(得分:1)

这很有意思。我想知道你的百万条目清单是否没有导致垃圾收集和结果偏差。尝试更改调用这些函数的顺序,看看结果会给你什么。

另一件事是JIT可能已经优化了你的代码,每次都不创建lambda,只是一遍又一遍地插入相同的值。可能值得在它上面运行ildasm并查看实际生成的内容。

答案 3 :(得分:0)

  

为什么test4和test6比委托版本慢得多?我还注意到Resharper特别对委托版本发表评论,建议将test5和test7更改为“Covert to method group”。哪个与test4和test6相同,但它们实际上更慢?

通过添加

,您将获得一个很大的线索
        Debug.Print(ReferenceEquals(list[0], list[1]) ? "same" : "different");

到每个方法的结尾。

使用委托版本,Func编译得有点像实际上:

var func = Func<CacheDependency> <>_hiddenfieldwithinvalidC#name;
if (func == null)
{
  <>_hiddenfieldwithinvalidC#name = func = () => p.GetDep();
}

使用方法组时,它的编译方式与:

相同
func = new Func<CacheDependency>(p.GetDep());

当编译器可以确定这样做是安全的时,使用lambdas创建的委托可以完成此备忘录,但不能将方法组强制转换为委托,并且您看到的性能差异可以说明原因。

  

在调用test4和test6时,我似乎没有一致的性能差异,静态调用不应该总是更快吗?

不一定。虽然静态调用的优点是可以传递少一个参数(因为没有隐式this参数),但这种区别是:

  1. 开始并不多。
  2. 如果不使用this,可能会被甩掉。
  3. 可以优化,因为在调用之前带有this指针的寄存器是调用后带有this指针的寄存器,所以没有必要实际做任何事情来获取它在那里。
  4. 呃,别的。我并没有声称这份清单是详尽无遗的。
  5. 真正的静态性能带来的好处更多的是,如果你在实例方法中做了自然静态的事情,你最终可能会过度传递不真正需要的对象并浪费时间。也就是说,如果你正在做静态方法中的自然实例,你可以最终存储/检索和/或分配和/或传递你不需要的参数中的对象,并且同样糟糕。