Ogni tanto accade di dover scambiare tra loro il contenuto di due variabili. Sembra proprio che il nostro VBA non permetta lo scambio direttamente, ma che sia necessario passare attraverso una variabile di appoggio temporanea:

tmp = s1
s1 = s2
s2 = tmp

Questo piccolo spuntino di Excel, tratto da uno dei suggerimenti di Francesco Balena, ci mostra invece che basta una chiamata ad una API piuttosto comune, CopyMemory (RtlMoveMemory) che invece di allocare fisicamente spazio per una nuova variabile, spostarvi il contenuto della prima, quindi riassegnare i diversi valori, semplicemente scambia tra loro i puntatori ai descrittori di stringa in memoria… e lo scambio di coppia è fatto 🙂

L’API CopyMemory si dichiara così in testa a un modulo:

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal numBytes As Long)

L’API CopyMemory copia un blocco di memoria da una locazione a un’altra quindi la velocità di esecuzione è quasi istantanea. Riceve tre parametri:
Dest: Un puntatore all’indirizzo iniziale del blocco memoria di destinazione
Source: Un puntatore all’indirizzo iniziale del blocco di memoria da copiare
numBytes: La dimensione del blocco di memoria da copiare, in bytes.

Ecco un codice completo da testare, la prima funzione esegue uno scambio classico, la seconda sfrutta CopyMemory e la sub di test mostra invece che i tempi di esecuzione sono ridotti almeno della metà quando sono in gioco stringhe enormi.

Option Explicit

' scambio si stringhe tra loro
' da I trucchi di VB6 di F. Balena

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(dest As Any, source As Any, ByVal numBytes As Long)

'metodo classico
Sub swap()
Dim tmp As String, s1 As String, s2 As String
    
    Randomize Timer
    s1 = String(99999999#, Chr(Int(Rnd * 26) + 65))
    s2 = String(99999999#, Chr(Int(Rnd * 26) + 65))
    
    tmp = s1
    s1 = s2
    s2 = tmp
End Sub

'metodo smart
Sub swap2()
Dim s1 As String
Dim s2 As String
Dim saveAddr As Long

    Randomize Timer
    s1 = String(99999999#, Chr(Int(Rnd * 26) + 65))
    s2 = String(99999999#, Chr(Int(Rnd * 26) + 65))
    
    ' salva il descrittore della prima stringa
    saveAddr = StrPtr(s1)
    ' copia il descrittore di s2 in quello di s1 (32 bit)
    CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4
    ' completa lo scambio dei descrittori
    CopyMemory ByVal VarPtr(s2), saveAddr, 4
End Sub


Sub test_swap()
Dim t1 As Single, t2 As Single
Dim f1 As Single, f2 As Single
Dim s1 As String, s2 As String
    
    t1 = Timer
    swap
    t2 = Timer
    f1 = (t2 - t1) * 1000
    
    t1 = Timer
    swap2
    t2 = Timer
    f2 = (t2 - t1) * 1000
    MsgBox "1° test tempo trascorso = " & _
    Format(f1, "0.000 \m\s") & vbNewLine & _
           "2° test tempo trascorso = " & _
    Format(f2, "0.000 \m\s")
    
End Sub
Scambiare contenuto di due variabili
Tag:         

Scambiare contenuto di due variabili

Login Registrati
Stai vedendo 10 articoli - dal 1 a 10 (di 10 totali)
  • Autore
    Articoli
  • #7555 Score: 0 | Risposta

    vecchio frac
    Senior Moderator
      245 pts

      Ogni tanto accade di dover scambiare tra loro il contenuto di due variabili. Sembra proprio che il nostro VBA non permetta lo scambio direttamente, ma che sia necessario passare attraverso una variabile di appoggio temporanea:

      tmp = s1
      s1 = s2
      s2 = tmp

      Questo piccolo spuntino di Excel, tratto da uno dei suggerimenti di Francesco Balena, ci mostra invece che basta una chiamata ad una API piuttosto comune, CopyMemory....
      [Leggi tutto al seguente link: https://www.excelvba.it/forumexcel/scambiare-contenuto-di-due-variabili/]

      #7557 Score: 0 | Risposta

      Mirko
      Partecipante
        2 pts

        Per utilzzare il codice con Excel  64 Bit

        Option Explicit
        
        ' scambio si stringhe tra loro
        ' da I trucchi di VB6 di F. Balena
        
        
        #If VBA7 Then
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
        (dest As Any, source As Any, ByVal numBytes As Long)
        #Else
        Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
        (dest As Any, source As Any, ByVal numBytes As Long)
        #End If
        
        'metodo classico
        Sub swap()
        Dim tmp As String, s1 As String, s2 As String
        
        Randomize Timer
        s1 = String(99999999#, Chr(Int(Rnd * 26) + 65))
        s2 = String(99999999#, Chr(Int(Rnd * 26) + 65))
        
        tmp = s1
        s1 = s2
        s2 = tmp
        End Sub
        
        'metodo smart
        Sub swap2()
        Dim s1 As String
        Dim s2 As String
        
        #If VBA7 Then
        Dim saveAddr As LongPtr
        #Else
        Dim saveAddr As Long
        #End If
        
        Randomize Timer
        s1 = String(99999999#, Chr(Int(Rnd * 26) + 65))
        s2 = String(99999999#, Chr(Int(Rnd * 26) + 65))
        
        ' salva il descrittore della prima stringa
        saveAddr = StrPtr(s1)
        ' copia il descrittore di s2 in quello di s1 (32 bit)
        CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4
        ' completa lo scambio dei descrittori
        CopyMemory ByVal VarPtr(s2), saveAddr, 4
        End Sub
        
        
        Sub test_swap()
        Dim t1 As Single, t2 As Single
        Dim f1 As Single, f2 As Single
        Dim s1 As String, s2 As String
        
        t1 = Timer
        swap
        t2 = Timer
        f1 = (t2 - t1) * 1000
        
        t1 = Timer
        swap2
        t2 = Timer
        f2 = (t2 - t1) * 1000
        MsgBox "1° test tempo trascorso = " & _
        Format(f1, "0.000 \m\s") & vbNewLine & _
        "2° test tempo trascorso = " & _
        Format(f2, "0.000 \m\s")
        
        End Sub
        #7561 Score: 0 | Risposta

        Luca73
        Partecipante
          56 pts

          Ciao

          alcune considerazioni.

          1) a me non è mai capitato di dovere scambiare il valore di due variabili.

          2) che vantaggio c'è a scrivere tutta la procedura qui sopra descritta invece di usare una variabile temporanea?

          3) se proprio fosse necessario avrei piuttosto pensato a qualcosa del genere

          Sub pippo()
          
          Dim a
          Dim b
          
          a = 100.5
          b = "pluto"
          
          a = a & "||Luca||" & b
          b = Left(a, InStr(1, a, "||Luca||") - 1)
          a = Right(a, Len(a) - Len("||Luca||") - Len(b))
          
          End Sub

          E' brutta ma comiuque più breve...

          Ciao

          Luca

          #7565 Score: 0 | Risposta

          vecchio frac
          Senior Moderator
            245 pts

            Ciao Luca,

            1) a me sì anche quando, magari solo per esercizio, mi sono trovato a scrivere le mie routine di sort; nota che banalmente ad esempio in python basta scrivere semplicemente a = b e lo swap è fatto 🙂

            2) il vantaggio (teorico: parliamo di qualche millisecondo) è nella performance, il guadagno è nelle stringhe di grandi dimensioni;

            3) benissimo, è sempre un'alternativa :), e  si torna a parlare di performances perchè se devi scambiare due valori numerici poi li devi convertire e il compilatore "perde tempo".

            Comunque anche a riscrivere procedure banali si tratta di dar corso a un pensiero laterale che male non fa, anche se naturalmente occorre tener conto delle correzioni di oggi per i sistemi a 64 bit come giustamente ha proposto Mirko.

            Lo scopo del fare articoli un po' inusuali è anche per stimolare la curiosità   

            #7585 Score: 0 | Risposta

            zer0kelvin
            Partecipante
              5 pts

              Ciao a tutti.

              Sembra proprio che il nostro VBA non permetta lo scambio direttamente, ma che sia necessario passare attraverso una variabile di appoggio temporanea

              Anche se, a causa dell'inefficienza del compilatore VBA, l'utilizzo della API può essere conveniente, trovo errata la premessa.

              In nessun caso lo scambio può avvenire direttamente, allo stesso modo in cui non si può scambiare il contenuto di due bottiglie senza usare un terzo contenitore.

              Se potessimo leggere il codice della function CopyMemory vedremmo che questa utilizza un'area di memoria temporanea (o un registro della CPU, forse, per variabili di pochi Byte) per effettuare lo scambio.

              Allo stesso modo, se guardassimo le istruzioni Assembler generate dall'istruzione Python, probabilmente osserveremmo la stessa cosa.

              Riguardo alle routines di ordinamento, che sono quelle in cui VBA ansima pesantemente , io di solito copio i valori  sul foglio e sfrutto l'ordinamento di Excel che è invece, piuttosto efficiente.

              #7586 Score: 0 | Risposta

              vecchio frac
              Senior Moderator
                245 pts

                zer0kelvin wrote:io di solito copio i valori  sul foglio e sfrutto l'ordinamento di Excel

                Questa è una tecnica normale sotto Excel ed è pienamente condivisibile.

                Del resto il codice proposta voleva essere svincolato da Excel 🙂

                #7588 Score: 0 | Risposta

                Luca73
                Partecipante
                  56 pts

                  Ciao a tutti

                  Ringrazio VF per le risposte. Non volevano essere sterili polemiche...

                  Però, a valle di tutto, volevo capire se c'è qualche vantaggio ad usare CopyMemory  può portre qualche vantaggio pratico (chiedo scusa per la brutalità) ad un normale utilizzatore di VBA. Ovvero se vi sono altre possibilità di uso (diverse dallo swop di variabili) tale per cui risulti uno strumento utile.

                  Ciao a tutti.

                  PS concordo appieno con

                  Comunque anche a riscrivere procedure banali si tratta di dar corso a un pensiero laterale che male non fa, [...]

                  Lo scopo del fare articoli un po' inusuali è anche per stimolare la curiosità  

                  #7591 Score: 0 | Risposta

                  vecchio frac
                  Senior Moderator
                    245 pts

                    Non ci sono mai sterili polemiche se si parla di descrivere codice e tecniche, il confronto è sempre, sempre costruttivo nel rispetto reciproco.

                    Luca73 wrote:se c'è qualche vantaggio ad usare CopyMemory 

                    La risposta veloce è no, al livello  delle applicazioni che costruiamo noi nel nostro quotidiano. Parlo per me ma credo che sia una questione condivisa, per lo più non siamo professionisti della programmazione ma dilettanti avanzati (qualcuno che si tira fuori c'è, mi viene in mente Oregon ossia A. G. che purtroppo non vedo più tra i nostri frequentatori). 

                    La risposta complessa è sì, ci son vantaggi grandi a livello di ottimizzazione delle prestazioni soprattutto quando si macinano grandi moli di dati.

                    La risposta diplomatica è ni, nel senso che serve valutare caso per caso   

                    #7603 Score: 0 | Risposta

                    scossa
                    Partecipante
                      25 pts

                      Ciao a tutti,

                      @vecchio frac: molto interessante quanto proponi. Mi permetto di suggerire l'utilizzo di un timer più preciso (MicroTimer) per apprezzare le differenze anche su stringhe più corte (anche soli 9 caratteri!).

                      Ripropongo il tuo codice con le modifiche necessarie per poterlo provare subito (lanciare la sub TestRun):

                       

                      `Option Explicit
                      
                      ' scambio si stringhe tra loro
                      ' da I trucchi di VB6 di F. Balena
                      
                      Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
                      (dest As Any, source As Any, ByVal numBytes As Long)
                      
                      '-------------- copy & paste in a standard module ---------------
                      '
                      'MicroTimer function
                      'Found on the net
                      '
                      #If VBA7 Then
                      
                        Private Declare PtrSafe Function getFrequency _
                                    Lib "kernel32" _
                                    Alias "QueryPerformanceFrequency" ( _
                                    cyFrequency As Currency) _
                                    As Long
                        
                        
                        Private Declare PtrSafe Function getTickCount _
                                    Lib "kernel32" _
                                    Alias "QueryPerformanceCounter" _
                                    (cyTickCount As Currency) _
                                    As Long
                      #Else
                        Private Declare Function getFrequency _
                                    Lib "kernel32" _
                                    Alias "QueryPerformanceFrequency" ( _
                                    cyFrequency As Currency) _
                                    As Long
                        
                        
                        Private Declare Function getTickCount _
                                    Lib "kernel32" _
                                    Alias "QueryPerformanceCounter" _
                                    (cyTickCount As Currency) _
                                    As Long
                      #End If
                      
                      Public nCnt As Integer
                      Public sMsgTmr As String
                      Public sMsgMcrTmr As String
                      
                      Public Function MicroTimer() As Double
                          Dim cyTicks1 As Currency
                          Static cyFrequency As Currency
                          MicroTimer = 0
                          If cyFrequency = 0 Then getFrequency cyFrequency
                          getTickCount cyTicks1
                          If cyFrequency Then MicroTimer = cyTicks1 / cyFrequency
                      End Function
                      
                      
                      'metodo classico
                      Sub swap()
                      Dim tmp As String, s1 As String, s2 As String
                          
                          Randomize Timer
                          s1 = String(9999#, Chr(Int(Rnd * 26) + 65))
                          s2 = String(9999#, Chr(Int(Rnd * 26) + 65))
                          
                          tmp = s1
                          s1 = s2
                          s2 = tmp
                      End Sub
                      
                      'metodo smart
                      Sub swap2()
                      Dim s1 As String
                      Dim s2 As String
                      Dim saveAddr As Long
                      
                          Randomize Timer
                          
                          s1 = String(9999#, Chr(Int(Rnd * 26) + 65))
                          s2 = String(9999#, Chr(Int(Rnd * 26) + 65))
                          
                          ' salva il descrittore della prima stringa
                          saveAddr = StrPtr(s1)
                          ' copia il descrittore di s2 in quello di s1 (32 bit)
                          CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4
                          ' completa lo scambio dei descrittori
                          CopyMemory ByVal VarPtr(s2), saveAddr, 4
                      End Sub
                      
                      
                      
                      Sub test_swap()
                      Dim t1 As Single, t2 As Single
                      Dim f1 As Single, f2 As Single, f3 As Single
                          
                          t1 = Timer
                          swap
                          t2 = Timer
                          f1 = (t2 - t1) * 1000
                          
                          t1 = Timer
                          swap2
                          t2 = Timer
                          f2 = (t2 - t1) * 1000
                      
                          sMsgTmr = "Timer - run " & nCnt & vbLf & "1° test (swap) tempo trascorso = " & _
                          Format(f1, "0.0000 \m\s") & vbNewLine & _
                                 "2° test (api) tempo trascorso = " & _
                          Format(f2, "0.0000 \m\s")
                          
                      End Sub
                      
                      
                      
                      Sub test_swapMT()
                      Dim t1 As Double, t2 As Double
                      Dim f1 As Double, f2 As Double, f3 As Double
                          
                          t1 = MicroTimer
                          swap
                          t2 = MicroTimer
                          f1 = (t2 - t1) * 1000
                          
                          t1 = MicroTimer
                          swap2
                          t2 = MicroTimer
                          f2 = (t2 - t1) * 1000
                      
                      
                          sMsgMcrTmr = "MicroTimer - run " & nCnt & vbLf & "1° test (swap) tempo trascorso = " & _
                          Format(f1, "0.0000 \m\s") & vbNewLine & _
                                 "2° test (api) tempo trascorso = " & _
                          Format(f2, "0.0000 \m\s")
                          
                      End Sub
                      
                      Sub TestRun()
                        nCnt = nCnt + 1
                        test_swap
                        test_swapMT
                        MsgBox sMsgTmr & vbLf & sMsgMcrTmr
                      End Sub
                      `

                       

                      Ciao.

                      #7605 Score: 0 | Risposta

                      vecchio frac
                      Senior Moderator
                        245 pts

                        Mi piace la funzione per il calcolo del tempo così accurata, io ne avevo una in canna però precisa solo fino al millisecondo    dai che ne facciamo un articoletto per la prossima volta!  

                      Login Registrati
                      Stai vedendo 10 articoli - dal 1 a 10 (di 10 totali)
                      Rispondi a: Scambiare contenuto di due variabili
                      Gli allegati sono permessi solo ad utenti REGISTRATI
                      Le tue informazioni: