10

キャッシングとメモ化に関するいくつかの記事と、デリゲートとジェネリックを使用して簡単に実装する方法を読んでいました。構文は非常に単純で、実装は驚くほど簡単ですが、繰り返しの性質があるため、同じ配管コードを何度も作成する代わりに、属性に基づいてコードを生成できるはずだと感じています。

デフォルトの例から始めたとしましょう:

class Foo
{
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

そして、これをメモするために:

// Let's say we have a utility class somewhere with the following extension method:
// public static Func<TResult> Memoize<TResult>(this Func<TResult> f)

class Foo
{
  public Func<int,int> Fibonacci = fib;

  public Foo()
  {
    Fibonacci = Fibonacci.Memoize();
  }

  public int fib(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

メモ化拡張メソッドの1つに一致するタグ付きメソッドが見つかったら、このコードを吐き出すコードジェネレーターを作成する方が簡単ではないかと思いました。したがって、この配管コードを記述する代わりに、属性を追加するだけで済みます。

class Foo
{
  [Memoize]
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

正直なところ、これは実際のコード生成よりもプリプロセッサによって変換されるコンパイラシュガーのように見えることを私は知っていますが、私の質問は次のとおりです。

  1. 特定の属性を持つac#ソースファイル内のメソッドを見つけ、parametertypesとreturntypeを解析し、このフィンガープリントに一致するデリゲートを生成するための最良の方法は何だと思いますか
  2. 実際にコードを上書きせずに、これをビルドプロセスに統合するための最良の方法は何でしょうか。コンパイラに渡す前に、ソースファイルに対していくつかの前処理を行うことは可能ですか?

ありとあらゆるアイデアをありがとう。

更新

Shayが提案したように、Postsharpライブラリを調べましたが、トランザクション管理、トレース、セキュリティなどのタイムクリティカルではないアプリケーションでの作業に非常に適しているようでした。

ただし、タイムクリティカルなコンテキストで使用すると、デリゲートよりもかなり遅いことがわかりました。実装ごとにフィボナッチの例を100万回繰り返すと、実行時間が80倍遅くなりました。(0.012msポストシャープvs 0.00015msデリゲート/コール)

しかし、正直なところ、私がそれを使用するつもりの文脈では、結果は完全に受け入れられます。回答ありがとうございます!

Update2

どうやらPostsharpの作者はリリース2.0に一生懸命取り組んでおり、これにはとりわけ、生成されたコードのパフォーマンスの向上とコンパイル時間が含まれます。

4

4 に答える 4

6

postharpを使用してこのメ​​モ化属性にぶつかります

于 2009-10-04T20:16:42.563 に答える
3

私のプロジェクトでは、次のメモ化関数を使用しました。

public class Foo
{
    public int Fibonacci(int n)
    {
        return n > 1 ? Fibonacci(n - 1) + Fibonacci(n - 2) : n;
    }
}

class Program
{
    public static Func<Т, TResult> Memoize<Т, TResult>(Func<Т, TResult> f) where Т : IEquatable<Т>
    {
        Dictionary<Т, TResult> map = new Dictionary<Т, TResult>();
        return a =>
        {
            TResult local;
            if (!TryGetValue<Т, TResult>(map, a, out local))
            {
                local = f(a);
                map.Add(a, local);
            }
            return local;
        };
    }

    private static bool TryGetValue<Т, TResult>(Dictionary<Т, TResult> map, Т key, out TResult value) where Т : IEquatable<Т>
    {
        EqualityComparer<Т> comparer = EqualityComparer<Т>.Default;
        foreach (KeyValuePair<Т, TResult> pair in map)
        {
            if (comparer.Equals(pair.Key, key))
            {
                value = pair.Value;
                return true;
            }
        }
        value = default(TResult);
        return false;
    }


    static void Main(string[] args)
    {
        var foo = new Foo();
        // Transform the original function and render it with memory
        var memoizedFibonacci = Memoize<int, int>(foo.Fibonacci);

        // memoizedFibonacci is a transformation of the original function that can be used from now on:
        // Note that only the first call will hit the original function
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
    }
}

私のプロジェクトでは、IEquatable <Т>を実装する単一の引数を持つ関数のみが必要でしたが、これはさらに一般化できます。もう1つの重要な注意点は、このコードはスレッドセーフではないということです。スレッドセーフが必要な場合は、内部マップハッシュテーブルへの読み取り/書き込みアクセスを同期する必要があります。

于 2009-10-04T19:59:24.550 に答える
1

具体的にあなたのポイントに対処するには:

  1. 本格的なC#文法パーサーが必要になるため、これを説明する方法で行うのは難しいでしょう。より実行可能な代替案は、コンパイルされたアセンブリをロードし、Reflectionを使用して型情報を抽出できるマネージドアプリを作成することです。これには、特定のアセンブリ内のすべてのTypeオブジェクトを取得し、型のメソッドを探し、カスタム属性を取得してから、メモ化コードを発行することが含まれます(この部分は少し難しいかもしれません)。
  2. #1で説明したルートを使用する場合は、ビルド後の手順を追加するだけでツールを実行できます。Visual Studio(下にMSBuildを使用)を使用すると、これが比較的簡単になります。
于 2009-10-04T20:50:14.820 に答える
1

LAOSライブラリを使用する代わりにPostSharpにプラグインを記述した場合、パフォーマンスに影響はありません。

于 2009-10-07T22:36:26.480 に答える