4

最近、クラスの使い方を試していますが、今のFunc<T>ところ大好きです。ただし、実際にのインスタンスを使用する代わりに、ますます使用し始めていることに気付いたTので、質問したいと思いました。vsを使用するオーバーヘッドは何ですか?Func<T>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....mmmを使用した拡張方法Func<T>、かなり!

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

4 に答える 4

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();
        }


    }
}

各拡張メソッドを100000000回呼び出すと(エントリが見つからないため、毎回Funcが実行されます)、次の結果が得られます。

T-10352ミリ秒

Func<T>-12268ミリ秒

各拡張メソッドを100000000回呼び出す(そしてエントリを見つけるため、Funcをまったく呼び出さない)と、次の結果が得られます。

T-15578ミリ秒

Func<T>-11072ミリ秒

したがって、どちらがより速く実行されるかは、保存するインスタンス化の数と、各インスタンス化のコストによって異なります。

デフォルトのpersonインスタンスを再利用してコードを少し最適化すると、次の場合は6809ミリ秒、次の場合はT7452ミリ秒になりFunc<T>ます。

            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);
            }

したがって、理論的には(方程式からインスタンス化を取り除いた場合)、コールスタックにホップを保存すると、パフォーマンスがいくらか向上しますが、実際には、この向上はごくわずかです。

于 2012-05-23T22:57:37.347 に答える
1

関数への参照の受け渡しとオブジェクトへの参照の受け渡しは非常に似ていると思います。これらの呼び出しを何百万回も実行している場合は、両方の側で参照を保存し、毎回同じ値を再利用することでおそらくメリットがあります。ただし、最初のケースでは、デフォルトのオブジェクトを毎回同じにすることができることに注意してください。

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

Personただし、2番目のケースでは、デフォルトが返されるたびに新しいインスタンスを作成します。

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>

于 2012-05-23T23:00:34.290 に答える
0

代表者は素晴らしいですが、これがあなたが彼らを使用することから利益を得る状況であるかどうかはわかりません。

オプション1は、新しいオブジェクトインスタンスを無条件に作成するため、非効率的です。

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

新しいPersonは常に作成されます。

新しいFuncインスタンスを作成するため、オプション2は非効率的です。あなたの構文はこれの省略形です:

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ミリ秒(オプション1)
  • 8705ミリ秒(オプション2)
  • 5972ミリ秒(TryGetValue)

ターゲットが辞書にある場合(たとえば、デフォルト値を使用する必要はありません):

  • 10026ミリ秒(オプション2)
  • 7712ミリ秒(オプション2)
  • 3491ミリ秒(TryGetValue)

何があっても、明らかに大幅に高速です。これは理にかなっています。オプション1または2のテストを実行するたびにFunc、結果に関係なくオブジェクトを作成する必要があるためです。シナリオでは、デフォルトが必要な場合は2つのオブジェクトを作成し、そうでない場合は1つのオブジェクトを作成する必要があります。したがって、デフォルトが常に必要な場合は最悪です。

一方、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;
}
于 2012-05-24T12:27:47.217 に答える
0

2番目の方法は、クロージャが実際に必要かどうかに関係なく、関数が呼び出されるたびにコンパイラがクロージャ(デリゲートとオブジェクトインスタンスの組み合わせ)を生成する必要があるため、非効率的です。作成に費用がかかる場合を除いPersonて、無条件にクロージャを生成することは、無条件にを生成するよりも悪くなりますPerson

別のアプローチは、ラムダを、そのパラメーターをref構造体として受け入れる静的メソッドにすることです。C#がこのアプローチに言語サポートを提供することを望みます。これは、クロージャーが実行できる多くのことをより効率的に実行できるためです。コードは次のようになります。

パブリックデリゲートTResultTFuncByRef<TParam、TResult>(ref TParam);
public static TValue GetValueOrDefault <TKey、TValue、TParam>
  (このIDictionaryソース、TKeyキー、
   FuncByRef <TParam、TValue> defaultSelector、
   ref TParam param)
{{
    ref TValue Result = default(TValue);
    if(!source.TryGetValue(key、ref Result))
        結果=defaultSelector(ref param);
    結果を返します。
}

structCreatePersonParams{パブリック文字列名; public int Age; public bool IsMale};
static Person CreatePersonByName(ref CreatePersonParams param)
{{
  新しいPerson(param.Name、param.Age、param.IsMale);を返します。
}
...それからそれを使用するには...
{{
  ..。
  CreatePersonParams newPersonParams;
  newPersonParams.Name="エミリー";
  newPersonParams.Age = 23;
  newPersonParams.IsMale = False;
  ..。
  何でも=myDict.GetValueOrDefault(keyValue、CreatePersonByName、ref newPersonParams);
  ..。
}

ローカル変数として構造体インスタンスを作成する方が、新しいクラスインスタンスを作成するよりも安価であることに注意してください(基本的に、構造体フィールドごとにローカル変数を作成するのと同じです)。CreatePersonByNameは静的メソッドであるため、システムはプログラムの存続期間中に1つのデリゲートを作成するだけでよいことにも注意してください。さらに、構造体がいくら大きくても、refによる構造体の受け渡しは安価であり、refによって渡された構造体のフィールドへのアクセスは、クラスのフィールドへのアクセスと同じくらい効率的であることに注意してください。

このアプローチを使用すると、同じGetValueOrDefaultメソッドで、クロージャを作成しなくても、パラメータの任意の組み合わせを必要とするルーチンを処理できます。残念ながら、C#はこのタイプのコード変換(実際にはラムダからクロージャへの変換よりも簡単な場合があります)をサポートする言語を提供していないため、C#で使用する可能性のあるすべてのタイプの構造を明示的に定義する必要があります。などの構造のファミリーを定義してTupleStruct<T1,T2,T3>それらを使用することもできますが、そのようなことはまだ少し醜いです。

于 2012-05-24T15:38:43.307 に答える