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
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!