【发布时间】:2022-02-03 02:30:00
【问题描述】:
在用户选择目标参数后,我尝试在 WPF 数据图表中显示很多值。为了实现这一点,我正在使用 WPF 的实时图表(例如:https://lvcharts.net/App/examples/v1/Wpf/Scrollable)并且它运行良好。要更改图表中的值,我必须调用此函数:
' Change values of xAxis
Private Sub ChangeXAxis(axis As Object, title As String, values As Object)
axis.Labels = values ' has to be array or list of values (strings for X, double for Y)
axis.Title = title
End Sub
选择的值在显示之前必须通过时间戳或参数过滤。为此,我正在使用以下功能:
Public Function FilterListForChart(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer)
...
Try
If axis = "X" Then
'Return filtered values for axis
Dim query As IEnumerable = (From rows In values
Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
Select (Math.Round(CDbl(rows(1)), 3))).ToList()
Return query
不幸的是,lvcharts 需要值作为列表或数组(字符串或双精度)来正确显示数据。问题是将 IEnumerable 转换为列表或数组需要很长时间,如果我想显示很多值(例如 >300.000 值需要 10 秒或更长时间)
因此,我尝试了很多不同的事情,就像这里讨论的那样:
- Why IEnumerable slow and List is fast?
- How to replace ToList() with Join()
- Fastest way to convert IEnumerable<T> to List<T> in C#
我尝试了以下选项但没有成功:
- 使用 ToArray()
- 使用 ToList()
- 使用具有定义容量的分配列表
- 用于每个循环而不是查询
- 不四舍五入的过滤器(使用简化的数据处理)
在我的测试中,我得到了以下处理时间:
- 从总共 180.000 个值中筛选出 180.000 个值并显示在图表中:X = 1088ms,Y = 1085ms,总计:2.173ms
- 从 1.800.000 个值中筛选出 180.000 个值并显示在图表中:X = 9919ms,Y = 9983ms,总计:19.902ms
查询和过滤数据只需几毫秒。大部分计算时间用于创建列表/数组。在我的测试中,我只能将计算时间减少几毫秒。
我目前的解决方案是使用
更新:
我又做了五个场景的测试:
Dim yAxis1 = TestSpeed_ForEach(values_YAxis, "Y", counterStart, counterEnd, decimalCut, False)
Dim yAxis2 = TestSpeed_ForEachFixedList(values_YAxis, "Y", counterStart, counterEnd, decimalCut, False)
Dim yAxis3 = TestSpeed_QueryToArray(values_YAxis, "Y", counterStart, counterEnd, decimalCut, False)
Dim yAxis4 = TestSpeed_QueryToList(values_YAxis, "Y", counterStart, counterEnd, decimalCut, False)
Dim yAxis5 = TestSpeed_QueryToListParallel(values_YAxis, "Y", counterStart, counterEnd, decimalCut, False)
我用秒表测试了从 2.300 --> 1.800.000 开始的不同值的函数。如前所述,我无法加快计算时间。固定列表的功能最快,但节省时间仅为 50 - 400 毫秒。以下是对总共 1.800.000 个值中的 27.500 个值的查询结果:
- 时间:Y 轴 1 - 10868 毫秒
- 时间:Y 轴 2 - 10844 毫秒
- 时间:Y 轴 3 - 11311 毫秒
- 时间:Y 轴 4 - 11265 毫秒
- 时间:Y 轴 5 - 11313 毫秒
在第二种情况下,我测试了包含 30.000 或 500.000 个条目的常量列表。但这只会产生很小的影响。
这里是用到的函数:
'################ TESTING ################
Public Function TestSpeed_ForEach(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer, decimalCut As Integer, isTimeAxis As Boolean)
Try
Dim yList As New List(Of Double)
For Each row In values
If CInt(row(3)) > counterStart And CInt(row(3)) < counterEnd Then
yList.Add(Math.Round(CDbl(row.ItemArray(4)), decimalCut))
End If
Next
Return yList
Catch ex As Exception
Return Nothing
End Try
End Function
Public Function TestSpeed_ForEachFixedList(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer, decimalCut As Integer, xIsTimeList As Boolean)
Try
Const capacity As Integer = 30000
Dim yList As New List(Of Double)(capacity)
For Each row In values
If CInt(row(3)) > counterStart And CInt(row(3)) < counterEnd Then
yList.Add(Math.Round(CDbl(row.ItemArray(4)), decimalCut))
End If
Next
Return yList
Catch ex As Exception
Return Nothing
End Try
End Function
Public Function TestSpeed_QueryToList(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer, decimalCut As Integer, isTimeAxis As Boolean)
Try
Dim query As IEnumerable = (From rows In values
Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
Select (Math.Round(CDbl(rows(4)), decimalCut))).ToList()
Return query
Catch ex As Exception
Return Nothing
End Try
End Function
Public Function TestSpeed_QueryToArray(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer, decimalCut As Integer, isTimeAxis As Boolean)
Try
Dim query As IEnumerable = (From rows In values
Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
Select (Math.Round(CDbl(rows(4)), decimalCut))).ToArray()
Return query
Catch ex As Exception
Return Nothing
End Try
End Function
Public Function TestSpeed_QueryToListParallel(values As IEnumerable, axis As String, counterStart As Integer, counterEnd As Integer, decimalCut As Integer, isTimeAxis As Boolean)
Try
Dim query As IEnumerable = (From rows In values
Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
Select (Math.Round(CDbl(rows(4)), decimalCut))).AsParallel.ToList()
Return query
Catch ex As Exception
Return Nothing
End Try
End Function
我还能做些什么来加快速度?
【问题讨论】:
-
尝试仅列举您通过 foreach 过滤的数据。我不相信
ToList()太慢了。是的,最好创建具有适当容量的列表,然后添加项目。也看看PLINQ -
考虑通过循环而不是使用 LINQ 填充列表
-
您是否考虑过为列表预分配底层存储?我希望分配策略是二次的(每次需要扩展时加倍),这将需要对大量项目进行大量重新分配和复制。
List(Of T)有一个构造函数,它接受初始容量,AddRange将从IEnumerable(Of T)添加。如果您事先不知道您将获得多少过滤结果,您将用性能空间换取性能。 -
没有人需要在图表上显示 300k 点。即使你填充了一个 8k 的显示,并且每点使用一个像素,你仍然最多可以显示 7680 个点,而其他 290k+ 个点则不显示。在绘制图表之前大量抽取您的数据
-
@kramambambuli 问题更深,你传入了一个
values As IEnumerable,所以即使在查询之前你已经在对象中装箱了双精度数。每一次访问都是一次拆箱。您需要放松到数据的来源,而是使用IEnumerable(Of Double)来做出任何改变。