Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Оптимизация Delphi-приложений. Шаг за шагом.


Информационный Канал Subscribe.Ru


Оптимизация Delphi-приложений. Шаг за шагом.

Строки изнутри

 

В этом выпуске еще раз поговорим о строках. Они вам еще не надоели? :) Ну ничего, зато рассмотрим их исчерпывающе.

Представляю вам статью соавтора рассылки Андрея Подкина.

Кстати, поскольку я погряз сразу в нескольких проектах и делаю выпуски рассылки редко, то приглашаю соавторов. Вести рассылку очень просто. Главное, чтобы вам было, что написать по теме, исходя из своего опыта. Ну а аудитория в несколько тысяч подписчиков никому не помешает :) Так что желающие стать соавторами, связывайтесь со мной.

А теперь статья.

 

Строки в Delphi настолько простой и в то в то же время мощный тип данных, что, с одной стороны, их можно начать использовать, ничего о них не зная, и, с другой стороны, чтобы использовать их оптимально, надо знать о них очень много. Особенно, когда дело заходит о приведении строк из одного типа в другой (String -> PChar и обратно).

Что же представляет собой строка (String или AnsiString) в Delphi?

В первую очередь - это динамический массив. Значит сама переменная типа String - это указатель на первый символ строки. По смещению -4 находится длина строки, а по смещению -8 - счетчик ссылок на строку (или $FFFFFFFF, если строка является константой).

Но строка из N символов занимает не (N+8) байт, как можно было бы ожидать, а (N+9): после последнего символа строки всегда находится символ с кодом 0.

Всё это легко проверить экспериментально, вооружившись окном CPU (View - Debug Windows - CPU) и просматривая в нем области памяти, отведенные под строки. Кроме того, следует помнить, что нумерация символов в строке начинается с 1 (а не с 0, как в динамических массивах).

Что следует из такой структуры строки:

1. Раз у строки есть счетчик ссылок, то значит, копирование строки приводит только к копированию указателя и увеличению счетчика ссылок на 1:

var
S1, S2: String;
...
S2 := S1; // просто копирование указателя.

2. Длина строки хранится вместе со строкой, следовательно, для строк можно использовать функцию Length, не опасаясь за оптимизацию:

i := 0;
while (i <= Length(S)) and (S[i] <> 'A') do
  Inc(i);

В данном случае вынесение вызова Length за цикл не даст никакого эффекта:

L := Length(S);
i := 0;
while (i <= L) and (S[i] <> 'A') do   // Ничуть не эффективней.
  Inc(i);

3. Каждая операция конкатенации приводит к выделению новой области памяти под строку (результат конкатенации). Причем, чем больше надо выделить памяти, тем больше времени будет занимать эта операция.

Попробуйте сами оценить эффективность нижеприведенной функции на строках длиной несколько миллионов символов (например, на содержимом большого файла).

function ReverseString1(const Source: String): String;
var
  i: Integer;
begin
  Result := '';
  for i := Length(Source) downto 1 do
    Result := Result + Source[i];
end;

4. Повысить эффективность использования строки можно за счет использования процедуры SetLength.

SetLength также выделяет новую память под строку. Важно помнить, что после использования SetLength надо использовать не конкатенацию (которая снова приведет к выделению памяти), а доступ к символам строки по индексам. Пример более эффективной функции реверсирования строки:

function ReverseString2(const Source: String): String;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := 1 to Length(Source) do
    Result[i] := Source[Length(Source) - i + 1];
end;

5. Поскольку строка реально является указателем на первый символ, а после последнего символа следует #0, то этот указатель можно свободно использовать везде, где требуется указатель типа PChar. Т.е. приведение PChar к строке приводит всего лишь к копированию указателя.

var
  S: String;
  P: PChar;
...

S := 'ABC';
P := PChar(S);
if Pointer(S) = Pointer(P) then
  ShowMessage('Указатели равны'); // Сообщение выводится всегда.

6. Поскольку для представления строки длиной N символов требуется (N+9) байт, то физически невозможно обеспечить преобразование PChar в String простым копированием указателя. Т.е. при этом происходит выделение памяти под новую строку.

var
  S: String;
  P: PChar;
...

P := 'ABC';
S := String(P);
if Pointer(S) = Pointer(P) then
  ShowMessage('Указатели равны'); // Сообщение не выводится никогда.

Это обязательно следует учитывать в коде, содержащем преобразования String в PChar и обратно. Например, при использовании строк (String) совместно с функциями WinAPI или методами TStrings GetText и SetText.

procedure TForm1.Button2Click(Sender: TObject);
var
  S: String;
begin
  S := String(ListBox1.Items.GetText); // Чрезвычайно не эффективный код.
  ListBox1.Items.SetText(PChar(S));
end;

Строку S := String(ListBox1.Items.GetText); лучше заменить на S := ListBox1.Items.Text; (свойство Text в TStrings читается не через GetText, а через GetTextStr).

 

PS. Достаточно много полезной информации об эффективной работе со с строками можно извлечь, анализируя исходные тексты Delphi RTL, в частности модуля StrUtils.

Andrew panda_bear@mail.ru

 

Владимир Волосенков uno@tut.by

 

Владимир ВОЛОСЕНКОВ

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу


В избранное