【问题标题】:Fast random access to a collection快速随机访问集合
【发布时间】:2016-06-06 00:37:39
【问题描述】:

我正在使用半随机令牌流。对于每个令牌,我都维护了大量数据(包括一些子集合)。

唯一令牌的数量是无限的,但实际上往往在 100,000-300,000 左右。

我从一个列表开始,并使用 Linq 查询确定了要更新的适当令牌对象。

public class Model {
    public List<State> States { get; set; }
    ...
}

var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();

在前约 3 万个唯一令牌中,我能够找到并更新约 1,100 个令牌/秒。

性能分析表明,总 Cpu 周期的 85% 都花在了 Where(...).SingleOrDefault() 上(这是有道理的,列表是一种低效的搜索方式)。

因此,我将列表切换到 HashSet 并再次进行分析,确信 HashSet 能够更快地随机搜索。这一次,我每秒只处理约 900 个令牌。在 Linq 上花费的时间几乎相同 (89%)。

所以...首先,我是否误用了HashSet? (使用 Linq 是否强制转换为 IEnumerable,然后是枚举/类似的东西?)

如果不是,实现自己的最佳模式是什么?我的印象是 HashSet 已经进行了二进制搜索,所以我认为我需要构建某种树结构并拥有更小的子集?

回答 cmets 的一些问题...条件是唯一的(如果我两次获得相同的令牌,我想更新相同的条目),HashSet 是股票 .Net 实现 (System.Collections.Generic.HashSet&lt;T&gt;)。

更广泛的代码视图是......

        var state = new RollingList(model.StateDepth); // Tracks last n items and drops older ones. (Basically an array and an index that wraps around
        var tokens = tokeniser.Tokenise(contents); // Iterator
        foreach (var token in tokens) {
            var stateText = StateToString(ref state);
            var match = model.States.Where(x => x.Condition == stateText).FirstOrDefault();
            // ... update the match as appropriate for the token
        }

【问题讨论】:

  • x.Condition 是唯一的吗?
  • 那么你的 HashSet 实现在哪里呢?
  • 如果您确实想对T 的实例执行集合操作,那么您可以从使用HashSet&lt;T&gt; 中受益,但当您必须遍历所有项目以检查 T- 的某个属性时则不然实例具有一定的价值。如果您想要快速查找,您应该使用字典,其中使用 Condition 属性作为键。
  • 显示您希望我们评论的代码。
  • 哈希集不进行二分查找。他们使用哈希算法。因此得名。

标签: c# performance search collections random-access


【解决方案1】:
var match = model.States.Where(x => x.Condition == stateText).SingleOrDefault();

如果您使用哈希集做同样的事情,那就没有任何节省。哈希集经过优化,可以快速回答“这个成员在集合中吗?”这个问题。不是“是否有一个成员使这个谓词在集合中为真?”后者无论是散列集还是列表都是线性时间。

满足您需求的可能数据结构:

  • 制作从文本到状态的字典映射,然后在字典中搜索文本键以获得结果状态。这是理论上搜索和插入的 O(1) ;在实践中,它取决于哈希的质量。

  • 进行从文本到状态的排序字典映射。再次,搜索文本。排序字典使键在平衡树中排序,因此搜索和插入的时间为 O(log n)。

【讨论】:

  • 如果您有兴趣,我已经设法根据您的建议,使用 OrderedDictionary(~9,960 令牌/秒)将速度提高了一个数量级。瓶颈现在在别处。感谢您抽出宝贵时间。
  • 不错!乐于助人。
【解决方案2】:

30k 并没有那么多,所以如果 state 是唯一的,你可以做这样的事情。 字典访问要快得多。

var statesDic = model.States.ToDictionary(x => x.Condition, x => x);
var match = statesDic.ConstainsKey(stateText) ? statesDic[stateText] : default(State);

引用 MSDN:

Dictionary 泛型类提供从一组键到一组值的映射。字典中的每个添加都包含一个值及其关联的键。使用其键检索一个值非常快,接近 O(1),因为 Dictionary 类是作为哈希表实现的。

您可以找到更多关于字典的信息here。 还要注意字典使用内存空间来提高性能,您可以对 300k 项进行快速测试,看看我在说什么类型的空间:

var memoryBeforeDic = GC.GetTotalMemory(true);
var dic = new Dictionary<string,object>(300000);
var memoryAfterDic = GC.GetTotalMemory(true);
Console.WriteLine("Memory: {0}", memoryAfterDic - memoryBeforeDic);

【讨论】:

  • 问题永远不会在搜索时间,字典的唯一问题是它们消耗大量内存。他们使用内存空间来提高性能,您可以进行快速测试以查看具有 300k 单位的字典消耗的总内存,执行类似于我将在我的问题中编辑的内容
  • 您可以使用 TryGetValue 而不是 contains + get 来保存查找。
猜你喜欢
  • 2017-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多