【问题标题】:Fastest way to make list out of IEnumerable in .NET在 .NET 中使用 IEnumerable 列出列表的最快方法
【发布时间】: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 秒或更长时间)

因此,我尝试了很多不同的事情,就像这里讨论的那样:

我尝试了以下选项但没有成功:

  • 使用 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) 来做出任何改变。

标签: .net vb.net linq charts


【解决方案1】:

我同意 cmets。这应该更快假设你的函数返回双列表...

    Const capacity As Integer = 1024 * 512

    Dim query As New List(Of Double)(capacity)

    For Each rows In values
        If CInt(rows(3)) > counterStart AndAlso CInt(rows(3)) < counterEnd Then
            query.Add(Math.Round(CDbl(rows(1)), 3))
        End If
    Next

    Return query

【讨论】:

  • 我试过这个。不幸的是,节省的时间非常少。我对结果发表了评论。
【解决方案2】:

您必须对其进行编辑并在有 ??? 的地方进行更正重点是测试传递给方法的 IEnumerable。

Public Function TestSpeed_ForEachFixedList(values As IEnumerable(Of ???),
                                            axis As String,
                                            counterStart As Integer,
                                            counterEnd As Integer,
                                            decimalCut As Integer,
                                            xIsTimeList As Boolean) As List(Of Double)

    Dim Nvals As List(Of ???) = values.ToList
    Try
        Const capacity As Integer = 300000
        Dim yList As New List(Of Double)(capacity)

        For Each row As ??? In Nvals
            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

【讨论】:

  • 我用values As IEnumerable(of Double)之类的转换值重复了测试。当然它要快得多,但现在计算时间是在加载之前创建列表 TestSpeed_ForEach(...) 代码:Dim query As IEnumerable(Of Double) = From rows In values_YAxis Where True Select CDbl(rows.ItemArray(4)) 将花费零毫秒,使用 @ 创建它的列表987654323@ 将需要 360 毫秒...如您所见,我们有 360 毫秒的计算时间,这与之前几乎相同(获取 2.300 值需要 400 毫秒)
【解决方案3】:

经过多次测试,我找到了计算时间最短的解决方案。

    Try
        If axis = "X" Then
            Dim query = (From rows In values.AsParallel
                         Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
                         Order By CDbl(rows.ItemArray(3)) Ascending
                         Select (String.Format("{0:f" & decimalCut & "}", rows.ItemArray(positionToAsk)))).ToList

            Return query

        ElseIf axis = "Y" Then
            Dim query = (From rows In values.AsParallel
                         Where CInt(rows(3)) > counterStart And CInt(rows(3)) < counterEnd
                         Order By CDbl(rows.ItemArray(3)) Ascending
                         Select (Math.Round(CDbl(rows.ItemArray(4)), decimalCut))).ToList

            Return query
        End If
    Catch ex As Exception
        Return Nothing
    End Try

重要的发现是:

  • 前面的 LINQ 查询必须带有隐式声明(没有 Dim query as IEnumeration...)(感谢 @djv)
  • 在 LINQ 查询中使用 .AsParallel 以使用多个内核
  • 如果要使用列表,应事先为其分配大小 (.Capacity)
  • 在某些情况下,For Each 循环比 LINQ 查询快一些

优化后的查询总共可以在 890 毫秒内从 1.800.000 中检索 27.500 个值(之前是 11.241 毫秒)

【讨论】:

  • 我很想知道 rows(3) 是什么数据类型。
  • @dbasnett 它是以秒为单位的经过时间,所以它是双倍的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-08
  • 1970-01-01
  • 2011-08-25
  • 2011-02-01
  • 2018-11-03
  • 2011-01-30
  • 2012-09-09
相关资源
最近更新 更多