Delphi Strings (O Que, Quando e Como) - Otimizações

download Delphi Strings (O Que, Quando e Como) - Otimizações

of 6

description

Delphi strings (O que, quando e como) - Otimizações As versões mais recentes do Delphi oferecem pelo menos 4 tipos de strings. Cada uma delas com características e comportamentos próprios, tendo sua aplicabilidade destinada a situações diferentes. Esse artigo está dividido em duas partes. A primeira irá descrever esses quatro tipos de strings e suas características; a segunda etapa é composta por análises de situações comuns na programação e que constituem oportunidades para um otimizações.

Transcript of Delphi Strings (O Que, Quando e Como) - Otimizações

  • Delphi strings (O que, quando e como) - Otimizaes As verses mais recentes do Delphi oferecem pelo menos 4 tipos de strings. Cada uma delas com caractersticas e comportamentos prprios, tendo sua aplicabilidade destinada a situaes diferentes. Esse artigo est dividido em duas partes. A primeira ir descrever esses quatro tipos de strings e suas caractersticas; a segunda etapa composta por anlises de situaes comuns na programao e que constituem oportunidades para um otimizaes.

    Breve Histrico ShortString Este um tipo de string oriundo das primeiras verses do Delphi e herdado do Turbo Pascal, que possuia este como o nico tipo de string. A menos que uma alocao manual seja feita, o tipo ShortString reside na stack e no na heap e, do ponto de vista de alocao de memria, tem o mesmo comportameto que os tipos bsicos alocados estaticamente (integer, boolean, char, recors, enum, ...). Quando o tipo ShortString utilizado o Delphi pr-aloca um bloco de 256 bytes e utiliza o primeiro byte (AStr[0]) para armazenar o tamanho 'utilizado'.

    var

    AStr: ShortString;

    Voc tambm pode especificar um tamanho mximo para as strings, mas esse valor no pode passar 255.

    var

    s: string[50];

    e: string[256]; // error

    PChar A limitao de 255 bytes para string representa um fator crtico para aplicaes do mundo real. No Delphi 1 foi introduzido o tipo PChar que era um tipo semelhante ao "char *" da linguagem C.

    Entretanto, devido a segmentao da memria do Windows ser de 16-bits, o tipo PChar estava limitado a 65535 bytes (64 KB). Diferentemente do tipo ShortString, PChar no possui um "campo" destinado a armazenar o tamanho da string, mas possui um terminador "null".

    Long String (AnsiString) No Delphi 2 foi introduzido o tipo AnsiString com a finalidade de prover uma forma eficiente e rpida de

    trabalhar com strings grandes (32-bits). Agora era possvel manipular strings com at 2 GB. Comparando a estrutura do tipo AnsiString podemos dizer que ele uma "forma hbrida" dos tipos PChar e ShortString. Uma porque ele utiliza um terminador "null" para indicar o final da string (igual ao PChar) e, segundo, porque adota um campo para armazenar o tamanho da string e o primeiro caracter inicia na posio 1.

    var

    s: AnsiString;

  • Observe o mapa de memria da varivel s, alm do campo "Lenght" esse tipo de sring mantm um outro campo, "RefCount". Resumidamente, esse campo incrementado sempre que a varivel referencida.

    Isso permite ao Delphi gerenciar o tempo de vida da string, liberando a memria quando a string no mais utilizada. WideString

    WideString foi o ltimo tipo de string adicionado famla Delphi, mais precisamente, introduzido na verso 6. No entanto, somente a partir da verso 2009 que se tornou o tipo padro de string. A finalidade do tipo WideString o suporte a caracteres Unicode e a diferena reside no fato de que cada caracter WideString representado por 2 bytes e no 1, como em AnsiString.

    var

    s: WideString;

    O tipo String No Delphi, o tipo string no nada mais do que um alias para ShortString, PChar, AnsiString ou

    WideString, dependendo da verso do Delphi que for utilizado. Por exemplo, no Delphi 7 o tipo string equivalente a AnsiString; j no Delphi 2009 e, mas recentemente, no Delphi 2010, string equivalente a WideString.

    O uso do tipo string deve ser aplicado com um pouco de cautela, principalmente se retro-compatibilidade ou portabilidade for uma das necessidades do cdigo fonte produzido. Tomamos por exemplo o lanamento do Delphi 2009, que trouxe aos usurios o desafio da migrao do cdigo fonte de manipulao de strings para o formato wide.

    Semntica e comportamento Para o correto uso das string necessrio entender sua semntica e seu comportamento. a) Exemplo 1

    Lembre-se, o tipo ShortString pre-aloca 256 bytes e operaes de atribuio resultaro em cpia do contedo fonte.

    var

    s: ShortString;

    b: ShortString;

    begin

    ...

    s := 'Teste';

    b := s; // O contedo de 's' copiado para 'b'

    s[1] := 'X' // A varivel 'b' ainda contm 'Teste'

    b) Exemplo 2

    O tipo PChar est frequentemente associado a alocao dinnimica e atribuies somente copiam o ponteiro do destino, no o contedo.

    var

    s: PChar;

    b: PChar;

    begin

    ...

  • s := 'Teste';

    b := s; // A varivel 'b' aponta para 's'

    s[0] := 'X' // Tanto 's' quanto 'b' contm 'Xeste'

    c) Exemplo 3

    Tanto o tipo AnsiString quanto WideString so semelhantes ao tipo PChar quando uma atribuio direta feita, ou seja, somente copiado o ponteiro do destino. Alm disso, AnsiString e WideString possuem um campo RefCount(usado para saber quando a string no mais utilizada) que, na atribuio direta,

    incrementado.. Observe o exemplo baixo.

    var

    s: AnsiString;

    b: AnsiString;

    begin

    ...

    s := 'Teste';

    b := s; // Ambas apontam para a mesma rea de memria e

    //o campo "RefCount' incrementado.

    d) Exemplo 4

    Vamos tomar o mesmo exemplo anterior, mas com uma linha a mais de cdigo.

    var

    s: AnsiString;

    b: AnsiString;

    begin

    ...

    s := 'Teste';

    b := s; // b recebe o ponteiro de 's'e o RefCount incrementado.

    s[1] := 'X'; // Faz uma cpia de 'b' com o primeiro byte modificado e

    //decrementa o RefCount de 'b'.

    Vamos detalhar o trabalho realizado pelo Delphi em cada uma das linhas ilustrando as estruturas em memria. Step 1) s := 'Teste';

    feito a alocao de memria para armazena a string. O campo "Lenght" setado para 5 (que o nmero de caracteres da string) e o campo "RefCount" setado para 1. A varivel 's' passa a apontar para o incio da string.

    Step 2) b := s;

    Observe que a linha de cdigo acima simplesmente copiou o ponteiro da varivel 's' para a varivel 'b' e incrementou o campo 'RefCount', ou seja, abas as variveis esto apontando para a mesma rea de

    memria. Step 3) s[1] := 'X';

  • Aqui a alterao de um simples byte resultou e vrias instrues. Pelo fato do "RefCount" ser superior a 1, ou seja, por haver mais do que uma referncia, foi realizada a alocao de uma nova string; copiado o contedo da string original; setado 1 para o "RefCount"; alterado o primeiro byte para 'X'; atribudo o ponteiro da nova string para a varivel 's'. Por ltimo, o 'RefCount' da string original decrementado.

    Otimizaes no uso de strings

    Uso de parmetros do tipo const A plavrava reservada "const" um modificador usado para definir algo como esttico, que no muda. E

    quando associadas com parmetros do tipo string, h um incremento no desempenho que pode ser perceptvel, dependendo da intensidade de uso. Vamos tomar como base uma funo simples.

    function GetStrSize(s: string): Integer;

    begin

    Result := Length(s);

    end;;

    Aqui o parmetro "s" ir incondicionalmente incrementar o campo "RefCount" da string a qual ele est

    referenciando antes de iniciar a execuo da primeira linha de cdigo da funo. Pode parecer um tarefa simples e pouco honerosa para o sistema, mas totalmente desnecessria porque o parmetro somente utilizado para leitura e nunca para escrita. Uma cdigo fonte mais eficiente, nesse caso, facilmente obtido apenas especificando o parmetro como "constante".

    function GetStrSize2(const s: string): Integer;

    begin

    Result := Length(s);

    end;

    A boa notcia que as otimizaes implementada nos compiladores mais recentes j detectam esse tipo de situao e geram um cdigo final mais eficiente. Evitando retorno do tipo string

    Quando voc for codificar uma funo na qual pretendido retornar uma string modificada da que passada por parmetro, aconselhado que seja feito atravs do prprio parmetro. A menos, claro, que voc necessite do valor original.

    function AddChar(const s: string): string;

    begin

    Result := s + '*';

    end;

    O exemplo acima, como pode ser notado, apenas acrescenta um caracter no final da string passa por parmetro. J a funo abaixo faz a mesma coisa, mas retorna a nova string atravs do prprio parmetro.

    procedure AddChar2(var s: string);

    begin

    s := s + '*';

    end;

  • Tanto o modificador "const" da primeira funo quanto o modificador "var" da segunda funo fazem com que no seja necessrio o incremento do "RefCount". Entretanto a segunda funo mais eficiente porque no requer que a string do parmetro seja duplicada, porque a concatenao feita diretamente na string original. Postergando condicionais de comparao de string

    Uma situao bem corriqueira no dia-a-dia, principalmente quando se est trabalhando com algum tipo de parser, codificar testes condicionais com mais do que uma validao onde h comparaes string.

    var

    lFailed: Boolean;

    lText: string;

    begin

    ...

    if (lText = '') and (not lFailed) then

    ...

    end;

    No exemplo acima o primeiro teste do "if" faz uma comparao entre string e o segundo uma

    comparao que envolve uma varivel booleana. Sabendo que uma comparao entre string uma tarefa "onerosa" para o processador, o mais sensato alterarmos a ordem das condies de forma que os testes mais simples sejam atendidos primeiros. Isso evita, nesse nosso exemplo, uma comparao de string desnecessria quando a varivel lFailed forFalse. Esse um tipo de Boas Prticas de Programao pode ser extentido para casos de forma geral que exigam um processamento extra. Redundncia de chamadas

    Por mais otimizado que a biblioteca do Delphi esteja, a chamada a uma funo de manipulao de string sempre impem alguma penalidade se comparado com operaes simples, e evitar chamadas desnecessrias ou redundantes certamente um cuidado bem vindo. O cdigo abaixo um pequeno trecho retirado de uma mtodo de validao de email. Podemos notar o uso repetido e redundante da funo "Pos".

    function IsValidEmail(const EMail: string): Boolean;

    begin

    ...

    if (Pos('@', EMail) 0) and (Pos('.', EMail) 0) then

    begin

    if (Pos('@', EMail) = 1) or (Pos('@', EMail) = Length(EMail)) or

    (Pos('.', EMail) = 1) or (Pos('.', EMail) = Length(EMail)) or (Pos(' ', EMail) = 0) then

    Result := False

    ...

    Esse um exemplo simples que utiliza funes especficas para tratamento de strings, mas o problema no exclusivo e estende-se para qualquer outro tipo de redundncia.

    function IsValidEmail(const EMail: string): Boolean;

    var

    lPos_Dot: Integer;

    lPos_Arroba: Integer;

    begin

    ...

    lPos_Dot := Pos('.', EMail);

    lPos_Arroba := Pos('@', EMail);

    if (lPos_Arroba 0) and (lPos_Dot 0) then

    begin

    if (lPos_Arroba = 1) or (lPos_Arroba = Length(EMail)) or

    (lPos_Dot = 1) or (lPos_Dot = Length(EMail)) or (Pos(' ', EMail) = 0) then

    Result := False

    ...

    A simples adio de duas variveis locais evitou quatro chamadas desnecessrias funo "Pos".

  • Verificando se uma string est vazia

    Existem muitas maneiras de conferir se uma string ou no vazia, mas voc sabe qual delas a mais eficiente? Abaixo eu apresento trs das formas mais freqentemente empregadas.

    var

    lMyStr: string;

    begin

    lMyStr := Caption;

    if lMyStr = '' then

    lMyStr := 'Maneira mais eficiente';

    if Length(lMyStr) = 0 then

    lMyStr := 'Maneira menos eficiente';

    if lMyStr[1] = '' then

    lMyStr := 'Forma desaconselhada';

    Para entender melhor a verdadeira razo do primeiro if conter a forma mais adequada de verificar se um

    string est ou no vazia, vou postar o cdigo assemble gerado pelo compilador. Mesmo voc no estando familiarizado com a linguagem assembly, tenho certeza que ser fcil o entendimento. O cdigo gerado pelo compilador apresentado logo abaixo da linha do cdigo fonte respectiva e cada um dos if acima est destacado em uma cor diferente.

    fuStrTest.pas.127: if lMyStr = '' then

    07992023 837DFC00 cmp dword ptr [ebp-$04],$00

    07992027 750D jnz $07992036

    fuStrTest.pas.128: lMyStr := 'Maneira mais eficiente';

    07992029 8D45FC lea eax,[ebp-$04]

    0799202C BADC209907 mov edx,$079920dc

    07992031 E842F0FFFF call $07991078

    fuStrTest.pas.129: if Length(lMyStr) = 0 then

    07992036 8D45FC lea eax,[ebp-$04]

    07992039 E822F0FFFF call $07991060

    0799203E E83DF0FFFF call $07991080

    07992043 85C0 test eax,eax

    07992045 750D jnz $07992054

    fuStrTest.pas.130: lMyStr := 'Maneira menos eficiente';

    07992047 8D45FC lea eax,[ebp-$04]

    0799204A BA18219907 mov edx,$07992118

    0799204F E824F0FFFF call $07991078

    fuStrTest.pas.131: if lMyStr[1] = '' then

    07992054 8B45FC mov eax,[ebp-$04]

    07992057 66833800 cmp word ptr [eax],$00

    0799205B 750D jnz $0799206a

    fuStrTest.pas.132: lMyStr := 'Forma desaconselhada';

    0799205D 8D45FC lea eax,[ebp-$04]

    07992060 BA54219907 mov edx,$07992154

    07992065 E80EF0FFFF call $07991078

    Somente observando o nmero de instrues necessrias para cada uma das trs situaes j suficiente para comprovar que o primeiro if o mais eficiente. Entretanto o segundo if, que utiliza a funo Length,

    merece alguns comentrios. Observe que h duas instrues call (call $07991060 e call $07991080) que so, na verdade, chamadas

    para as funes EnsureUnicodeString e UStrLen, respectivamente. Essas funes so compostas por diversas instrues e, do ponto de vista de micro otimizaes, impem uma penalidade de performance bastante grande. Mesmo no tento realizados testes estatsticos mais precisos, posso afirmar que um simples if lMyStr = '' then incontveis vezes mais rpido que o uso da funo Length.

    Novamente ressaltando que isso do ponto de vista de micro otimizaes e para a maioria dos desenvolvedores o ganho de desempenho seria imperceptvel.