Kir*_*ril 36 c# performance function memoization
我正在尝试为具有任意数量参数的函数创建一个memoization接口,但我失败了,我觉得我的解决方案不是很灵活.我试图为一个函数定义一个接口,该接口在执行时自动被记忆,每个函数都必须实现这个接口.以下是具有两个参数指数移动平均值函数的示例:
class EMAFunction:IFunction
{
    Dictionary<List<object>, List<object>> map;
    class EMAComparer : IEqualityComparer<List<object>>
    {
        private int _multiplier = 97;
        public bool Equals(List<object> a, List<object> b)
        {
            List<object> aVals = (List<object>)a[0];
            int aPeriod = (int)a[1];
            List<object> bVals = (List<object>)b[0];
            int bPeriod = (int)b[1];
            return (aVals.Count == bVals.Count) && (aPeriod == bPeriod);
        }
        public int GetHashCode(List<object> obj)
        {
            // Don't compute hash code on null object.
            if (obj == null)
            {
                return 0;
            }
            List<object> vals = (List<object>) obj[0];
            int period = (int) obj[1];
            return (_multiplier * period.GetHashCode()) + vals.Count;
        }
    }
    public EMAFunction()
    {
        NumParams = 2;
        Name = "EMA";
        map = new Dictionary<List<object>, List<object>>(new EMAComparer());
    }
    #region IFunction Members
    public int NumParams
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
    public object Execute(List<object> parameters)
    {
        if (parameters.Count != NumParams)
            throw new ArgumentException("The num params doesn't match!");
        if (!map.ContainsKey(parameters))
        {
            //map.Add(parameters,
            List<double> values = new List<double>();
            List<object> asObj = (List<object>)parameters[0];
            foreach (object val in asObj)
            {
                values.Add((double)val);
            }
            int period = (int)parameters[1];
            asObj.Clear();
            List<double> ema = TechFunctions.ExponentialMovingAverage(values, period);
            foreach (double val in ema)
            {
                asObj.Add(val);
            }
            map.Add(parameters, asObj);
        }
        return map[parameters];
    }
    public void ClearMap()
    {
        map.Clear();
    }
    #endregion
}
以下是我对该功能的测试:
private void MemoizeTest()
{
    DataSet dataSet = DataLoader.LoadData(DataLoader.DataSource.FROM_WEB, 1024);
    List<String> labels = dataSet.DataLabels;
    Stopwatch sw = new Stopwatch();
    IFunction emaFunc = new EMAFunction();
    List<object> parameters = new List<object>();
    int numRuns = 1000;
    long sumTicks = 0;
    parameters.Add(dataSet.GetValues("open"));
    parameters.Add(12);
    // First call
    for(int i = 0; i < numRuns; ++i)
    {
        emaFunc.ClearMap();// remove any memoization mappings
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks not-memoized " + (sumTicks/numRuns));
    sumTicks = 0;
    // Repeat call
    for (int i = 0; i < numRuns; ++i)
    {
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks memoized " + (sumTicks/numRuns));
}
更新:
 
感谢您指出我的n00bish错误...我总是忘记在秒表上调用重置!  
我已经看到了另一种memoization的方法 ......它不提供n参数memoization,但是我使用接口的方法并没有多大优势,因为我必须为每个函数编写一个类.有没有合理的方法可以将这些想法合并为更强大的东西?我希望能够更容易地记住一个函数,而不会让用户为他们打算使用的每个函数编写一个类.
Eri*_*ert 86
这个怎么样?首先写一个单参数的memoizer:
static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        if (!d.TryGetValue(a, out r))
        {
            r = f(a);
            d.Add(a, r);
        }
        return r;
    };
}  
直截了当.现在编写一个函数tuplifier:
static Func<Tuple<A, B>, R> Tuplify<A, B, R>(this Func<A, B, R> f)
{
    return t => f(t.Item1, t.Item2);
}
还有一个脱钩器:
static Func<A, B, R> Detuplify<A, B, R>(this Func<Tuple<A, B>, R> f)
{
    return (a, b) => f(Tuple.Create(a, b));
}
现在一个两个参数的记忆器很简单:
static Func<A, B, R> Memoize<A, B, R>(this Func<A, B, R> f)
{
    return f.Tuplify().Memoize().Detuplify();
}
要编写一个三参数的memoizer,请继续遵循以下模式:制作一个3-tuplifier,一个3-untuplifier和一个3-memoizer.
当然,如果你不需要它们,就没有必要使用tuplifiers标称方法:
static Func<A, B, R> Memoize<A, B, R>(this Func<A, B, R> f)
{
    Func<Tuple<A, B>, R> tuplified = t => f(t.Item1, t.Item2);
    Func<Tuple<A, B>, R> memoized = tuplified.Memoize();
    return (a, b) => memoized(Tuple.Create(a, b));
}
更新:你问如果没有元组类型该怎么办.你可以写自己的; 这并不难.或者您可以使用匿名类型:
static Func<T, R> CastByExample<T, R>(Func<T, R> f, T t) { return f; }
static Func<A, B, R> Memoize<A, B, R>(this Func<A, B, R> f)
{
    var example = new { A=default(A), B=default(B) };
    var tuplified = CastByExample(t => f(t.A, t.B), example);
    var memoized = tuplified.Memoize();
    return (a, b) => memoized(new {A=a, B=b});
}
光滑,嗯?
更新:C#7现在具有内置于该语言的值元组; 使用它们而不是滚动自己或使用匿名类型.