【问题标题】:Fastest way to filter a dictionary and "simplify" its values in C#在 C# 中过滤字典并“简化”其值的最快方法
【发布时间】:2018-01-16 16:44:15
【问题描述】:

在 C# 中,给定一个 SortedDictionary,我需要过滤它的键,然后“简化”它的值。下面的 MWE 可以很好地解释这一点,它完全符合我的要求

static void Main()
{
    var lowerBound = new DateTime(2018, 01, 02);
    var upperBound = new DateTime(2018, 01, 04);

    var myInput = new SortedDictionary<DateTime, SimpleItem>();

    myInput.Add(new DateTime(2018, 01, 01), new SimpleItem { item1 = 1.1, item2 = 2.1 });
    myInput.Add(new DateTime(2018, 01, 02), new SimpleItem { item1 = 1.2, item2 = 2.2 });
    myInput.Add(new DateTime(2018, 01, 03), new SimpleItem { item1 = 1.3, item2 = 2.3 });
    myInput.Add(new DateTime(2018, 01, 04), new SimpleItem { item1 = 1.4, item2 = 2.4 });
    myInput.Add(new DateTime(2018, 01, 05), new SimpleItem { item1 = 1.5, item2 = 2.5 });
    myInput.Add(new DateTime(2018, 01, 06), new SimpleItem { item1 = 1.6, item2 = 2.6 });
    myInput.Add(new DateTime(2018, 01, 07), new SimpleItem { item1 = 1.7, item2 = 2.7 });

    var q = myInput.Where(x => x.Key >= lowerBound && x.Key <= upperBound);

    Dictionary<DateTime, double> d = 
                  q.ToDictionary(x => x.Key, x => x.Value.item1);

    SortedDictionary<DateTime, double> myOutput = 
                  new SortedDictionary<DateTime, double>(d);

    int wait = 0;
}

class SimpleItem
{
    public double item1 { get; set; }
    public double item2 { get; set; }
}

通过分析我的实际代码(不是这个 MWE),很明显ToDictionary 非常非常慢(所有其他部分看起来都还可以)。所以我只是要求另一种方式(希望是最快的)来做同样的事情。

【问题讨论】:

  • 你有多少个元素,“非常慢”是什么意思?
  • 如果您不需要 modify 生成的字典(只需从中读取值),我的建议是围绕它创建一个包装器:A new class which implements IDictionary&lt;DateTime, double&gt;IEnumerable&lt;KeyValuePair&lt;DateTime, double&gt;&gt;(或您实际需要的 SortedDictionary 的任何部分)“过滤”数据请求,如果在请求范围内,则将它们转发到“真实”字典。
  • 加入 Heinzi 的建议和不可变的字典/包装器,你就准备好了!
  • 此 MWE 是否真实表示将使用的数据? “简单物品”代表什么?我问这个的原因是“简单项目”分配占用的内存大约是它们所代表的日期的两倍。您是否尝试过使用浮点数而不是双精度数?
  • 非常慢,我的意思是配置文件显示 80-90% 的时间花在 ToDictionary 上——这是通过运行一个执行许多其他操作的程序。 SimpleItem 只是为了获得一个 MWE,实际上我有一个简单的 cals 有一些双打,一些字符串等等,但最后我只需要获得一个双打。

标签: c# linq dictionary sorteddictionary


【解决方案1】:

您的问题是您对SortedDictionary 的过滤没有利用它已排序的事实。由于ICollection(以及一般的 C# 泛型集合)没有实现任何类型的高效拼接操作,因此查找是您最好的选择。

转动你的过滤器,你会得到:

var q = Enumerable.Range(0, (Int32)(upperBound - lowerBound).TotalDays+1).Select(n => new { Key = lowerBound.AddDays(n), Item = myInput[lowerBound.AddDays(n)].item1 });

var myOutput = new SortedDictionary<DateTime, double>();

foreach (var pair in q)
    myOutput.Add(pair.Key, pair.Item);

其他方法的平均时间都差不多。在lowerBoundupperBound 中使用非常小的间隔可以使性能提高数千倍。当 myInput 包含 200 万个条目时,即使使用两年跨度,性能也会提高数百倍。

请注意,加速的范围实际上取决于SortedList 中有多少条目,一个小列表不会显示出太大的性能差异。

【讨论】:

  • 非常感谢 - 我很快尝试了它,它似乎显着加快了速度。只是为了确定:我认为你需要 (upperBound - lowerBound).TotalDays + 1 来实现与我的代码完全相同的行为,我理解正确吗?
【解决方案2】:

SortedDictionary 构造函数简单地遍历输入字典的 KeyValuePair 对象并调用.Add()

public SortedDictionary(IDictionary<TKey,TValue> dictionary, IComparer<TKey> comparer) {
    if( dictionary == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
    }

    _set = new TreeSet<KeyValuePair<TKey, TValue>>(new KeyValuePairComparer(comparer));

    foreach(KeyValuePair<TKey, TValue> pair in dictionary) {
        _set.Add(pair);
    }            
}

这意味着您不会通过创建中间字典获得任何收益。您可以编写一个查询来过滤和选择您想要的值,然后通过 ICollection.Add 方法将它们添加到字典中:

var q = myInput.Where(x => x.Key >= lowerBound && x.Key <= upperBound)
               .Select(x=>new KeyValuePair<DateTime,double>(x.Key,x.Value.item1));

var myOutput = new SortedDictionary<DateTime, double>();    
var coll=(ICollection<KeyValuePair<DateTime,double>>)myOutput;

foreach(var pair in q)
{
  coll.Add(pair);
}

SortedDictionary 对于编写 枚举不是线程安全的,这意味着您不能使用 PLINQ 来加快过滤源字典或创建新字典的速度。

【讨论】:

  • 我想知道为什么 C# 明确地实现了ICollection.Add。我的时间似乎表明这并不比ToDictionary/new SortedDictionary 快,而且通常慢一点。
  • @Panagiotis Kanavos:谢谢 - 实际上我试过了,但没有发现加速。请注意,从我的分析来看,该代码的瓶颈是对 ToDictionary 的调用。其他调用(Where 和 SortedDictionary ctor)看起来几乎是免费的
  • @Giulio 这个答案消除了。循环本质上与构造函数的代码相同。如果您没有看到任何加速,则不是ToDictionary 导致延迟,而是过滤。您无法加快 的速度,因为枚举 SortedDictionary 不是线程安全的。否则你可以写 myInput.AsParallel() 来并行化它。
  • @Giulio 没有实际数据大小和性能数据,就不可能回答您的问题。如果要并行化 LINQ 查询,请不要使用 SortedDictionary 或 Dictionary。 SimpleItem 是一个类,这意味着只有对实际对象的引用存储在字典中。您可以将项目存储在可以并行化的数组中,然后将它们添加到 SortedDictionary 中,只需为额外的引用付费。基本上你会使用SortedDictionary 作为索引
  • @Panagiotis Kanavos 基本上可以确定瓶颈是 ToDictionary。探查器表明了这一点。让我再次尝试分析您的代码和配置文件:我猜瓶颈会是 Select?
猜你喜欢
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 2016-12-29
  • 2021-09-18
  • 2018-12-11
  • 1970-01-01
  • 2019-03-13
  • 2012-01-15
相关资源
最近更新 更多