2015-04-20 7 views
10

Ho sempre pensato che lo DirectCast() fosse abbastanza economico, perforante e per quanto riguarda la memoria, e l'ho visto fondamentalmente come un modo per aiutarmi con IntelliSense, ad es. nei gestori di eventi:Quali sono gli effetti di DirectCast sulle prestazioni e sull'associazione tardiva/anticipata?

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 

ho pensato che questo era meglio, ovviamente, per me come sviluppatore, ma anche per il codice compilato e le prestazioni derivanti, perché ha permesso "l'associazione anticipata" in contrasto con ...

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

... che compila e funziona anche, ma usa "late binding", se ho ottenuto i termini correttamente. Supponendo che sender sia in effetti un oggetto myClass.

Per quanto riguarda le prestazioni, fine/l'associazione anticipata, o qualsiasi altra cosa, quali sono le differenze tra il primo frammento di sopra e il seguente:

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 

E 'la esplicita DirectCast() chiamata utile/dannoso o fa non fa alcuna differenza dopo che il compilatore ha ottimizzato il codice?

+3

Non correlato alla domanda ma suggerisco vivamente di abilitare Option Strict su tutti i progetti. –

+0

Se si conosce C#, quindi, in base a [questa risposta] (http://stackoverflow.com/a/40782/240733), 'DirectCast (obj, T)' è uguale a '(T) obj' di C#. Se ciò è corretto, probabilmente è il tipo di cast più efficiente (con la magia meno nascosta) che puoi eseguire; seguito da 'TryCast' (' as' in C#). – stakx

+1

@the_lotus Attivando 'Option Strict On' si disabilita la possibilità di utilizzare l'associazione tardiva nella maggior parte dei casi che renderebbe questa domanda discutibile. Tuttavia, poiché il solito valore predefinito per Visual Studio è 'Option Strict Off' (boo Microsoft), e il fatto che l'associazione tardiva sia utile a volte (guardando te, LINQ e' System.Reflection'), penserei che questa domanda merita una risposta –

risposta

7

TL; DR versione: Uso DirectCast() invece di associazione tardiva o di riflessione per migliorare le prestazioni di esecuzione.

Questa domanda è stata molto intrigante per me. Io uso DirectCast() su base molto regolare per quasi tutte le applicazioni che ho scritto. L'ho sempre usato perché rende IntelliSense funzionante e se non lo faccio con Option Strict On allora ricevo errori durante la compilazione. Ogni tanto uso Option Strict Off se ho fretta e sto testando un concetto di design o se ho fretta per una soluzione rapida e sporca a un problema una tantum. Non ho mai pensato un secondo agli effetti delle prestazioni nell'usarlo perché non ho mai dovuto preoccuparmi delle prestazioni di runtime con le cose che scrivo.

Pensare a questa domanda mi ha reso un po 'curioso, quindi ho deciso di provarlo e vedere le differenze da solo. Ho creato una nuova applicazione Console in Visual Studio e sono andato a lavorare. Di seguito è riportato l'intero codice sorgente per l'applicazione. Se si desidera vedere i risultati per te si dovrebbe essere in grado di solo copia/incolla direttamente:

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 

Ho provato a fare funzionare l'applicazione in configurazione Release oltre a gestire la configurazione di rilascio senza il debugger di Visual Studio allegato.Ecco i risultati emessi:

uscita con Visual Studio Debugger:

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 

uscita senza Debugger di 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 

Guardando questi risultati sarebbe guardare come se usando DirectCast() non ha quasi nessun riscontro in termini di prestazioni rispetto al compilatore che aggiunge nel cast. Tuttavia, quando si fa affidamento sull'oggetto per essere rilegato in ritardo, si verifica un errore di prestazioni ENORME e l'esecuzione rallenta notevolmente. Quando si utilizza System.Reflection, si verifica un leggero rallentamento rispetto alla trasmissione diretta dell'oggetto. Ho pensato che fosse insolito che ottenere il numero PropertyInfo fosse molto più veloce rispetto all'utilizzo del metodo .InvokeMember().

Conclusione: Quando possibile uso DirectCast() o lanciare direttamente l'oggetto. Usare il riflesso dovrebbe essere riservato solo quando ne hai bisogno. Utilizzare gli oggetti in ritardo solo come ultima risorsa. Sinceramente però, se stai solo modificando un oggetto alcune volte qua o là, è probabile che non abbia importanza nel grande schema delle cose.

Invece dovresti essere più preoccupato di come quei metodi potrebbero fallire e come impedirgli di farlo. Ad esempio, nel metodo SetObjectInvokeMember(), se l'oggetto sender non aveva la proprietà MyProperty, quel bit di codice avrebbe generato un'eccezione. Nel metodo SetObjectReflection(), le informazioni sulle proprietà restituite potevano essere nothing che avrebbe comportato un NullReferenceException.

+0

L'associazione tardiva deve utilizzare il reflection per trovare il metodo da chiamare, mentre i due cast sono determinati in fase di compilazione e devono solo eseguire un controllo del tipo per verificare se è possibile eseguire il cast. – MicroVirus

+0

@MicroVirus Ho aggiunto due metodi di riflessione nella mischia, tuttavia nessuno dei due è vicino al tempo necessario per il metodo di associazione in ritardo. –

+0

Riassumendo i risultati (che ho ripetuto), la riflessione è su un ordine di grandezza più lento rispetto al cast, late-bound (e 'CallByName') è di nuovo un altro ordine di grandezza più lento, e' InvokeMember' è un punto tra i due. Ho anche trovato _is_ la chiamata 'SetValue' che è la parte lenta del codice' Reflection' (in particolare il caching di 'PropertyInfo' risparmia solo il 22% di tempo) e, come suggerito da Hans Passant, LinqPad mostra l'IL di' SetObjectDirectCast' e 'SetObjectImplictCasting' _are_ identico. –

2

Suggerirei di eseguire il binding tardivo e il cast diretto in un ciclo for circa 100.000 volte e vedere se c'è una differenza di tempo tra i due.

Crea cronometro per entrambi i loop e stampa i risultati. Facci sapere se c'è qualche differenza. 100.000 volte potrebbe essere troppo basso e potresti effettivamente farlo funzionare più a lungo.

+0

diagnostica corretta qui. farlo una volta non è un buon test a causa della JIT come Brandon B sta mostrando nel suo codice. –

+0

@Ahmedilyas Sono confuso. Stai dicendo che il mio è un buon test perché ho provato senza il JIT o stai dicendo che il mio test è sbagliato a causa del JIT? –

+0

Brandon B sta dicendo che stai prendendo il colpo di prestazioni del JIT la prima volta che viene eseguito. Quindi è molto probabile che se si esegue lo stesso snippet di codice più volte, solo la primissima esecuzione richiederebbe 8 secondi .. si dovrebbe mediare fuori dopo più esecuzioni. Se il tuo programma lo fa solo una volta poi si chiude allora sì, c'è un grosso problema. Se si tratta di un servizio che rimane attivo per 6 mesi e viene eseguito 3000 volte al giorno, non è stata calcolata la media. – DoomVroom