Sei sulla pagina 1di 6

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.

Potrebbero piacerti anche