技术开发 频道

谈表达式树的缓存:五种缓存方式的性能比较

试验代码

    首先,自然是获取用户输入参数,方法同上:

static void PerfTest(NameValueCollection args)
{
    string intput = args["in"] ?? "expressions.txt";
    int repeat = Int32.Parse(args["repeat"] ?? "100");
 

  现在便要读入表达式了。解析表达式不是一件容易的事情,我们这里使用ScottGu所提过的Dynamic Query Library,解析一个表达式就再容易不过了:

List<Expression> expressions = File.ReadAllLines(intput).Select(
        s => DynamicExpression.Parse(null, s)).ToList();


  接着,准备5种缓存容器:

 var caches = new SortedDictionary<string, IExpressionCache<object>>()
    {
        { "1. SimpleKeyCache", new SimpleKeyCache<object>() },
        { "2. PrefixTreeCache", new PrefixTreeCache<object>() },
        { "3. SortedListCache", new SortedListCache<object>() },
        { "4. HashedListCache", new HashedListCache<object>() },
        { "5. DictionaryCache", new DictionaryCache<object>() },
    };
  初始化CodeTimer:

    CodeTimer.Initialize();

  遍历字典中的每个缓存对象,将其放入缓存容器中。这段代码还有一个作用便是“热身”——请注意,对.NET中任意代码作性能测试时,都需要让它预运行一下。由于JIT的存在,一个方法第一次运行时所花时间总是较长的,这不应该统计在内:

    var value = new object();
    foreach (var pair in caches)
    {
        var cache = pair.Value;
        expressions.ForEach(exp => cache.Get(exp, (_) => value));
  最后,则是使用CodeTimer对当前缓存容器进行性能测试:

        CodeTimer.Time(pair.Key, repeat,
            () => expressions.ForEach(exp => cache.Get(exp, null)));
    }
}
 

  PerfTest编写完毕,我们最后还需要指定一个函数的入口,如下:

static void Main(string[] args)
{
    var arguments = ParseArguments(args);

    if (arguments["task"] == "test")
    {
        PerfTest(arguments);
    }
    else
    {
        GenerateExpressions(arguments);
    }
}
 

  如果直接执行程序,便会创建一个expression.txt文件,其中包括操作符数量在11到20之间,每种表达式各100个。如果加上了task参数,并指定test字符串,便会进行性能比较。

比较结果
  输入命令,便会使用expression.txt中的每个表达式各调用100次:

Benchmark.exe /task:test
  对于运算符数量为11到20的表达式各100个(即总共1000个表达式),各调用100次的结果如下(不过,请不要直接看结果,再想想,再想想):

1. SimpleKeyCache
        Time Elapsed:   2,807ms
        CPU Cycles:     7,090,489,283
        Gen 0:          412
        Gen 1:          0
        Gen 2:          0

2. PrefixTreeCache
        Time Elapsed:   2,391ms
        CPU Cycles:     6,039,211,085
        Gen 0:          142
        Gen 1:          0
        Gen 2:          0

3. SortedListCache
        Time Elapsed:   1,939ms
        CPU Cycles:     4,903,538,425
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

4. HashedListCache
        Time Elapsed:   1,237ms
        CPU Cycles:     3,129,685,287
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

5. DictionaryCache
        Time Elapsed:   1,219ms
        CPU Cycles:     3,076,107,042
        Gen 0:          3
        Gen 1:          1
        Gen 2:          0
 


  结果和您想象的是否一样?在老赵的机器上,这个结果还是相当稳定的,每次测试只差十几毫秒,而垃圾收集次数则完全一样。从这个数据中您看出什么来了吗?或者说,您能否回答以下几个问题呢?

SimpleKeyCache的垃圾收集次数为什么明显较多?PrefixTreeCache为什么也有不少垃圾收集?
SimpleKeyCache和PrefixTreeCache的时间复杂度都是理论最优值O(m),但是为什么它们却比不过SortedListCache这个理论上时间复杂度是O(m * log(n))的容器呢?
您能否设定一种用例,让SortedListCache的耗时超过PrefixTreeCache或SimpleKeyCache呢?
HashedListCache为什么会超过SortedListCache,DictionaryCache的性能为什么也那么好呢(与HashedListCache不分伯仲,多次测试互有“胜负”)?
DictionaryCache有一次1代的垃圾收集,这说明DictionaryCache消耗内存超过前些容器吗?
SimpleKeyCache从时间和空间上看全面落后,那么他有什么好处吗?
您能为每种容器提出改进意见吗?
  您是否还能提出更多问题?您能够在老赵发布下一篇文章讨论这些问题之前,在这里留言给出您对这些问题的看法呢?

原文地址:http://www.cnblogs.com/jeffreyzhao/archive/2009/05/26/expression-cache-6-perf-test.html

0
相关文章