首页 > 解决方案 > Linq 在大数据集上的查询性能

问题描述

我正在运行一种方法来对存储在ConcurrentQueue<T>. 在 CPU 性能分析中,主要的影响似乎是:

foreach (Item inSequence in items.Where(w => w.SequenceNumber == i.SequenceNumber && w.Device == i.Device)) {}

对于 1,000 和 10,000,它实际上非常快。在 100,000 个项目时,性能变得至关重要 - 特定Linq查询从占用总运行时 CPU 的 4.5% 到超过总运行时 CPU 的 58% 以上。我假设性能下降是专门由于 的大小造成的ConcurrentQueue,但我不知道该怎么做。如果避免 Linq 查询解决了这个问题,那很好。我只是不知道该怎么做。是否有其他一些性能更高的并发类型?

这是一个 CQ,因为数据是异步构建和读取的。然而,在这个特定的方法中,发生在数据构建之后和数据被读回之前,它在单个线程上运行。

非常松散的样本在这里:https ://dotnetfiddle.net/hjDOva

using System;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    static int count = 100000;

    public static void Main()
    {
        var items = new ConcurrentQueue<Item>();
        var r = new Random();
        for (int i = 0; i < count; i++)
        {
            items.Enqueue(new Item());
        }

        var sw = Stopwatch.StartNew();
        foreach (Item i in items.DistinctBy(d => new { d.SequenceNumber, d.Device }))
            foreach (Item inSequence in items.Where(w => w.Device == i.Device && w.SequenceNumber == i.SequenceNumber))
            {

            }

        Console.WriteLine(sw.Elapsed);
    }
}

public static class Extensions
{
    public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        HashSet<TKey> seenKeys = new HashSet<TKey>();
        foreach (TSource element in source)
        {
            if (seenKeys.Add(keySelector(element)))
            {
                yield return element;
            }
        }
    }
}

public class Item
{
    #region Fields
    protected bool fixDates;
    protected string randomSerial;
    protected decimal amount;
    protected string device;
    protected DateTime depositTime;
    public int SequenceNumber = -1;
    [NonSerialized()]
    protected System.Random rnd = new Random(Int32.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));
    #endregion

    #region Properties
    public bool FixDates
    {
        get
        {
            return this.fixDates;
        }

        set
        {
            this.fixDates = value;
        }
    }

    public string Amount
    {
        get
        {
            return this.amount.ToString();
        }

        set
        {
            this.amount = Convert.ToDecimal(value);
        }
    }

    public string RandomSerial
    {
        get { return randomSerial; }
        set { randomSerial = value; }
    }

    public string Device
    {
        get { return this.device; }
        set { this.device = value; }
    }

    public DateTime DepositTime
    {
        get { return this.depositTime; }
        set { this.depositTime = value; }
    }
    #endregion

    #region Constructors
    public Item()
    {
        fixDates = false;
        RandomSerial = Guid.NewGuid().ToString().Substring(0, 8);
        this.amount = 5.00m;
        this.device = "IC" + rnd.Next(6).ToString();
        this.depositTime = DateTime.Now;
        this.SequenceNumber = rnd.Next(10);
    }
    #endregion
}

但是,它不提供 100,000 个项目所需的内存。

关于使用 CQ 的问题,是的,我知道队列不适合这个。该工具生成数据以测试各种产品类型的进口情况。只有一个产品需要这种方法,Transactionalize(). 大多数时候不使用此代码。

这是一个 CQ,因为系统并行创建对象(当它发生时,这是一个显着的性能改进),并且在大多数情况下,它们也是并行出列的。

标签: c#performancelinq

解决方案


假设下面代码的目的是分组处理项目,每个组具有相同的SequenceNumberDevice

foreach (Item i in items.DistinctBy(d => new { d.SequenceNumber, d.Device }))
    foreach (Item inSequence in items
        .Where(w => w.Device == i.Device && w.SequenceNumber == i.SequenceNumber))
    {

    }

...您可以通过使用这样的 Linq 方法更有效地实现相同的GroupBy目标:

var groups = items.GroupBy(i => (i.SequenceNumber, i.Device));
foreach (IGrouping<(string, string), Item> group in groups)
    foreach (Item inSequence in group)
    {

    }

请注意,我没有使用匿名类型,而是使用了更轻量级ValueTuple的 s 作为键,它们不需要垃圾收集。

如果您还希望以后能够非常有效地搜索特定组,而不是GroupBy使用类似的ToLookup.


推荐阅读