Resposta ao Desafio da Semana #2 [Performance - Visual Basic 6]


Por: Roberto A. Farah

CENÁRIO

Eis o link para o Desafio: https://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: https://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon98/html/vbstartpage.asp

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

https://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

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

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

https://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!