Desafio da Semana #2
DESAFIO DA SEMANA #2
Por: Roberto A. Farah
O desafio dessa semana foi feito pensando numa situação que encontramos por aqui com frequência. Problemas de performance em código legado, como, por exemplo, aplicações em Visual Basic 6. Na grande maioria das vezes temos que propor uma solução que seja a mais prática para nosso clientes, ou seja, a que menos exige dinheiro, recursos e mudanças drásticas para implementar. A solução ótima muitas vezes é se recomendar uma mudança de arquitetura, ou reescrita de alguma parte da aplicação, mas geralmente é a solução boa que acaba sendo escolhida: a otimização na parte identificada como sendo o gargalo de performance.
Certamente a mesma situação que vocês devem encontrar com seus clientes.
Pois bem, o Desafio da Semana está relacionado com isso.
SITUAÇÃO
Um cliente procura você para saber se você consegue otimizar uma rotina Visual Basic 6 que foi identificada pelos desenvolvedores como um dos gargalos de performance mais críticos da aplicação.
Portanto, você deve obter um baseline da performance atual para, posteriormente, medir com o código otimizado e quantificar o ganho de performance.
Para facilitar o código de medir performance está implementado. J
Então você deve criar uma rotina otimizada que execute mais rápido que a rotina atual.
Use apenas Visual Basic 6 para fazer a otimização.
Identifique:
PROBLEMA
O que está causando o gargalo de performance?
SOLUÇÃO
Apresente sua solução otimizada, que deve ser feita em Visual Basic 6.
Instruções:
a) Crie uma aplicação Visual Basic com essa interface e atributo Read-Only nos TextBox:
b) Crie um módulo .BAS e coloque:
Option Explicit
' Declara chamada de API para medir o tempo. QueryPeformanceCounter() e' mais precisa
' mas GetTickCount funciona para nossa aplicacao.
Declare Function GetTickCount Lib "Kernel32" () As Long
c) Abra o From1.frm e coloque:
Private Sub Command1_Click()
Dim i As Double
Dim lStartTime As Long
Dim lFinalTime As Long
Dim strText As String
strText = "String para ser concatenada!!!"
lStartTime = GetTickCount()
For i = 0 To 9000000 ' Nao desenrole esse loop... :)
Original strText ' Chama rotina original.
Next
lFinalTime = GetTickCount() - lStartTime
Text1.Text = CStr(lFinalTime)
End Sub
Private Sub Command2_Click()
Dim i As Double
Dim lStartTime As Long
Dim lFinalTime As Long
Dim strText As String
strText = "String para ser concatenada!!!"
lStartTime = GetTickCount()
For i = 0 To 9000000 ' Nao desenrole esse loop... :)
Optimized strText ' Chama rotina original.
Next
lFinalTime = GetTickCount() - lStartTime
Text2.Text = CStr(lFinalTime)
End Sub
' Fazer 10 concatenacoes de string.
Private Sub Original(ByVal strText As String)
Dim strOutput As String
Dim i As Integer
strOutput = vbNullString
For i = 1 To 10
strOutput = strOutput + strText
Next
End Sub
' Fazer 10 concatenacoes de string.
Private Sub Optimized(ByVal strText As String)
SEU CÓDIGO VEM AQUI
End Sub
Otimize, teste para ver a performance, modifique, teste novamente e veja qual o máximo de performance você consegue obter.
Semana que vem apresentarei uma resposta e estou curioso para ver as abordagens apresentadas! J
Comments
- Anonymous
April 25, 2006
Olá Amigos,
Inicialmente queria parabenizar nosso amigo Roberto Farah pela iniciativa pelo site. Realmente há aqui informações de grande valia para todos nós!
Fiz uma série de códigos afim de conseguir a máxima otimização para o problema proposto, segue abaixo toda a evolução até atingir o código mais otimizado.
'=================================
'CÓDIGO 1
'=================================
Dim strOutput As String
Dim i As Integer
strOutput = Space(Len(strText) * 10)
For i = 1 To 10
Mid$(strOutput, 1 + (i - 1) * Len(strText), Len(strText)) = strText
Next
'=================================
'CÓDIGO 2
'=================================
Dim strOutput As String
Dim i As Integer
strOutput = Space(Len(strText) * 10)
For i = 1 To Len(strText) * 10 Step Len(strText)
Mid$(strOutput, i, Len(strText)) = strText
Next
'=================================
'CÓDIGO 3
'=================================
Dim strOutput As String
Dim i As Integer
Dim Size As Integer
Size = Len(strText)
strOutput = Space(Size * 10)
For i = 1 To Size * 10 Step Size
Mid$(strOutput, i, Size) = strText
Next
'=================================
'CÓDIGO 4
'=================================
Dim strOutput As String
Dim i As Integer
Dim Size As Integer
Dim SizeFull As Integer
Size = Len(strText)
SizeFull = Size * 10
strOutput = Space(SizeFull)
For i = 1 To SizeFull Step Size
Mid$(strOutput, i, Size) = strText
Next
'=================================
'CÓDIGO 5
'=================================
Dim strOutput As String
Dim i As Long
Dim SizeB As Integer
Dim SizeFullB As Long
Dim p As Long
Dim p2 As Long
strOutput = Space(Len(strText) * 10)
p = StrPtr(strText)
p2 = StrPtr(strOutput)
SizeB = LenB(strText)
SizeFullB = p2 + SizeB * 10 - 1
For i = p2 To SizeFullB Step SizeB
CopyMemory2 i, p, SizeB
Next
Tabela com medições comparativas
CÓDIGO | MEDIÇÃO 1 | MEDIÇÃO 2 | MEDIÇÃO 3 | MÉDIA
1 42468 42609 42484 42520,33
2 36203 36266 36235 36234,67
3 35937 35531 35844 35770
4 35922 35391 35343 35552
5 35125 35297 34875 35099
Original 53422 53485 53109 53338,67
METODOLOGIA/CONCLUSÕES
A concatenação de string no Visual Basic realmente é lenta pois o Visual Basic nos disponibiliza a simplicidade máxima para trabalho com strings dinâmicas... Internamente, claro, ele precisa controlar alocação de memória, realocação, cópia de segmento de memória de um local para outro, etc, liberação, etc.
Pensando nisso resolvi iniciar os testes tentando alocar a memória necessária de uma vez só e logo após ir preenchendo os segmentos com a string original, fazendo então a concatenação. Realmente o ganho de performance foi muito grande.
Em seguida optei por reduzir o número de cálculos efetuados, fazendo que a posição para cópia dos trechos não fosse calculada dentro do For (Em código Visual Basic) mas sim na própria estrutura do For por meio do Step, o que resultou em um ganho de performance ainda maior.
Os dois próximos passos experimentei retirar a chamada excessiva de funções e realização de cálculos, o que mais uma vez resultou em ganho de performance.
Por fim resolvi então utilizar um CopyMemory diretamente para fazer a concatenação de strings para o buffer já alocado. Essa última alteração também resultou em ganho de performance, bem menos significativo (apenas 1.3% em relação a segunda melhor abordagem) mas que também será muito bem vindo, visto que o ideal é atingir a melhor performance.
Abraços a todos.
Danilo Pimentel - Anonymous
April 25, 2006
Olá amigos,
Acabei de verificar um equívoco :-(
Infelizmente postei o código 5 incorreto, esse aí é um código "pré 5" digamos assim! :-D
O código 5 que gerou o resultado mostrado na tabela de medições é esse:
'++++++++++++++++++++++++++++
Dim strOutput As String
Dim i As Long
Dim SizeB As Integer
Dim PstrText As Long
Dim PStart As Long
Dim PEnd As Long
strOutput = Space(Len(strText) * 10)
PstrText = StrPtr(strText)
PStart = StrPtr(strOutput)
SizeB = LenB(strText)
PEnd = PStart + SizeB * 10 - 1
For i = PStart To PEnd Step SizeB
CopyMemory i, PstrText, SizeB
Next
'++++++++++++++++++++++++++++
Abraços.
Danilo Pimentel - Anonymous
April 26, 2006
Olá Amigos,
Queria corrigir o código postado como CÓDIGO 5, esse que está aí não corresponde à medida realizada, essa era uma versão quando estava fazendo ainda a implementação. A versão final e que realmente gerou os valores apresentados segue abaixo:
'++++++++++++++++++++++++++
Dim strOutput As String
Dim i As Long
Dim SizeB As Integer
Dim PstrText As Long
Dim PStart As Long
Dim PEnd As Long
strOutput = Space(Len(strText) * 10)
PstrText = StrPtr(strText)
PStart = StrPtr(strOutput)
SizeB = LenB(strText)
PEnd = PStart + SizeB * 10 - 1
For i = PStart To PEnd Step SizeB
CopyMemory i, PstrText, SizeB
Next
'++++++++++++++++++++++++++
Segue também declaração da CopyMemory:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDestination As Long, ByVal pSource As Long, ByVal Length As Long)
Abraços,
Danilo Pimentel - Anonymous
April 27, 2006
Pessoal, como segunda-feira é feriado no Brasil e entendo que muita gente pode ter tido a semana apertada, vou deixar para postar a resposta (e os devidos elogios :) ) na próxima sexta assim dá tempo de mais gente postar.
Obrigado! - Anonymous
May 05, 2006
The comment has been removed