【问题标题】:What effect does DirectCast have an performance and late/early binding?DirectCast 对性能和后期/早期绑定有什么影响?
【发布时间】:2015-06-27 07:02:31
【问题描述】:

我一直认为DirectCast() 在性能和内存方面相当便宜,并且基本上将其视为帮助我使用 IntelliSense 的一种方式,例如在事件处理程序中:

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'Explicit casting with DirectCast
    Dim myObject As myClass = DirectCast(sender, myClass)

    myObject.MyProperty = "myValue"
End Sub

我认为这显然对作为开发人员的我来说更好,而且对于编译的代码和产生的性能也更好,因为它启用了“早期绑定”而不是......

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'No casting at all (late binding)
    myObject.MyProperty = "myValue"
End Sub

... 如果我正确理解了条款,它也可以编译和运行,但使用“后期绑定”。即假设 sender 实际上是一个 myClass 对象。

在性能、后期/早期绑定或其他方面,上面的第一个 sn-p 和下面的有什么区别:

Public Sub myObject_EventHandler(sender As Object, e As System.EventArgs)
    'Implicit casting via variable declaration
    Dim myObject As myClass = sender

    myObject.MyProperty = "myValue"
End Sub

显式的DirectCast() 调用是否有用/有害,还是在编译器优化代码后没有区别?

【问题讨论】:

  • 与问题无关,但我强烈建议您在所有项目上启用 Option Strict。
  • 如果你懂C#,那么根据this answerDirectCast(obj, T)和C#的(T)obj是一样的。如果这是正确的,那么它可能是您可以执行的最有效的类型转换(具有最少的隐藏魔法);其次是TryCast(C# 中的as)。
  • @the_lotus Turning Option Strict On 将禁用在大多数情况下使用后期绑定的能力,这会使这个问题变得毫无意义。但是,由于 Visual Studio 的通常默认值是Option Strict Off(微软嘘),而且后期绑定有时很有用(看看你,LINQ 和System.Reflection),我认为这个问题值得回答。
  • 第二个 sn-p 相当昂贵,它必须从名称解析属性,这需要反射。 1号和3号没有区别。它在第三个 sn-p 中自动生成 DirectCast,因为它知道 myObject 的类型。您需要使用 ildasm.exe 实用程序来亲自查看这些内容。并使用 Stopwatch 类自己测量这些东西。
  • @HansPassant - 就我而言,你再次被证明是正确的。

标签: vb.net performance directcast


【解决方案1】:

TL;DR 版本: 使用DirectCast() 代替后期绑定或反射以获得更好的运行时性能。

这个问题让我很感兴趣。对于我编写的几乎所有应用程序,我都会定期使用DirectCast()。我一直使用它,因为它使 IntelliSense 工作,如果我不使用Option Strict On,那么我会在编译时出错。每隔一段时间,我会使用Option Strict Off,如果我很着急并且我正在测试一个设计概念,或者我急于为一次性问题提供快速而肮脏的解决方案。我从来没有考虑过使用它时的性能影响,因为我从来不用担心我写的东西的运行时性能。

想到这个问题让我有点好奇,所以我决定自己测试一下,看看有什么不同。我在 Visual Studio 中创建了一个新的控制台应用程序并开始工作。以下是该应用程序的完整源代码。如果您想自己查看结果,您应该可以直接复制/粘贴:

Option Strict Off
Option Explicit On
Imports System.Diagnostics
Imports System.Reflection
Imports System.IO
Imports System.Text


Module Module1
    Const loopCntr As Int32 = 1000000
    Const iterationCntr As Int32 = 5
    Const resultPath As String = "C:\StackOverflow\DirectCastResults.txt"

    Sub Main()
        Dim objDirectCast As New MyObject("objDirectCast")
        Dim objImplicitCasting As New MyObject("objImplicitCasting")
        Dim objLateBound As New MyObject("objLateBound")
        Dim objReflection As New MyObject("objReflection")
        Dim objInvokeMember As New MyObject("objInvokeMember")
        Dim sbElapsed As New StringBuilder : sbElapsed.Append("Running ").Append(iterationCntr).Append(" iterations for ").Append(loopCntr).AppendLine(" loops.")
        Dim sbAverage As New StringBuilder : sbAverage.AppendLine()

        AddHandler objDirectCast.ValueSet, AddressOf SetObjectDirectCast
        AddHandler objImplicitCasting.ValueSet, AddressOf SetObjectImplictCasting
        AddHandler objLateBound.ValueSet, AddressOf SetObjectLateBound
        AddHandler objReflection.ValueSet, AddressOf SetObjectReflection
        AddHandler objInvokeMember.ValueSet, AddressOf SetObjectInvokeMember

        For Each myObj As MyObject In {objDirectCast, objImplicitCasting, objLateBound, objReflection, objInvokeMember}
            Dim resultlist As New List(Of TimeSpan)
            sbElapsed.AppendLine().Append("Time (seconds) elapsed for ").Append(myObj.Name).Append(" is: ")
            For i = 1 To iterationCntr
                Dim stpWatch As New Stopwatch
                stpWatch.Start()
                For _i = 0 To loopCntr
                    myObj.SetValue(_i)
                Next
                stpWatch.Stop()
                sbElapsed.Append(stpWatch.Elapsed.TotalSeconds.ToString()).Append(", ")
                resultlist.Add(stpWatch.Elapsed)
                Console.WriteLine(myObj.Name & " is done.")
            Next

            Dim totalTicks As Long = 0L
            resultlist.ForEach(Sub(x As TimeSpan) totalTicks += x.Ticks)
            Dim averageTimeSpan As New TimeSpan(totalTicks / CLng(resultlist.Count))
            sbAverage.Append("Average elapsed time for ").Append(myObj.Name).Append(" is: ").AppendLine(averageTimeSpan.ToString)
        Next

        Using strWriter As New StreamWriter(File.Open(resultPath, FileMode.Create, FileAccess.Write))
            strWriter.WriteLine(sbElapsed.ToString)
            strWriter.WriteLine(sbAverage.ToString)
        End Using
    End Sub

    Sub SetObjectDirectCast(sender As Object, newValue As Int32)
        Dim myObj As MyObject = DirectCast(sender, MyObject)
        myObj.MyProperty = newValue
    End Sub

    Sub SetObjectImplictCasting(sender As Object, newValue As Int32)
        Dim myObj As MyObject = sender
        myObj.MyProperty = newValue
    End Sub

    Sub SetObjectLateBound(sender As Object, newValue As Int32)
        sender.MyProperty = newValue
    End Sub

    Sub SetObjectReflection(sender As Object, newValue As Int32)
        Dim pi As PropertyInfo = sender.GetType().GetProperty("MyProperty", BindingFlags.Public + BindingFlags.Instance)
        pi.SetValue(sender, newValue, Nothing)
    End Sub

    Sub SetObjectInvokeMember(sender As Object, newValue As Int32)
        sender.GetType().InvokeMember("MyProperty", BindingFlags.Instance + BindingFlags.Public + BindingFlags.SetProperty, Type.DefaultBinder, sender, {newValue})
    End Sub
End Module

Public Class MyObject
    Private _MyProperty As Int32 = 0
    Public Event ValueSet(sender As Object, newValue As Int32)
    Public Property Name As String

    Public Property MyProperty As Int32
        Get
            Return _MyProperty
        End Get
        Set(value As Int32)
            _MyProperty = value
        End Set
    End Property

    Public Sub New(objName As String)
        Me.Name = objName
    End Sub

    Public Sub SetValue(newvalue As Int32)
        RaiseEvent ValueSet(Me, newvalue)
    End Sub

End Class

我尝试在 Release 配置中运行应用程序,以及在没有附加 Visual Studio 调试器的情况下运行发布配置。以下是输出结果:

使用 Visual Studio 调试器发布:

Running 5 iterations for 1000000 loops.

Time (seconds) elapsed for objDirectCast is: 0.0214367, 0.0155618, 0.015561, 0.015544, 0.015542, 
Time (seconds) elapsed for objImplicitCasting is: 0.014661, 0.0148947, 0.015051, 0.0149164, 0.0152732, 
Time (seconds) elapsed for objLateBound is: 4.2572548, 4.2073932, 4.3517058, 4.480232, 4.4216707, 
Time (seconds) elapsed for objReflection is: 0.3900658, 0.3833916, 0.3938861, 0.3875427, 0.4558457, 
Time (seconds) elapsed for objInvokeMember is: 1.523336, 1.1675438, 1.1519875, 1.1698862, 1.2878384, 

Average elapsed time for objDirectCast is: 00:00:00.0167291
Average elapsed time for objImplicitCasting is: 00:00:00.0149593
Average elapsed time for objLateBound is: 00:00:04.3436513
Average elapsed time for objReflection is: 00:00:00.4021464
Average elapsed time for objInvokeMember is: 00:00:01.2601184

发布不带 Visual Studio 调试器:

Running 5 iterations for 1000000 loops.

Time (seconds) elapsed for objDirectCast is: 0.0073776, 0.0055385, 0.0058196, 0.0059637, 0.0057557, 
Time (seconds) elapsed for objImplicitCasting is: 0.0060359, 0.0056653, 0.0065522, 0.0063639, 0.0057324, 
Time (seconds) elapsed for objLateBound is: 4.4858827, 4.1643164, 4.2380467, 4.1217441, 4.1270739, 
Time (seconds) elapsed for objReflection is: 0.3828591, 0.3790779, 0.3849563, 0.3852133, 0.3847144, 
Time (seconds) elapsed for objInvokeMember is: 1.0869766, 1.0808392, 1.0881596, 1.1139259, 1.0811786, 

Average elapsed time for objDirectCast is: 00:00:00.0060910
Average elapsed time for objImplicitCasting is: 00:00:00.0060699
Average elapsed time for objLateBound is: 00:00:04.2274128
Average elapsed time for objReflection is: 00:00:00.3833642
Average elapsed time for objInvokeMember is: 00:00:01.0902160

看看这些结果,与添加转换的编译器相比,使用DirectCast() 几乎没有性能损失。但是,当依赖对象进行后期绑定时,会出现 HUGE 性能损失,并且执行速度会大大减慢。使用System.Reflection 时,直接投射对象会稍微变慢。我认为获得PropertyInfo 比使用.InvokeMember() 方法快得多是不寻常的。

结论: 尽可能使用DirectCast() 或直接投射对象。应该只在需要时才使用反射。仅将后期绑定的物品用作最后的手段。不过说实话,如果你只是在这里或那里修改一个对象几次,那么它在宏伟的计划中可能并不重要。

相反,您应该更关心这些方法如何失败以及如何防止它们这样做。例如,在SetObjectInvokeMember() 方法中,如果sender 对象没有MyProperty 属性,那么该位代码将引发异常。在SetObjectReflection() 方法中,返回的属性信息可能是nothing,这将导致NullReferenceException

【讨论】:

  • 后期绑定需要使用反射找到调用的方法,而两种类型转换是在compile-type确定的,只需要进行类型检查看是否可以进行类型转换。
  • @MicroVirus 我在竞争中添加了两种反射方法,但是两者都没有接近后期绑定方法所需的时间。
  • 总结你的结果(我已经重复了),反射比投射慢一个数量级,后期绑定(和CallByName)又慢了一个数量级,@987654341 @ 介于两者之间。我还发现它 is SetValue 调用是 Reflection 代码的慢速部分(特别是缓存 PropertyInfo 只能节省 22% 的时间),正如 Hans Passant 所建议的,LinqPad 显示SetObjectDirectCastSetObjectImplictCasting 的 IL 相同。
【解决方案2】:

我建议在 for 循环中运行后期绑定和直接强制转换大约 100,000 次,看看两者之间是否存在时间差。

为两个循环创建秒表并打印出结果。让我们知道是否有任何区别。 100,000 次可能太低,您实际上可能让它运行更长时间。

【讨论】:

  • 此处正确诊断。这样做一次不是一个好的测试,因为像 Brandon B 这样的 JIT 在他的代码中显示。
  • @Ahmedilyas 我很困惑。你是说我的测试很好,因为我在没有 JIT 的情况下进行了尝试,还是说我的测试很差,因为 JIT?
  • Brandon B 他说你在 JIT 第一次运行时就受到了性能的影响。所以很有可能如果你多次运行相同的代码 sn-p,只有第一次运行需要 8 秒。你必须在多次运行后平均它。如果您的程序只执行一次然后关闭,那么是的,这是一个巨大的问题。如果是一个服务 6 个月,每天运行 3000 次,那么你还没有计算平均值。
  • @Ahmedilyas 我更新了我的答案以执行多次迭代并计算每种类型的平均值。不过最终结果并没有太大变化。
猜你喜欢
  • 2016-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 2016-10-22
  • 2021-10-08
  • 2013-02-08
  • 1970-01-01
相关资源
最近更新 更多