文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

下面的标准测试程序来自一个包含大约500K文档的小型数据库,通过id进行随机加载。 如你所见,我突出显示了一个有问题的地方:

image

我们花了很多时间尽可能优化路由,所以当我看到一个分析器的输出显示我们花了 7-8% 的时间来处理路由问题,我很恼火。

事实上,那是一个谎言。我们大部分时间都在寻找我们应该使用什么数据库。

我决定大量简化代码来得到其中的要点,这也是问题中的热点:

public class ResourceLocator
{
    private readonly Dictionary<string, string> _itemsIgnoreCase 
        = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    public ResourceLocator(IEnumerable<KeyValuePair<string, string>> data)
    {
        foreach (var kvp in data)
        {
            _itemsIgnoreCase[kvp.Key] = kvp.Value;
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public bool TryGetValue(string key, out string value)
    {
        return _itemsIgnoreCase.TryGetValue(key, out value);
    }
}
第 1 段(可获 1.24 积分)

我们不能真正地在像Benchmark.NET这样的程序上进行测试,因为我们需要看看当使用多线程和并发访问时它如何工作。相比单个调用,我们更关心整体性能。

所以我通过旋转32线程来测试,这些线程用以下关键词调用上面的类(用10个不同的值初始化):

  • Oren
  • oren
  • oRen

每个线程在15秒的时间跨度内将处理尽可能多的调用。然后我们合计结果。上面的代码每秒8900万次调用,这是令人印象深刻的。除了这个,实际上可以利用GetCaseInsensitiveHash,这是一个非常高效的内部调用(用C ++编写)。另一方面,我的字符串段代码要慢得多。

第 2 段(可获 1.71 积分)

image

另一方面,在上面的代码中,如果我放弃了OrdinalIgnoreCase,我们得到2.25亿次操作/秒,所以表上中一定有性能提升的空间。

首先尝试引入一些简化的代码——如果在有一个匹配的情况下,我们可以检查它,而且仍然比不区分大小写的版本更快。 代码如下所示:

public class ResourceLocator
{
    private readonly Dictionary<string, string> _itemsIgnoreCase 
        = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, string> _items
      = new Dictionary<string, string>(StringComparer.Ordinal);

    public ResourceLocator(IEnumerable<KeyValuePair<string, string>> data)
    {
        foreach (var kvp in data)
        {
            _items[kvp.Key] = kvp.Value,
            _itemsIgnoreCase[kvp.Key] = kvp.Value;
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public bool TryGetValue(string key, out string value)
    {
        if (_items.TryGetValue(key, out value) == false)
            return _itemsIgnoreCase.TryGetValue(key, out value);
        return false;
    }
}
第 3 段(可获 0.81 积分)

运行混合匹配时,给出了每秒7600万次的性能,并且总是使用案例匹配时,得到了每秒2亿5百万次的性能。这是令人惊叹的,但我们忘了考虑一些事情。这个优化只会在你真的有一个准确的匹配情况下才会发生,但是这种情况并不常见。事实上,我们注意到,在应用这种优化之后,我们创建了一个不同的标准测试程序,其中遇到一个不匹配的情况时,遇到了相同的性能问题。

所以接下来尝试实时学习。 基本思想是我们仍然有两个字典,但是当我们在第一级有一个遗漏时,我们将添加条目到基于搜索区分大小写的字典。这样,我们可以随着时间学习,这样大多数的调用将非常快。代码如下:

第 4 段(可获 1.9 积分)

当我们使用它时得到每秒1.67亿次操作的性能。使用ConcurrentDictionary则会得到每秒180万次操作的性能。

这是实际实现的最终结果:

image

花费的时间从 29.2  秒下降到了 6.3 秒! 使用并发字典仍然有很大的成本,但花费时间下降显示我们找到了问题所在:

image

这是所有的高端框架代码。但我们可以做得更好。不是调用框架,而是通过多个调用,我们可以直接比较内存值,像这样:

image

这一结果是:

第 5 段(可获 1.26 积分)

image

我们把成本从6.3(29.2开始!)秒降低到5秒。

尽管如此,让我们更深入的看这个,好吗?

image

看起来我们在这里找到数据的实际成本现在由对ResourceCache.TryGetValue的调用主导。这里我们做了一个小的改变:

image

因此,在我们的测试运行中节省了超过250毫秒,并共有6.36%的运行时间成本。

改变是什么?

image

黄色中概述的部分是新的。因此,我们现在有一个处理乐观情况的非常简单的方法,而不是用一个很复杂的方法,其余的是被极少调用的不可能的方法。

第 6 段(可获 1.48 积分)

文章评论