【问题标题】:Optimizing loop优化循环
【发布时间】:2017-09-21 21:13:39
【问题描述】:

我正在尝试实现一个工具,该工具可以根据单词的引理对某些字符串进行分组。在初始化期间,我为每个可能的组创建一个字典,其中包含将分组到该键中的单词列表。这是我目前所拥有的:

public Dictionary<string, HashSet<string>> Sets { get; set; }

private void Initialize(IStemmer stemmer)
{
    // Stemming of keywords and groups
    var keywordStems = new Dictionary<string, List<string>>();
    var groupStems = new Dictionary<string, List<string>>();

    foreach (string keyword in Keywords)
    {
        keywordStems.Add(keyword, CreateLemmas(keyword, stemmer));
        foreach (string subset in CreateSubsets(keyword))
        {
            if (subset.Length > 1 && !groupStems.ContainsKey(subset))
            {
                groupStems.Add(subset, CreateLemmas(subset, stemmer));
            }
        }
    }

    // Initialize all viable sets
    // This is the slow part
    foreach (string gr in groupStems.Keys)
    {
        var grStems = groupStems[gr];
        var grKeywords = new HashSet<string>((from kw in Keywords
                                                where grStems.All(keywordStems[kw].Contains)
                                                select kw));
        if (grKeywords.Count >= Settings.MinCount)
        {
            Sets.Add(gr, grKeywords);
        }
    }
}

有什么办法可以加快这种方法的瓶颈?

【问题讨论】:

  • 首先确定这是否瓶颈。因此,您应该测量这段代码真正需要多长时间。你真的想把精力投入到只需要几毫秒的事情上吗?
  • 这确实是个瓶颈。对于 5k+ 字符串,这个特定部分占用了 90% 的运行时间。
  • 代码需要多长时间?你想用多长时间?
  • 您显然有一个关键字列表或一组关键字。为什么不预先计算数据,将其保存在数据库中并加载它?丢失的关键字可以在错过时添加并保存以备将来使用...
  • 3500 个字符串大约需要 24 秒,这部分代码负责其中的 21 个。通过将 List 更改为 HashSet,我设法抓取了大约 7 秒。谢谢!

标签: c# algorithm loops optimization


【解决方案1】:

@mjwills 的回答是个好主意。这似乎是最昂贵的操作:

var grKeywords = new HashSet<string>((
  from kw in Keywords
  where grStems.All(keywordStems[kw].Contains)
  select kw));

建议是利用词干是一个集合这一事实来优化包含。但如果它们是一组,那我们为什么还要反复要求收容呢? 它们是一组;做集合操作。问题是“什么是关键字,使得 grStem 集的每个成员都包含在关键字的词干集中”。 “这个集合的每个成员是否都包含在那个集合中”是 subset 操作。

var grKeywords = new HashSet<string>((
  from kw in Keywords
  where grStems.IsSubsetOf(keywordStems[kw])
  select kw));

IsSubsetOf 的实现针对“两个操作数都是集合”等常见场景进行了优化。并且需要提前退出;如果您的组词干集比关键字词干集更大,那么您不需要检查每个元素;其中一个将丢失。但是您的原始算法无论如何都会检查每个元素,即使您可以提早保释并节省所有时间。

【讨论】:

  • 谢谢!更改为 IsSubsetOf 又花了 3 秒时间。
  • 很好的答案 - 谢谢@EricLippert! IsSubsetOf 看起来很适合这种情况,我不知道。
【解决方案2】:

再次@mjwills 有一个好主意,我会建议一些可能的改进。这里的想法是执行查询,将结果缓存在一个数组中,然后在必要时将其实现为哈希集:

foreach (var entry in groupStems)
{
    var grStems = entry.Value;
    var grKeywords = (WHATEVER).ToArray();
    if (grKeywords.Length >= Settings.MinCount)
        Sets.Add(entry.Key, new HashSet<string>(grKeywords));
}

首先:我实际上怀疑通过用不必要的数组构造替换它来避免不必要的哈希集构造是一种胜利。测量一下看看。

第二:ToList 可以比 ToArray 更快,因为在你知道查询结果集的大小之前就可以构造一个列表。 ToArray 基本上必须先做一个 ToList,然后将结果复制到一个精确大小的数组中。因此,如果 ToArray 没有成功,那么 ToList 可能会成功。或不。测量它。

第三:我注意到如果你喜欢那种风格,整个事情可以重写成一个查询。

var q = from entry in groupStems
        let grStems = entry.Value
        let grKeywords = new HashSet<string>(WHATEVER)
        where grKeywords.Count >= Settings.MinCount
        select (entry.Key, grKeywords);
var result = q.ToDictionary( ... and so on ... )

这可能不会更快,但可能更容易推理。

【讨论】:

  • I actually doubt that avoiding the unnnecessary hash set construction by replacing it with unnecessary array constructions is a win. 这在很大程度上取决于实例化List / ArrayHashSet 的成本(我怀疑HashSet 会因为GetHashCode 调用而相对较慢?),以及if 被调用的次数比例。但是,正如您所说,测量是这里的关键。很重要的一点:ToList vs ToArray
【解决方案3】:

一个建议是改变:

var keywordStems = new Dictionary<string, List<string>>();

到:

var keywordStems = new Dictionary<string, HashSet<string>>();

由于您后来的Contains 电话,这应该会产生影响:

var grKeywords = new HashSet<string>((from kw in Keywords
                                                where grStems.All(keywordStems[kw].Contains)
                                                select kw));

因为ContainsHashSet 上通常比List 更快。

同时考虑改变:

foreach (string gr in groupStems.Keys)
{
    var grStems = groupStems[gr];
    var grKeywords = new HashSet<string>((from kw in Keywords
                                            where grStems.All(keywordStems[kw].Contains)
                                            select kw));
    if (grKeywords.Count >= Settings.MinCount)
    {
        Sets.Add(gr, grKeywords);
    }
}

到:

foreach (var entry in groupStems)
{
    var grStems = entry.Value;
    var grKeywords = (from kw in Keywords
                                          where grStems.All(keywordStems[kw].Contains)
                                          select kw).ToArray();
    if (grKeywords.Length >= Settings.MinCount)
    {
        Sets.Add(entry.Key, new HashSet<string>(grKeywords));
    }
}

通过将HashSet 初始化(与初始化Array 相比相对昂贵)进入 if 语句,那么您可以提高性能如果 if 相对很少输入(在您的 cmets 中,您声明它大约有 25% 的时间输入)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-20
    • 2021-01-21
    • 2017-02-18
    • 2013-07-17
    相关资源
    最近更新 更多