【问题标题】:Fast multi-key lookup without Dictionary?没有字典的快速多键查找?
【发布时间】:2012-11-27 21:27:05
【问题描述】:

我有一个 Id 映射缓存占用了太多内存。它用于容纳对象的 3 种不同类型的 Id 的组合,它们的映射从表中读取,并缓存在 6 个不同的字典中,以便从任何一种 Id 类型快速查找/翻译到另一种(性能是对我的申请很重要)。

我想将其重写为内存占用更小的东西,所以我确实实现了一个 Id 的合并列表,并使用 linq/lambda 表达式来提取我想要的值。目前看起来是这样的。

public struct IdMappings
{
     public int Id1;
     public int Id2;
     public int Id3;
}

//new cache    
private static List<IdMappings> AllIdMappings = null;

//current cache implementation
private static Dictionary<int, int> Id1ToId2 = null;
private static Dictionary<int, int> Id1ToId3 = null;
//etc.

public static void FillCache(DataSet data)
{
     foreach (DataRow r in data.Tables[0].Rows)
     {
          //fill list and/or dictionaries with id's
     }
}

查询示例如下:

public static int GetId2FromId1(int id1)
{
    return AllIdMappings.FirstOrDefault(m => m.Id1 == id1).Id2;
    //or
    return Id1ToId2[id1];
}

这在减少内存使用方面满足了我的需求,但查找性能因此受到影响,因此我正在研究如何实现不同的东西。有没有办法进行多索引键或多键查找比遍历列表相对更快?

【问题讨论】:

  • This does what I need in terms of reducing memory usage, but performance for lookups has suffered as a result - 这可能是因为您不再在内存中准备对象,因此一旦找到 ID 就需要从数据库中检索它们?即它是延迟加载而不是急切加载。您确定性能下降发生在“查找”而不是“检索”中吗?
  • 缓存在应用启动时被填充,所以只有一次。字典/哈希集是为快速查找而设计的,大约为 O(1)。我确实相信使用带有 linq 的列表将始终是 O(n),因为它会遍历整个列表以找到匹配项。
  • 如果这是对我的评论的回应,当您说“缓存已满”时,您是指objects 还是keys?如果您预先检索对象,您不妨创建一个对象字典而不是它们的键 - 没有内存优势。而如果你不检索对象,那么在找到合适的键后,你需要从数据库中获取相应的对象,这将导致更长的执行时间。

标签: c# linq search .net-4.0 lookup


【解决方案1】:

如果添加这三个字典:

private static Dictionary<int, IdMappings> Id1Lookup = null;
private static Dictionary<int, IdMappings> Id2Lookup = null;
private static Dictionary<int, IdMappings> Id3Lookup = null;

如果字典值是相同的引用,它应该使用最少的内存,但保持与原始实现相同的查找速度。

如果我的想法是正确的,这应该使用您的 6 字典解决方案的一半内存,但使用 List&lt;IdMappings&gt; 类型解决方案的两倍。

正如@SWeko 指出的那样,IdMappings 必须是class 而不是struct,以确保使用引用指针而不是它的副本。

【讨论】:

  • 为此,OP 还可能需要将 IdMappings 重新定义为 class 而不是 struct
【解决方案2】:

是的,对列表进行排序并使用二分查找(List 已经在 Find 方法中为您实现了这一点) 然后在 O(logn) 中完成维护排序列表和查找。

【讨论】:

    【解决方案3】:

    一个潜在的性能改进可能是使用Hashset&lt;IdMappings&gt; 而不是List&lt;IdMappings&gt;,但这主要有助于直接查找,而不是FirstOrDefault,它或多或少地按顺序迭代列表。

    如果您的查找全部来自 ID1 -> ID2 和 ID3 方向,您可以使用 Dictionary&lt;int, Tuple&lt;int, int&gt;&gt; 作为键,这样可以从当前字典中消除额外的 ID1 值。

    不管怎样,缓存顾名思义就是用内存换取查找速度,所以我认为你不能提高内存消耗。

    【讨论】:

    • 它们是 6 个不同的查找,因此原始实现有 6 个不同的字典:id1 -> id2, id1 -> id3, id2 -> id1, id2 -> id3, id3 -> id1, id3 -> id2
    • @Tom:那么您将需要三个Dictionary&lt;int, Tuple&lt;int, int&gt;,这将(我认为)将您的内存占用减少约 25%。
    • 大致如此。这与上面胸腺嘧啶的答案相同,这很好。
    【解决方案4】:

    也许你最好的选择是创建一个映射结构:

    struct Mapping: IComparable<Mapping>
    {
        private readonly int FromId;
        private readonly int ToId;
        public Mapping(int fid, int tid);
        // implement the IComparable.Compare method to compare FromId
    }
    

    然后,为每个索引创建一个List&lt;Mapping&gt;,并对列表进行排序。然后您可以使用List.Find 找到您想要的项目。

    【讨论】:

      猜你喜欢
      • 2017-02-05
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 2019-10-16
      • 2017-11-11
      相关资源
      最近更新 更多