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 (5)

  1. Danilo Pimentel says:

    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

  2. Danilo Pimentel says:

    Olá amigos,

    Acabei de verificar um equívoco 🙁

    Infelizmente postei o código 5 incorreto, esse aí é um código "pré 5" digamos assim! 😀

    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

  3. Danilo Pimentel says:

    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

  4. Roberto Farah says:

    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!

  5. Farah says:

    Danilo, parabens pelas solucoes apresentadas! Voce apresentou nao uma mas duas otimas estragetias para otimizar a construcao original.

    Minha resposta é basicamente a mesma com uma pequena otimização adicional que em determinados cenários contribui para melhorar a performance ainda mais. Explicarei porque essas abordagens são mais rápidas e colocarei muitas dicas de otimização de código Visual Basic 6.

    Continue participando! 🙂

    Obrigado