效率:Func <t>,Vs实例T </t>

时间:2012-05-23 22:34:11

标签: c# .net generics functional-programming

最近我一直在尝试使用Func<T>课程,到目前为止我很喜欢它。然而我注意到越来越多的我开始使用它而不是实际使用T的实例,所以我想问; 使用Func<T> vs T的开销是多少?我知道这是一个有点普遍的问题,因为T可以是任何东西,所以我想这个问题应该是专注于,传递函数的开销是什么,而不是简单对象的实例?

为了论证,我们假设如下。

我们的模拟对象T

public class Person
{
    private string _name = string.Empty;
    private int _age = 0;
    private bool _isMale = true;

    public Person(string name, int age, bool isMale)
    {
        this.Name = name;
        this.Age = age;
        this.IsMale = isMale;
    }

    public string Name
    {
        get { return this._name; }
        set { this._name = value; }
    }

    public int Age
    {
        get { return this._age; }
        set { this._age = value; }
    }

    public bool IsMale
    {
        get { return this._isMale; }
        set { this._isMale = value; }
    }
}

现在,假设我们在IDictionary上有一个非常好的扩展方法,它通过键选择默认值。伪代码可以描述如下:
是KeyValuePair集合中的密钥    是的,返回值 不,返回默认值

选项1.我们的扩展方法使用T

的实例
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary source, TKey key, TValue @default)
{
    if (source.ContainsKey(key))
    {
        return source[key];
    }
    return @default;
}

// usage
var myValue = myDictionary.GetValueOrDefault("Richard", new Person());

选项2.我们的扩展方法使用Func<T> ... mmm,漂亮!

public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary source, TKey key, Func<TValue> defaultSelector)
{
    if (source.ContainsKey(key))
    {
        return source[key];
    }
    return defaultSelector();
}

// usage
var myValue = myDictionary.GetValueOrDefault("Richard", () => new Person("Richard", 25, true));

比较

比较上述选项,很明显两者都有潜在的好处。选项1稍微容易阅读,但我目前喜欢使用Func<T>,因此对我来说选项2似乎是理想的。我想我认为它是一个懒惰实例化的参数,只在需要时执行,因此节省了效率,但我是对的吗?

4 个答案:

答案 0 :(得分:6)

这是我用于基准测试的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication3
{
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
    using System.Numerics;
    using System.Xml.Linq;

    public class Program
    {

        public class Person
        {
            private string _name = string.Empty;

            private int _age = 0;

            private bool _isMale = true;

            public Person(string name, int age, bool isMale)
            {
                this.Name = name;
                this.Age = age;
                this.IsMale = isMale;
            }

            public string Name
            {
                get
                {
                    return this._name;
                }
                set
                {
                    this._name = value;
                }
            }

            public int Age
            {
                get
                {
                    return this._age;
                }
                set
                {
                    this._age = value;
                }
            }

            public bool IsMale
            {
                get
                {
                    return this._isMale;
                }
                set
                {
                    this._isMale = value;
                }
            }
        }

        private static void Main(string[] args)
        {
            var myDictionary = new Dictionary<string, Person>();
            myDictionary.Add("notRichard", new Program.Person("Richard1", 26, true));
            myDictionary.Add("notRichard1", new Program.Person("Richard2", 27, true));
            myDictionary.Add("notRichard2", new Program.Person("Richard3", 28, true));
            myDictionary.Add("notRichard3", new Program.Person("Richard4", 29, true));
            // usage
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for(int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", new Program.Person("Richard", 25, true));
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", ()=> new Program.Person("Richard", 25, true));
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadKey();
        }
    }
    public static class Ex
    {
        public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key, TValue @default)
        {
            if (source.ContainsKey(key))
            {
                return source[key];
            }
            return @default;
        }
        public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key, Func<TValue> defaultSelector)
        {
            if (source.ContainsKey(key))
            {
                return source[key];
            }
            return defaultSelector();
        }


    }
}

调用每个extenssion方法 100000000 次(没有找到条目,因此每次都会执行Func)会得到以下结果:

T - 10352 ms

Func<T> - 12268 ms

调用每个extenssion方法 100000000 次(并找到一个条目,因此根本不调用Func)会得到以下结果:

T - 15578 ms

Func<T> - 11072 ms

因此,哪一个执行得更快取决于您节省了多少实例化以及每个实例化的成本。

通过重用默认人员实例来优化代码,T为6809毫秒,Func<T>为7452:

            Stopwatch sw = new Stopwatch();
            var defaultPerson = new Program.Person("Richard", 25, true);
            sw.Start();
            for(int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", defaultPerson);
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", () => defaultPerson);
            }

因此,从理论上讲(如果你从实例中取出实例化),在调用堆栈中保存跳跃可以获得一些性能提升,但实际上,这个增益可以忽略不计。

答案 1 :(得分:1)

传递对函数的引用并将引用传递给对象,我希望它非常相似。如果您正在进行一百万次这些调用,那么两者都可能通过保存侧面的引用并每次重用相同的值而受益。但请注意,在第一种情况下,默认对象每次都可以相同:

Person defaultPerson = new Person();
var myValue = myDictionary.GetValueOrDefault("Richard", defaultPerson);

但在第二种情况下,每次返回默认值时,您都会实例化一个全新的Person

Func<Person> defaultPersonFunc = () => new Person("Richard", 25, true);
var myValue = myDictionary.GetValueOrDefault("Richard", defaultPersonFunc);

您可以通过更改为:

来解决这个问题
Person defaultPerson = new Person("Richard", 25, true);
Func<Person> defaultPersonFunc = () => defaultPerson;
var myValue = myDictionary.GetValueOrDefault("Richard", defaultPersonFunc);

但是我没有看到你使用Func<Person>

获得任何东西

答案 2 :(得分:0)

代表们很棒,但我不确定这是否会让您受益于使用它们。

选项1效率低下,因为您无条件地创建了一个新的对象实例:

var myValue = myDictionary.GetValueOrDefault("Richard", new Person());

始终会创建一个新人。

选项2效率低下,因为您创建了一个新的Func实例。您的语法是此简写:

var myValue = myDictionary.GetValueOrDefault("Richard", new Func<Person>(()=> {
    Person("Richard", 25, true));
});

编译器可能会优化另一个,但最终,这是使用错误的工具来解决问题的情况。解决方案是使用内置的TryGetValue方法,实际仅在需要时运行默认代码:

Person myValue;
if (!myDictionary.TryGetValue("Richard", out myValue)) {
    myValue = new Person("Richard",25,true);
}

当目标不在字典中时,我从另一个答案运行测试:

  • 7309 ms(opt 1)
  • 8705毫秒(选择2)
  • 5972 ms(TryGetValue)

当目标在词典中时(例如,它永远不需要使用默认值):

  • 10026 ms(opt 2)
  • 7712 ms(选择2)
  • 3491 ms(TryGetValue)

无论如何,它显然要快得多。这是有道理的 - 因为你必须创建一个对象,无论结果是什么每次,你都要对选项1或2进行测试。在Func场景中,你必须创建<当需要默认值时,em>两个对象,而当需要默认值时,则为一个对象,因此当始终需要默认值时,它会变得更糟。

另一方面,只需使用TryGetValue并且只有条件地执行默认代码,只需要在需要默认代码时创建一个对象(而且只是你真正需要的对象)。

有时老式的方式是最好的:)

FWIW - 我认为像GetValueOrDefault这样的方法肯定是有用的,但是当你必须明确定义默认值时可能不会。也就是说,在使用委托的代码风格方面我没有看到巨大的好处,并且肯定没有性能优势。但是,如果您不需要实际定义默认人员的内容,那么为什么不创建这样的扩展方法:

public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary source, 
    TKey key) where TValue: new()
{
    TValue value;
    if (!source.TryGetValue(key, out value)) {
        value = new TValue();
    }
    return value;
}

答案 3 :(得分:0)

第二种方法是低效的,因为它要求编译器在每次调用函数时生成闭包(委托和对象实例的组合),而不管是否实际需要闭包。除非Person创建成本高,否则无条件生成闭包将比无条件生成Person更糟糕。

另一种方法是将lambda作为静态方法,将其参数作为ref结构接受。我希望C#能为这种方法提供一些语言支持,因为它可以更有效地完成闭包可以做的许多事情。代码看起来像:

public delegate TResult TFuncByRef<TParam,TResult>(ref TParam);
public static TValue GetValueOrDefault<TKey, TValue, TParam>
  (this IDictionary source, TKey key, 
   FuncByRef<TParam, TValue> defaultSelector, 
   ref TParam param)
{
    ref TValue Result = default(TValue);
    if (!source.TryGetValue(key, ref Result))
        Result = defaultSelector(ref param);
    return Result;
}

struct CreatePersonParams {public string Name; public int Age; public bool IsMale};
static Person CreatePersonByName(ref CreatePersonParams param)
{
  return new Person(param.Name, param.Age, param.IsMale);
}
... then to use it...
{
  ...
  CreatePersonParams newPersonParams;
  newPersonParams.Name = "Emily";
  newPersonParams.Age = 23;
  newPersonParams.IsMale = False;
  ...
  whatever = myDict.GetValueOrDefault(keyValue, CreatePersonByName, ref newPersonParams);
  ...
}

请注意,将结构实例创建为局部变量比创建新的类实例更便宜(与为每个结构字段创建局部变量基本相同)。另请注意,因为CreatePersonByName是静态方法,所以系统只需要在程序的生命周期内创建一个委托。请注意,无论结构有多大,通过ref传递结构都很便宜,并且访问ref传递的结构字段与访问类的字段一样有效。

使用这种方法,相同的GetValueOrDefault方法可以处理需要任何参数组合的例程,而无需创建任何闭包。不幸的是,因为C#不提供任何语言支持这种类型的代码转换(实际上可能比将lambdas转换为闭包更简单),所以有必要明确定义一个人可能想要使用它的所有类型的结构。人们可以定义一个TupleStruct<T1,T2,T3>等结构的家族并使用它们,但这样的事情仍然有点难看。