【发布时间】:2020-08-12 02:12:59
【问题描述】:
我正在运行一种方法来事务化存储在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,因为系统会并行创建对象(当它发生时,这是一个显着的性能改进),并且在大多数情况下,它们也是并行出队的。
【问题讨论】:
-
说实话,架构是有缺陷的。你有一个
ConcurrentQueue,它是一个线程安全的 FIFO,但你想过滤它。您应该以一种又一种方式处理队列。如果您只想要任何线程安全的集合,请使用ConcurrentDictionary,将您的序列号和设备设置为Key。如果您确实必须有一个分区的 FIFO,请为每个 SequenceNumber 和 Device 创建一个 CQ。否则你正在做的事情有点违背 CQ 的目的。 -
请向我们展示整个代码集——最好是我们可以自己运行的样本数据集。还请包括所有对象模型。从您的描述中我可以想象出哪里可能存在一些问题,但是除非我看到完整的代码,否则我无法给您答案。
-
我猜它不是您发布的导致问题的代码,就像您在 for 循环或其他地方所做的一样。除了“hrrm,很抱歉听到这个消息”
-
@zaitsman 哈哈,是的,这不会发生:)。然而,这个例子是为了说明需要更多信息的观点。其中可能包括关于适当的数据结构、模型、分析器实际在说什么、我们将其与什么进行比较等方面的长时间讨论。
-
@TheGeneral 如果队列中有 100_000 个项目的并发生产者和消费者,我仍然认为使用 CQ 过滤不是超级有效。
标签: c# performance linq