Freigeben über


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