【问题标题】:Performance of my implementation of IComparer我实现 IComparer 的性能
【发布时间】:2011-07-16 00:15:54
【问题描述】:

我正在对“文档”类型的集合(通常大约 10 万条记录)进行排序。排序通常需要大约 4-5 秒,我想知道是否有办法通过修改实现 IComparer(Of Document) 的“DocumentComparer”类来加快排序。由于 Compare() 方法将被调用数十万次,是否有任何我忽略的性能改进?

Public Class DocumentComparer
    Implements IComparer(Of Document)

    Private _Prop As PropertyDescriptor
    Private _Properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(GetType(Document))
    Private _SortDirection As ListSortDirection
    Private _PropertyType As Type

    Public ReadOnly Property Prop As PropertyDescriptor
        Get
            Return _Prop
        End Get
    End Property

    Public ReadOnly Property SortDirection As ListSortDirection
        Get
            Return _SortDirection
        End Get
    End Property

    Public Sub New(ByVal prop As PropertyDescriptor, ByVal sortDirection As ListSortDirection)
        _Prop = prop
        _SortDirection = sortDirection
    End Sub

    Public Function Compare(ByVal x As Document, ByVal y As Document) As Integer Implements System.Collections.Generic.IComparer(Of Document).Compare
        Dim xPropertyValue As Object
        Dim yPropertyValue As Object
        Dim compareValue As Integer = 0

        Try
            xPropertyValue = _Prop.GetValue(x)
            yPropertyValue = _Prop.GetValue(y)

            If _Prop.Name = "Revision" Then
                ' When sorting by the revision, actually sort by the RevisionSort property from the Document class.
                _Prop = _Properties.Item("RevisionSort")
                xPropertyValue = _Prop.GetValue(x)
                yPropertyValue = _Prop.GetValue(y)
                compareValue = xPropertyValue.ToString().CompareTo(yPropertyValue.ToString())
            ElseIf _Prop.Name = "ReleaseDate" Then
                If xPropertyValue Is Nothing And yPropertyValue Is Nothing Then
                    compareValue = 0
                ElseIf xPropertyValue Is Nothing Then
                    compareValue = -1
                ElseIf yPropertyValue Is Nothing Then
                    compareValue = 1
                Else
                    compareValue = DirectCast(xPropertyValue, DateTime).CompareTo(DirectCast(yPropertyValue, DateTime))
                End If
            ElseIf xPropertyValue Is Nothing And yPropertyValue Is Nothing Then
                Return 0
            ElseIf xPropertyValue Is Nothing And yPropertyValue IsNot Nothing Then
                compareValue = -1
            ElseIf xPropertyValue IsNot Nothing And yPropertyValue Is Nothing Then
                compareValue = 1
            ElseIf _Prop.PropertyType Is GetType(String) Then
                ' If we are sorting string values...that's easy.  Just call the String's CompareTo() method.
                compareValue = xPropertyValue.ToString().CompareTo(yPropertyValue.ToString())
            ElseIf _Prop.PropertyType Is GetType(DateTime) Then
                ' If we are sorting by a DateTime column then just call the DateTime's CompareTo() method.
                compareValue = DirectCast(xPropertyValue, DateTime).CompareTo(DirectCast(yPropertyValue, DateTime))
            ElseIf _Prop.PropertyType Is GetType(Integer) Then
                compareValue = DirectCast(xPropertyValue, Integer).CompareTo(DirectCast(yPropertyValue, Integer))
            Else
                ' Future expansion of comparison of different types
                Throw New NotImplementedException("Datatype of column cannot be compared.")
            End If
        Catch ex As Exception
            DocDbModelException.AddObjectToExceptionData(Me, ex)
            Throw New DocDbModelException(String.Format("Failed comparing objects:  {0}", ex.Message), ex, True)
        End Try

        If _SortDirection = ListSortDirection.Ascending Then
            Return compareValue
        Else
            Return -compareValue
        End If
    End Function
End Class

【问题讨论】:

    标签: vb.net visual-studio


    【解决方案1】:

    这段代码中让我想到的大问题是:

    • 使用反射
    • 大型 if/else 列表
    • Compare 方法(被调用数十万次)中执行检查,这些检查可以在对象初始化时确定,或者至少在 Compare 方法第一次被调用时懒惰地确定

    那么,例如,这样的事情怎么样?

    Public Class DocumentComparer
        Implements IComparer(Of Document)
    
        Private _Prop As PropertyDescriptor
        Private _SortDirection As ListSortDirection
        Private _Comparer As Func(Of Document, Document, Integer)
    
        Public ReadOnly Property Prop() As PropertyDescriptor
            Get
                Return _Prop
            End Get
        End Property
    
        Public ReadOnly Property SortDirection() As ListSortDirection
            Get
                Return _SortDirection
            End Get
        End Property
    
        Shared _ComparersByName As ConcurrentDictionary(Of String, Func(Of Document, Document, Integer))
        Shared Sub New()
    
            Dim dict = New Dictionary(Of String, Func(Of Document, Document, Integer))() From { _
                {"Revision", Function(x, y) String.Compare(x.RevisionSort, y.RevisionSort)}, _
                {"ReleaseDate", Function(x, y) Nullable.Compare(Of DateTime)(x.ReleaseDate, y.ReleaseDate)} _
                ' add remaining sort functions here
            }
            _ComparersByName = New ConcurrentDictionary(Of String, Func(Of Document, Document, Integer))(dict)
    
    
        End Sub
    
        Public Sub New(prop As PropertyDescriptor, sortDirection As ListSortDirection)
            _SortDirection = sortDirection
            _Comparer = _ComparersByName(prop.Name)
        End Sub
    
        Public Function Compare(x As Document, y As Document) As Integer
            Try
                Dim compareResult As Integer = _Comparer(x, y)
                Return If(_SortDirection = ListSortDirection.Ascending, compareResult, -compareResult)
            Catch ex As Exception
                Throw New Exception(String.Format("Failed comparing objects:  {0}", ex.Message), ex)
            End Try
        End Function
    End Class
    

    更新

    如果您不想显式处理每个属性,这会更复杂,但肯定有选择。您可以创建另一个共享/静态字典作为备用字典,该字典以属性 Type 而非其名称为键。由于您在编译时不知道属性,因此每个键的值不能只是您在上面示例中看到的简单比较函数。相反,您必须做出选择:

    • 该值可以是在给定属性时可以构建表达式树的函数,然后您可以将其编译为比较函数(请参阅http://msdn.microsoft.com/en-us/library/bb397951.aspx)。首先编译函数是一个昂贵的过程,因此您可能希望拥有另一个共享/静态字典作为每个属性的已编译函数的缓存。 专业人士:一旦你编译了这个函数,它就会运行得非常快。 缺点:生成表达式树很繁琐,通常涉及大量代码。
    • 该值可能是使用反射的函数,类似于您在原始问题中使用的代码。 专业人士:代码更简单,而且您几乎已经拥有所需的代码。 缺点:虽然这确实消除了大的 if/else 列表,但它可能不会给您带来几乎一样多的性能差异,因为您仍在使用反射。

    最后一点:

    1. 在适当的情况下使用string.CompareNullable.Compare<T> 以避免使用空检查使代码复杂化。在比较不可为空的类型(intDateTime 等)时,不要为空检查而烦恼,因为它们不是必需的。

    【讨论】:

    • 这速度快得令人难以置信,几乎是瞬间完成!感谢您提供完美的解决方案。
    • 我在弄清楚如何对剩余属性进行排序时遇到了一些麻烦。如果我最终将其他属性添加到 Document 类,我不想维护这个 Comparer。我如何对其他属性进行排序,而不必在这里明确定义它们(注意有些可能是空值)?
    【解决方案2】:

    在实际需要之前不要初始化_Properties,这可能会有所帮助。 TypeDescriptor 使用反射总是会增加性能。

    实际上,(查看您的其他帖子),您是否有实际需要使用TypeDescriptor 或者您可以只使用真实属性的属性而不进行反射。反射就像试图找出鞋子的颜色,将鞋子脱下来放在眼睛上,而你可以低头看。

    【讨论】:

      猜你喜欢
      • 2010-10-30
      • 2021-12-05
      • 1970-01-01
      • 2011-05-09
      • 2017-08-07
      • 1970-01-01
      • 2012-08-01
      • 2019-02-26
      • 2021-02-15
      相关资源
      最近更新 更多