性能创意(内存中的C#hashset并包含太慢)

时间:2011-02-23 20:33:20

标签: c# performance hashtable contains hashset

我有以下代码

private void LoadIntoMemory()
{
    //Init large HashSet
    HashSet<document> hsAllDocuments = new HashSet<document>();

    //Get first rows from database
    List<document> docsList = document.GetAllAboveDocID(0, 500000);

    //Load objects into dictionary
    foreach (document d in docsList)
    {
        hsAllDocuments.Add(d);
    }

    Application["dicAllDocuments"] = hsAllDocuments;
}

private HashSet<document> documentHits(HashSet<document> hsRawHit, HashSet<document> hsAllDocuments, string query, string[] queryArray)
{
    int counter = 0;
    const int maxCount = 1000;

    foreach (document d in hsAllDocuments)
    {
        //Headline
        if (d.Headline.Contains(query))
        {
            if (counter >= maxCount)
                break;
            hsRawHit.Add(d);
            counter++;
        }

        //Description
        if (d.Description.Contains(query))
        {
            if (counter >= maxCount)
                break;
            hsRawHit.Add(d);
            counter++;
        }

        //splitted query word by word
        //string[] queryArray = query.Split(' ');
        if (queryArray.Count() > 1)
        {
            foreach (string q in queryArray)
            {
                if (d.Headline.Contains(q))
                {
                    if (counter >= maxCount)
                        break;
                    hsRawHit.Add(d);
                    counter++;
                }

                //Description
                if (d.Description.Contains(q))
                {
                    if (counter >= maxCount)
                        break;
                    hsRawHit.Add(d);
                    counter++;
                }
            }
        }

    }

    return hsRawHit;
}       

首先我将所有数据加载到一个哈希集中(通过Application供以后使用) - 运行正常 - 完全可以慢慢做我正在做的事情。

将在C#中运行4.0框架(无法使用异步内容更新到4.0的新升级)。

documentHits方法在我当前的设置上运行得相当慢 - 考虑到它全部在内存中。我该怎么做才能加速这种方法?

示例很棒 - 谢谢。

4 个答案:

答案 0 :(得分:7)

我发现您使用的是HashSet,但您没有使用它的任何优势,所以您应该只使用List

花费时间循环遍历所有文档并在其他字符串中查找字符串,因此您应该尝试尽可能地消除它。

一种可能性是设置哪些文档包含哪些字符对的索引。如果字符串query包含Hello,您将查看包含Heellllo的文档。

您可以设置Dictionary<string, List<int>>,其中字典键是字符组合,列表包含文档列表中文档的索引。当然,设置字典需要一些时间,但您可以专注于不太常见的字符组合。如果80%的文档中存在字符组合,则对于消除文档是没有用的,但如果只有2%的文档中存在字符组合,则它会消除98%的工作。

如果循环遍历列表中的文档并将字母添加到字典中的列表中,则将对索引列表进行排序,以便以后加入列表非常容易。当您向列表添加索引时,您可以在列表过大时丢弃列表,并且您发现它们对于消除文档没有用处。这样你只会保留较短的列表而且不会占用太多内存。

编辑:

它汇集了一个小例子:

public class IndexElliminator<T> {

  private List<T> _items;
  private Dictionary<string, List<int>> _index;
  private Func<T, string> _getContent;

  private static HashSet<string> GetPairs(string value) {
    HashSet<string> pairs = new HashSet<string>();
    for (int i = 1; i < value.Length; i++) {
      pairs.Add(value.Substring(i - 1, 2));
    }
    return pairs;
  }

  public IndexElliminator(List<T> items, Func<T, string> getContent, int maxIndexSize) {
    _items = items;
    _getContent = getContent;
    _index = new Dictionary<string, List<int>>();
    for (int index = 0;index<_items.Count;index++) {
      T item = _items[index];
      foreach (string pair in GetPairs(_getContent(item))) {
        List<int> list;
        if (_index.TryGetValue(pair, out list)) {
          if (list != null) {
            if (list.Count == maxIndexSize) {
              _index[pair] = null;
            } else {
              list.Add(index);
            }
          }
        } else {
          list = new List<int>();
          list.Add(index);
          _index.Add(pair, list);
        }
      }
    }
  }

  private static List<int> JoinLists(List<int> list1, List<int> list2) {
    List<int> result = new List<int>();
    int i1 = 0, i2 = 0;
    while (i1 < list1.Count && i2 < list2.Count) {
      switch (Math.Sign(list1[i1].CompareTo(list2[i2]))) {
        case 0: result.Add(list1[i1]); i1++; i2++; break;
        case -1: i1++; break;
        case 1: i2++; break;
      }
    }
    return result;
  }

  public List<T> Find(string query) {
    HashSet<string> pairs = GetPairs(query);
    List<List<int>> indexes = new List<List<int>>();
    bool found = false;
    foreach (string pair in pairs) {
      List<int> list;
      if (_index.TryGetValue(pair, out list)) {
        found = true;
        if (list != null) {
          indexes.Add(list);
        }
      }
    }
    List<T> result = new List<T>();
    if (found && indexes.Count == 0) {
      indexes.Add(Enumerable.Range(0, _items.Count).ToList());
    }
    if (indexes.Count > 0) {
      while (indexes.Count > 1) {
        indexes[indexes.Count - 2] = JoinLists(indexes[indexes.Count - 2], indexes[indexes.Count - 1]);
        indexes.RemoveAt(indexes.Count - 1);
      }
      foreach (int index in indexes[0]) {
        if (_getContent(_items[index]).Contains(query)) {
          result.Add(_items[index]);
        }
      }
    }
    return result;
  }

}

测试:

List<string> items = new List<string> {
  "Hello world",
  "How are you",
  "What is this",
  "Can this be true",
  "Some phrases",
  "Words upon words",
  "What to do",
  "Where to go",
  "When is this",
  "How can this be",
  "Well above margin",
  "Close to the center"
};
IndexElliminator<string> index = new IndexElliminator<string>(items, s => s, items.Count / 2);

List<string> found = index.Find("this");
foreach (string s in found) Console.WriteLine(s);

输出:

What is this
Can this be true
When is this
How can this be

答案 1 :(得分:2)

你在所有文档中线性运行以找到匹配项 - 这是O(n),如果你解决了逆问题,你可以做得更好,类似于全文索引的工作原理:从查询术语开始并预处理集合与每个查询字词匹配的文档 - 由于这可能会变得复杂,我建议只使用具有全文功能的数据库,这将比您的方法快得多。

你也在滥用HashSet - 而只是使用List,而不是重复 - 所有产生匹配的documentHits()中的所有情况都应该是独占的。

答案 2 :(得分:0)

如果您在开始时有大量时间来创建数据库,则可以考虑使用 Trie

Trie 会使字符串搜索更快。

end here中有一些解释和实现。

另一项实施:Trie class

答案 3 :(得分:0)

您不应该针对所有测试步骤测试每个文档!

相反,您应该在第一次成功的测试结果后转到下一个文档。

hsRawHit.Add(d);
counter++;

你应该在 counter ++;

之后添加继续;
hsRawHit.Add(d);
counter++;
continue;