Por: Roberto A. Farah

 

CENÁRIO

 

Eis o link para o Desafio: http://blogs.technet.com/latam/archive/2006/04/21/426009.aspx

O problema causando o sintoma de baixa performance é colocado abaixo junto com uma solução.

 

PROBLEMA

 

A concatenação de strings é efetuada com o operador ‘+’ que, assim como ‘&’ alocações e desalocações internamente, que, para código nativo é algo custoso. Note que usar ‘+’ e ‘&’ não são sinônimos conforme explicado abaixo: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon98/html/vbstartpage.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon98/html/vbstartpage.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon98/html/vbconadvancedvarianttopics.asp

 

Entretanto, usar qualquer desses operadores causa o mesmo impacto. Veja isso:

 

Option Explicit

 

Private Sub Original(ByVal strText As String)

    Dim strOutput As String

    Dim i As Integer

   

    strOutput = vbNullString

   

    'Usando operador +

    For i = 1 To 10

        strOutput = strOutput + strText

    Next

   

    'Usando operador &

    strOutput = vbNullString

    For i = 1 To 10

        strOutput = strOutput & strText

    Next

End Sub   

 

 

Agora observe o código disassemblado:

 

 

Ok, eis o endereço das variáveis locais no início da rotina. Como uma curiosiade note que o VB usa Unicode internamente (wchar_t):

 

local  0012f49c @ebp+0x08 void * Me = 0x0014a550

local  0012f4a0 @ebp+0x0c wchar_t * strText = 0x001518a4 "String para ser concatenada!!!"

local  0012f478 @ebp-0x1c wchar_t * strOutput = 0x00000000 ""

local  0012f47c @ebp-0x18 wchar_t * strText = 0x00153b7c "String para ser concatenada!!!"

local  0012f480 @ebp-0x14 short i = -23216

 

Rotina disassemblada:

 

Project1!Form1::Original [C:\Development\My Tools\BLOG Articles\Article #6\Form1.frm @ 92]:

00402220 55              push    ebp

00402221 8bec            mov     ebp,esp

00402223 83ec08          sub     esp,8

00402226 6816114000      push    offset Project1!__vbaExceptHandler (00401116)

0040222b 64a100000000    mov     eax,dword ptr fs:[00000000h]

00402231 50              push    eax

00402232 64892500000000  mov     dword ptr fs:[0],esp

00402239 83ec20          sub     esp,20h

0040223c 53              push    ebx

0040223d 56              push    esi

0040223e 57              push    edi

0040223f 8965f8          mov     dword ptr [ebp-8],esp

00402242 c745fcf0104000  mov     dword ptr [ebp-4],offset Project1!_imp____vbaFreeObj+0x4c (004010f0)

00402249 8b550c          mov     edx,dword ptr [ebp+0Ch]

0040224c 8b3d78104000    mov     edi,dword ptr [Project1!_imp___vbaStrCopy (00401078)]

00402252 33c0            xor     eax,eax

00402254 8d4de8          lea     ecx,[ebp-18h]

00402257 8945e8          mov     dword ptr [ebp-18h],eax

0040225a 8945e4          mov     dword ptr [ebp-1Ch],eax

0040225d ffd7            call    edi

 

CÓDIGO USANDO OPERADOR +

 

0040225f 33d2            xor     edx,edx

00402261 8d4de4          lea     ecx,[ebp-1Ch]    <-- Carrega strOutput.

00402264 ffd7            call    edi              <-- Chama __vbaStrCopy do VBRuntime.

00402266 bb01000000      mov     ebx,1

0040226b 8bf3            mov     esi,ebx

0040226d b80a000000      mov     eax,0Ah          <-- Prepara o contador do loop.

00402272 663bf0          cmp     si,ax

00402275 7f25            jg      Project1!Form1::Original+0x7c (0040229c)

00402277 8b45e4          mov     eax,dword ptr [ebp-1Ch]    <-- strOutput.

0040227a 8b4de8          mov     ecx,dword ptr [ebp-18h]    <-- strText

0040227d 50              push    eax                        <-- Apos colocar em registradores salva na pilha,

0040227e 51              push    ecx                        <-- pois sao os parametros de _vbaStrCat.

0040227f ff1518104000    call    dword ptr [Project1!_imp___vbaStrCat (00401018)]

00402285 8bd0            mov     edx,eax

00402287 8d4de4          lea     ecx,[ebp-1Ch]

0040228a ff158c104000    call    dword ptr [Project1!_imp____vbaStrMove (0040108c)]  <-- Move os ponteiros dos caracteres fonte e destino.

00402290 668bd3          mov     dx,bx

00402293 6603d6          add     dx,si

00402296 706c            jo      Project1!Form1::Original+0xe4 (00402304)

00402298 8bf2            mov     esi,edx

0040229a ebd1            jmp     Project1!Form1::Original+0x4d (0040226d)

 

CÓDIGO USANDO OPERADOR &

 

0040229c 33d2            xor     edx,edx                                 <-- Aqui inicia a parte usando &.

0040229e 8d4de4          lea     ecx,[ebp-1Ch]

004022a1 ffd7            call    edi                                     <-- __vbaStrCopy.

004022a3 bf01000000      mov     edi,1

004022a8 bb0a000000      mov     ebx,0Ah                                 <-- Exatamente o mesmo padrao se repete.

004022ad 8bf7            mov     esi,edi

004022af 663bf3          cmp     si,bx

004022b2 7f25            jg      Project1!Form1::Original+0xb9 (004022d9)

004022b4 8b45e4          mov     eax,dword ptr [ebp-1Ch]

004022b7 8b4de8          mov     ecx,dword ptr [ebp-18h]

004022ba 50              push    eax

004022bb 51              push    ecx

004022bc ff1518104000    call    dword ptr [Project1!_imp___vbaStrCat (00401018)]

004022c2 8bd0            mov     edx,eax

004022c4 8d4de4          lea     ecx,[ebp-1Ch]

004022c7 ff158c104000    call    dword ptr [Project1!_imp____vbaStrMove (0040108c)]

004022cd 668bd7          mov     dx,di

004022d0 6603d6          add     dx,si

004022d3 702f            jo      Project1!Form1::Original+0xe4 (00402304)

004022d5 8bf2            mov     esi,edx

004022d7 ebd6            jmp     Project1!Form1::Original+0x8f (004022af)

 

004022d9 68ef224000      push    offset Project1!Form1::Original+0xcf (004022ef)

004022de 8b35a0104000    mov     esi,dword ptr [Project1!_imp____vbaFreeStr (004010a0)]

004022e4 8d4de8          lea     ecx,[ebp-18h]

004022e7 ffd6            call    esi

004022e9 8d4de4          lea     ecx,[ebp-1Ch]

004022ec ffd6            call    esi

004022ee c3              ret

004022ef 8b4df0          mov     ecx,dword ptr [ebp-10h]

004022f2 5f              pop     edi

004022f3 5e              pop     esi

004022f4 33c0            xor     eax,eax

004022f6 64890d00000000  mov     dword ptr fs:[0],ecx

004022fd 5b              pop     ebx

004022fe 8be5            mov     esp,ebp

00402300 5d              pop     ebp

00402301 c20800          ret     8

00402304 ff156c104000    call    dword ptr [Project1!_imp____vbaErrorOverflow (0040106c)]

0040230a 90              nop

0040230b 90              nop

0040230c 90              nop

0040230d 90              nop

0040230e 90              nop

0040230f 90              nop

 

Pois bem, as chamadas do VBRuntime, iniciadas por _imp___ (rotinas importadas do VBRutime) chamam, dentro delas, outras APIs que constantemente alocam e desalocam memória. Para se ter uma idéia, olhe as chamadas feitas pelo VBRuntime para cada interação de qualquer um dos loops disassemblados acima:

 

  352 13277 [  0] Project1!Form1::Original

    7     0 [  1]   MSVBVM60!__vbaFreeStr

   10     0 [  2]     OLEAUT32!SysFreeString

   11     0 [  3]       kernel32!TlsGetValue

   19    11 [  2]     OLEAUT32!SysFreeString

   16     0 [  3]       OLEAUT32!APP_DATA::FreeCachedMem

    9     0 [  4]         ole32!CRetailMalloc_GetSize

   15     0 [  5]           ntdll!RtlSizeHeap

    3     0 [  6]             ntdll!RtlDebugSizeHeap

   19     0 [  7]               ntdll!_SEH_prolog

   17    19 [  6]             ntdll!RtlDebugSizeHeap

   14     0 [  7]               ntdll!RtlpCheckHeapSignature

   26    33 [  6]             ntdll!RtlDebugSizeHeap

   19     0 [  7]               ntdll!RtlEnterCriticalSection

   31    52 [  6]             ntdll!RtlDebugSizeHeap

   15     0 [  7]               ntdll!RtlpValidateHeap

   10     0 [  8]                 ntdll!RtlpValidateHeapHeaders

   27    10 [  7]               ntdll!RtlpValidateHeap

   38    89 [  6]             ntdll!RtlDebugSizeHeap

   35     0 [  7]               ntdll!RtlpValidateHeapEntry

   18     0 [  8]                 ntdll!RtlpCheckBusyBlockTail

   18     0 [  9]                   ntdll!RtlCompareMemory

   27    18 [  8]                 ntdll!RtlpCheckBusyBlockTail

   58    45 [  7]               ntdll!RtlpValidateHeapEntry

   44   192 [  6]             ntdll!RtlDebugSizeHeap

   34     0 [  7]               ntdll!RtlSizeHeap

   49   226 [  6]             ntdll!RtlDebugSizeHeap

    5     0 [  7]               ntdll!RtlDebugSizeHeap

    8     0 [  8]                 ntdll!RtlLeaveCriticalSection

    6     8 [  7]               ntdll!RtlDebugSizeHeap

   51   240 [  6]             ntdll!RtlDebugSizeHeap

    9     0 [  7]               ntdll!_SEH_epilog

   52   249 [  6]             ntdll!RtlDebugSizeHeap

   19   301 [  5]           ntdll!RtlSizeHeap

   11   320 [  4]         ole32!CRetailMalloc_GetSize

   95   331 [  3]       OLEAUT32!APP_DATA::FreeCachedMem

   22   437 [  2]     OLEAUT32!SysFreeString

   10   459 [  1]   MSVBVM60!__vbaFreeStr

  353 13746 [  0] Project1!Form1::Original

 

 

Agora observe o total de uma completa execução do método Original():

 

14108 instructions were executed in 14107 events (0 from other threads)

 

Function Name                               Invocations MinInst MaxInst AvgInst

MSVBVM60!__vbaFreeStr                              2      10      10      10

MSVBVM60!__vbaStrCat                              20      12      12      12

MSVBVM60!__vbaStrCopy                              3      14      19      16

MSVBVM60!__vbaStrMove                            20      12      14      13

OLEAUT32!APP_DATA::AllocCachedMem         21      20      52      30

OLEAUT32!APP_DATA::FreeCachedMem         21      35      95      55

OLEAUT32!BstrLenA                                   40       8       9       8

OLEAUT32!GetAppData                               21      17      17      17

OLEAUT32!SysAllocStringByteLen                 21      33      58      34

OLEAUT32!SysFreeString                             21      22      22      22

OLEAUT32!VarBstrCat                                20      58     117     102

Project1!Form1::Original                              1     362     362     362

kernel32!TlsGetValue                                 42      11      11      11

ntdll!RtlCompareMemory                             21      18      18      18

ntdll!RtlDebugSizeHeap                              42       6      52      29

ntdll!RtlEnterCriticalSection                         21      19      19      19

ntdll!RtlLeaveCriticalSection                        21       8       8       8

ntdll!RtlSizeHeap                                       42      19      34      26

ntdll!RtlpCheckBusyBlockTail                       21      27      27      27

ntdll!RtlpCheckHeapSignature                      21      14      14      14

ntdll!RtlpValidateHeap                               21      27      27      27

ntdll!RtlpValidateHeapEntry                        21      58      58      58

ntdll!RtlpValidateHeapHeaders                    21      10      10      10

ntdll!_SEH_epilog                                      21       9       9       9

ntdll!_SEH_prolog                                      21      19      19      19

ole32!CRetailMalloc_GetSize                       21      11      11      11

 

 

Quando eu dúvida verifique o código disassemblado: o Assembly nunca mente! J

 

Pois bem, uma solução otimizada deve eliminar ou reduzir drasticamente as constantes alocações e desalocações de memória.

 

 

SOLUÇÃO

 

A solução se baseia em evitar constante alocação/desalocação de memória.

 

Eis a rotina otimizada:

 

' Fazer 10 concatenacoes de string.

Private Sub Optimized(ByVal strText As String)

    'PARA CONCATENAR STRINGS NUNCA USE O OPERADOR +, PREFIRA O & QUE É MAIS RÁPIDO.

    'MAS PREFIRA PREALOCAR UMA VARIÁVEL E USAR MID$ QUE É MUITO MAIS RÁPIDO, POIS EVITA CONSTANTES

    'REALOCAÇÕES DE STRING CAUSADAS POR &.

    'Nao e' usado um loop ou uma chamada de rotina propositalmente para poupar ciclos de CPU.

    'E' uma tecnica conhecida como unroll the loop.

    Dim lIndex As Long

    Dim strBuffer As String

    Dim strSubStr As String

    Dim lLen As Long

   

    'Aloca buffer para armazenar a string.

    'ATENÇÃO! Colocar um valor suficientemente grande para a tarefa.

    strBuffer = Space$(Len(strText) * 10)

    lIndex = 1

   

    '1- Inicializa a string a ser colocada no buffer.

    '   Poderiamos usar strText no exemplo e poupar uma variavel e alguns ciclos de processamento,

    '   mas usando strSubStr fica mais facil para voce reusar esse codigo! :)

    strSubStr = strText

 

    '2- Obtemos tamanho da substring inicial.

    lLen = Len(strText)

   

    '3- Passamos a substring para o buffer.

    Mid$(strBuffer, lIndex, lLen) = strSubStr

   

    '4- Atualiza o novo indice para adicionar as proximas strings.

    lIndex = lIndex + lLen

   

    '5- Repetimos os passos. Entretanto, como estamos concatenando uma string de tamanho fixo que

    '   nao mudou, podemos ignorar as chamadas dos passos 1 e 2 acima.

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

    lIndex = lIndex + lLen

   

    Mid$(strBuffer, lIndex, lLen) = strSubStr

  

   

End Sub

 

Recomendo que você teste ela contra a implementação original para medir o ganho de performance.

 

Preferi ater minha resposta unicamente com comandos Visual Basic para ter uma resposta em VB “puro” J, mas outra abordagem seria utilizar chamadas de API, que deveria proporcionar uma solução um pouco mais rápida, usando por exemplo:

 

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSrc As Any, ByVal Length As Long)

 

How To Call Windows API Functions with Special Requirements from Visual Basic

http://support.microsoft.com/kb/202179/en-us

 

How To Retrieve Individual Bytes from a Multi-Byte Type in VB

http://support.microsoft.com/kb/171652/en-us

 

 

Voltando a solução apresentada acima, observe o que representa essa otimização em comparação com o código original:

 

  147  5319 [  0] Project1!Form1::Optimized

    7     0 [  1]   MSVBVM60!__vbaFreeStr

   10     0 [  2]     OLEAUT32!SysFreeString

   11     0 [  3]       kernel32!TlsGetValue

   19    11 [  2]     OLEAUT32!SysFreeString

   16     0 [  3]       OLEAUT32!APP_DATA::FreeCachedMem

    9     0 [  4]         ole32!CRetailMalloc_GetSize

   15     0 [  5]           ntdll!RtlSizeHeap

    3     0 [  6]             ntdll!RtlDebugSizeHeap

   19     0 [  7]               ntdll!_SEH_prolog

   17    19 [  6]             ntdll!RtlDebugSizeHeap

   14     0 [  7]               ntdll!RtlpCheckHeapSignature

   26    33 [  6]             ntdll!RtlDebugSizeHeap

   19     0 [  7]               ntdll!RtlEnterCriticalSection

   31    52 [  6]             ntdll!RtlDebugSizeHeap

   15     0 [  7]               ntdll!RtlpValidateHeap

   10     0 [  8]                 ntdll!RtlpValidateHeapHeaders

   27    10 [  7]               ntdll!RtlpValidateHeap

   38    89 [  6]             ntdll!RtlDebugSizeHeap

   35     0 [  7]               ntdll!RtlpValidateHeapEntry

   18     0 [  8]                 ntdll!RtlpCheckBusyBlockTail

   18     0 [  9]                   ntdll!RtlCompareMemory

   27    18 [  8]                 ntdll!RtlpCheckBusyBlockTail

   58    45 [  7]               ntdll!RtlpValidateHeapEntry

   44   192 [  6]             ntdll!RtlDebugSizeHeap

   34     0 [  7]               ntdll!RtlSizeHeap

   49   226 [  6]             ntdll!RtlDebugSizeHeap

    5     0 [  7]               ntdll!RtlDebugSizeHeap

    8     0 [  8]                 ntdll!RtlLeaveCriticalSection

    6     8 [  7]               ntdll!RtlDebugSizeHeap

   51   240 [  6]             ntdll!RtlDebugSizeHeap

    9     0 [  7]               ntdll!_SEH_epilog

   52   249 [  6]             ntdll!RtlDebugSizeHeap

   19   301 [  5]           ntdll!RtlSizeHeap

   11   320 [  4]         ole32!CRetailMalloc_GetSize

   37   331 [  3]       OLEAUT32!APP_DATA::FreeCachedMem

   22   379 [  2]     OLEAUT32!SysFreeString

   10   401 [  1]   MSVBVM60!__vbaFreeStr

  148  5730 [  0] Project1!Form1::Optimized

 

Compare a coluna Invocations abaixo contra a mesma coluna da solução não otimizada! Veja a diferença que se traduz em tempo de execução bem menor!

 

Nota: Os valores da tabela acima devem ser divididos por 2 (como regra geral) porque usei dois loops com os dois operadores nesse teste.

 

5887 instructions were executed in 5886 events (0 from other threads)

 

Function Name                               Invocations MinInst MaxInst AvgInst

MSVBVM60!__vbaFreeStr                            3      10      10      10

MSVBVM60!__vbaLenBstr                           2       6       6       6

MSVBVM60!__vbaMidStmtBstr                   10      16      16      16

MSVBVM60!__vbaMidStmtBstrB                 10      73      73      73

MSVBVM60!__vbaStrCopy                           2      19      19      19

MSVBVM60!__vbaStrMove                           1      12      12      12

MSVBVM60!omemset                                 1      75      75      75

MSVBVM60!rtcSpaceBstr                             1      22      22      22

OLEAUT32!APP_DATA::AllocCachedMem       3      58      60      58

OLEAUT32!APP_DATA::FreeCachedMem        3      35      46      39

OLEAUT32!GetAppData                              3      17      17      17

OLEAUT32!SysAllocStringByteLen                2      58      58      58

OLEAUT32!SysAllocStringLen                      1      31      31      31

OLEAUT32!SysFreeString                            3      22      22      22

Project1!Form1::Optimized                         1     157     157     157

kernel32!TlsGetValue                                6      11      11      11

ntdll!RtlAllocateHeap                                3      25      25      25

ntdll!RtlAllocateHeapSlowly                       9       4     339     118

ntdll!RtlCompareMemory                           3      18      18      18

ntdll!RtlCompareMemoryUlong                   3      66     250     180

ntdll!RtlDebugAllocateHeap                       6       6      85      45

ntdll!RtlDebugSizeHeap                            6       6      52      29

ntdll!RtlEnterCriticalSection                       6      19      19      19

ntdll!RtlFillMemoryUlong                            6      27      71      49

ntdll!RtlGetNtGlobalFlags                          6       4       4       4

ntdll!RtlLeaveCriticalSection                      6       8       8       8

ntdll!RtlSizeHeap                                     6      19      34      26

ntdll!RtlpCheckBusyBlockTail                      3      27      27      27

ntdll!RtlpCheckHeapSignature                    6      14      14      14

ntdll!RtlpGetExtraStuffPointer                    6      10      10      10

ntdll!RtlpUpdateIndexInsertBlock               3      12      12      12

ntdll!RtlpUpdateIndexRemoveBlock             3      14      14      14

ntdll!RtlpValidateHeap                              6      27      27      27

ntdll!RtlpValidateHeapEntry                       3      58      58      58

ntdll!RtlpValidateHeapHeaders                   9      10      10      10

ntdll!_SEH_epilog                                   15       9       9       9

ntdll!_SEH_prolog                                   15      19      19      19

ole32!CRetailMalloc_Alloc                          3       9       9       9

ole32!CRetailMalloc_GetSize                      3      11      11      11

 

 

Agora a melhor parte! Dicas quentes de otimização de código Visual Basic 6.

 

 

DICAS DE OTIMIZAÇÃO DE CÓDIGO EM VISUAL BASIC 6

 

1-   Coloque Refências de Objetos em variáveis auxiliares, chamadas cache.

 

*      Exemplo NÃO OTIMIZADO:

*      Set rst = New ADODB.Recordset

*      Set rst.ActiveConnection = CurrentProject.Connection

*      rst.Source = “tblTests”

*      rst.Open

 

*      For i = 1 to lRepeats

*            strName = rst.Fields(0).Name

*      Next

 

*      Exemplo OTIMIZADO:

*      Set rst = New ADODB.Recordset

*      Set rst.ActiveConnection = CurrentProject.Connection

*      rst.Source = “tblTests”

*      rst.Open

*      Dim fld as ADODB.Field

*      Set fld = rst.Fields(0)    ‘Criada como as Field.

 

*      For i = 1 to lRepeats

    strName = fld.Name

*      Next

 

*      Nota: O mesmo não se aplica para Visual Basic .NET que consegue otimizar o código quando identifica a versão convencional gerando um código melhor do que se o mesmo fosse otimizado.

 

2-   Use LenB para testar strings.

 

*      Exemplo NÃO OTIMIZADO:

*      If strValue = “” then

 

*      Exemplo OTIMIZADO:

*      If LenB(strValue) = 0 then

 

*      Nesse caso ao se usar LenB o VB consulta o tamanho da string já armazenado no início da mesma, num DWORD (4 bytes), e ao se usar a comparação com “” o VB cria uma string vazia para comparar com a string atual. Notem que o tamanho da string está em bytes, portanto, Len() teria que fazer a conversão que LenB() não faz.

 

3- Use vbNullString ao invés de inicializar uma string com “”

 

*      Exemplo NÃO OTIMIZADO:

*      strItem = “”

 

*      Exemplo OTIMIZADO:

*      strItem = vbNullString

 

*      No exemplo não otimizado o VB cria uma string nova e copia na variável, no exemplo otimizado o VB usa seu próprio ponteiro interno para uma string vazia, economizando tempo ao se inicializar uma string.

 

 

4- Evite concatenações desnecessárias.

 

*      Exemplo NÃO OTIMIZADO:

*      strTest = “A” & “B” & “C” & “D” & “E”

 

*      Exemplo OTIMIZADO:

*      strTest = “ABCDE”

 

É preferível criar uma longa string do que criar string menores e concatená-las pois a concatenação implica em realocar memória para o novo conteúdo, que é uma operação custosa.

 

5- Prefira usar Mid$ do que concatenar com &

 

*      Exemplo NÃO OTIMIZADO:

*       strAux = strAux & “AAA “

*       strAux = strAux & “BBB "

*       strAux = strAux & “CCC "

*       strAux = strAux & “DDD "

*       strAux = strAux & “EEE"

 

*      Exemplo OTIMIZADO:

*      Dim lIndex As Long

*      Dim strBuffer As String

*      Dim strSubStr As String

*      Dim lLen As Long

*          

*      'Aloca buffer para armazenar a string.

*      strBuffer = Space$(100)

*      lIndex = 1

*          

*      strSubStr = “AAA"

 

*      lLen = Len(strSubStr)

*      Mid$(strBuffer, lIndex, lLen) = strSubStr

*      lIndex = lIndex + lLen

*          

*      strSubStr = “BBB"

*      lLen = Len(strSubStr)

*      Mid$(strBuffer, lIndex, lLen) = strSubStr

*      lIndex = lIndex + lLen

*          

*      E assim sucessivamente. Isso evita a realocação de memória constante do exemplo não otimizado.

 

 

6- Ao comparar strings use StrComp ao invés de UCase

 

*      Exemplo NÃO OTIMIZADO:

*      If UCase(strValue)  = UCase(strValue2) then

 

*      Exemplo OTIMIZADO:

*      If StrComp(strValue, strValue2, vbTextCompare) = 0 then

 

*      Essa técnica é vantajosa para strings não muito grandes uma vez que em comparações de strings grandes não há muita diferença de performance.

 

 

7- Use o operador LIKE ao invés de comparar caracteres individuais.

 

*      Exemplo NÃO OTIMIZADO:

*      bMatch = True

*      For j = 1 to 5

*          intCh = AscW(Mid$(strTest, j, 1))

    Select Case j

                        Case 1, 3, 4, 5

                                if(IsCharAlpha(intCh) = 0) then

                                        bMatch = false

                                end if

                        Case 2

                                if(IsCharAlpha(intCh) <> 0) then

                                        bMatch = false

                                endif

    End Select

        if not bMatch then

          Exit For

     end if

*      Next

 

*      Exemplo OTIMIZADO:

*      bMatch = strTest Like “[A-Z]#[A-Z][A-Z][A-Z]”

 

*      Há grande diferença de performance quando usando a abordagem otimizada.

 

8- Use funções com terminação em $ sempre que possível.

 

*      Exemplo NÃO OTIMIZADO:

*      For i = 1 To 5

*          strValue = Left(strValue, 3)

*      Next

 

*      Exemplo OTIMIZADO:

*      For i = 1 To 5

*          strValue = Left$(strValue, 3)

*      Next

 

*      Ao utilizar as funções com terminação em $ não há conversões de tipos, ou seja, o retorno é sempre string. Ao usar as funções sem a terminação $ o retorno é sempre um tipo Variant que é convertido para String. Conversões implícitas de dados são prejudiciais em qualquer linguagem de programação, incluindo Visual Basic 6 tanto por razões de performance quanto por aumentar a probabilidade de se cometer bugs.

 

9- Use atribuições lógicas ao invés de IF’s

 

*      Exemplo NÃO OTIMIZADO:

*      If x = 5 Then

*          y = true

*      Else

*          y = false

*      End If

 

*      Exemplo OTIMIZADO:

*      y = (x = 5)

 

Nota: Essa otimização é válida para Access e Visual Basic 6.0 uma vez que em .NET e Visual C++ o compilador otimiza a chamada não otimizada.

 

 

10- For…Next é mais rápido que Do…Loop

 

*      Exemplo NÃO OTIMIZADO:

*      i = 1

*      Do Until i > nLimit

*          j = i

*          ‘Faz algo.

*          i = i + 1

*      Loop

 

*      Exemplo OTIMIZADO:

*      For i = 1 To nLimit

*          j = i

*          ‘Faz algo.

*      Next

 

*      A versão com Do…Loop é mais lenta porque é necessário se incrementar ou decrementar uma variável, quando o For…Loop faz isso automaticamente.

 

 

11- Use IF/ELSE/END IF ao invés de IIF()

 

*      Exemplo NÃO OTIMIZADO:

*      strValue = IIf(i Mod 2 = 0, “Mesmo”, “Diferente”)

 

*      Exemplo OTIMIZADO:

*      If i Mod 2 = 0 Then

*          strValue = “Mesmo”

*      Else

*          strValue = “Diferente”

*      End If

 

 

12- Em Arrays o For…Next é mais rápido que For Each…Next

 

*      Exemplo NÃO OTIMIZADO:

*      For Each Index In Array

*          j = Index

*      Next

 

*      Exemplo OTIMIZADO:

*      For i = LBound(Array) To UBound(Array)

*          j = Array(i)

*      Next

 

*      Atenção! Para Collections a regra oposta é a que se aplica.

 

 

13- Sempre use Early Binding.

 

*      Exemplo NÃO OTIMIZADO:

*      Dim rst as ADODB.Recordset

*      Dim strName as String

*      Dim fld as Object   ‘Late Binding, prejuízo de performance.

 

*      Set rst = New ADODB.Recordset

*      Set rst.ActiveConnection = CurrentProject.Connection

*      rst.Source = “tblTests”

*      rst.Open

 

*      Set fld = rst.Fields(0)

 

*      Exemplo OTIMIZADO:

*      Dim rst as ADODB.Recordset

*      Dim strName as String

*      Dim fld as ADODB.Field    ‘Early Binding, ganho de performance

 

*      Set rst = New ADODB.Recordset

*      Set rst.ActiveConnection = CurrentProject.Connection

*      rst.Source = “tblTests”

*      rst.Open

 

*      Set fld = rst.Fields(0)

 

 

14- Simplifique e otimize expressões com AND, OR e XOR

 

*      Exemplo NÃO OTIMIZADO:

*      If (x < 0 And y < 0) Or (x >= 0 And y >= 0) Then

 

*      Exemplo OTIMIZADO:

*      If (x Xor y) >= 0 then

 

 

15- Use a técnica de “curto-circuito” em expressões.

 

*      Exemplo NÃO OTIMIZADO:

*      If x > 0 And y <= 0 And z = 0 Then

 

*      Exemplo OTIMIZADO:

*      Select Case False

*          Case x > 0, y <= 0, z = 0

*          Case Else

*               ‘Faça isso.

*      End Select

 

*      O Visual Basic 6.0 quando avalia uma expressão como no exemplo não otimizado, valida cada condição desnecessariamente enquanto os compiladores C++ analisam apenas o suficiente para concluir a expressão. Entretanto, ao utilizar o artifício do Select Case o VB analisa a expressão fazendo “short-circuit” como no Visual C++.

 

16- Procure atribuir o resultado de um recordset em um array quando possível.

 

*      Exemplo NÃO OTIMIZADO:

*      Dim rs As New ADODB.Recordset

*      Dim fldName as ADODB.Field, fldName as ADODB.Field

*      rs.Open “SELECT au_lname, au_fname FROM authors”, “DSN=pubs”, , ,

*      Set fldLName = rs.Fields(“au_lname”)

*      Set fldFName = rs.Fields(au_fName”)

*      Do Until rs.EOF

*          List1.AddItem fldLName & “, “ & fldFName

*      Loop

*      rs.Close

 

*      Exemplo OTIMIZADO:

*      Dim rs As New ADODB.Recordset

*      Dim varArray() as Variant

*      Dim i As Long

 

*      rs.Open “SELECT au_lname, au_fname FROM authors”, “DSN=pubs”,

*      varArray() = rs.GetRows()

 

*      For i = 0 to UBound(varArray, 2)

*          List1.AddItem varArray(0, 1)

*      Next

 

*      Nota: Válido para quando for necessário colocar valores em um controle como num ListBox.

 

 

17 – Use AscW e ChrW$.

 

*      Exemplo não otimizado:

*      value = Right$(Asc(strSomeChar))

 

*      Exemplo otimizado:

*      value = Right$(AscW(strSomeChar))

 

*      VB usa Unicode internamente, o qual usa 2 bytes por caracter. Usando as funções Unicode o VB não tem que fazer a conversão de ASCII para Unicode implicitamente.

 

18 – Cheque existência de uma substring dentro de uma string usando InStrB

 

*      Como usar:

*      If InStrB(Text$, SearchFor$) <> 0 then

 

*      A versão byte de InStrB é a mais rápida e serve para checar se uma string existe dentro de outra desde que a localização não seja relevante, uma vez que o retorno é em bytes.

 

 

Espero que tenham gostado desse Desafio.

Agora vou publicar o terceiro Desafio da Semana!