Delphi: быстрое (э) широкополосное объединение - PullRequest
2 голосов
/ 09 июня 2010

У меня есть функция, работа которой заключается в преобразовании ADO Recordset в html:

class function RecordsetToHtml(const rs: _Recordset): WideString;

И в этой функции много конкатенации строк:

   while not rs.EOF do
   begin
      Result := Result+CRLF+
         '<TR>';

      for i := 0 to rs.Fields.Count-1 do
         Result := Result+'<TD>'+VarAsWideString(rs.Fields[i].Value)+'</TD>';

      Result := Result+'</TR>';
      rs.MoveNext;
    end;

С несколькими тысячами результатов функция занимает, как чувствовал бы любой пользователь, слишком много времени для запуска. Delphi Sampling Profiler показывает, что 99,3% времени затрачивается на широкополосную конкатенацию (@WStrCatN и @WstrCat).

Может кто-нибудь придумать способулучшить широкополосную конкатенацию?я не думаю, что в Delphi 5 есть какой-либо струнный конструктор.И Format не поддерживает Unicode.


И чтобы никто не пытался укротить: представьте, что вы реализуете интерфейс:

IRecordsetToHtml = interface(IUnknown)
    function RecordsetToHtml(const rs: _Recordset): WideString;
end;

Update One

Я думал об использовании IXMLDOMDocument, чтобы создать HTML как XML.Но потом я понял, что окончательный HTML-код будет xhtml, а не html - небольшая, но важная разница.

Обновление два

Статья базы знаний Microsoft: КакУлучшение производительности конкатенации строк

Ответы [ 5 ]

2 голосов
/ 10 июня 2010

WideString по своей сути медленные, потому что они были реализованы для совместимости с COM и проходят через COM-вызовы. Если вы посмотрите на код, он продолжит перераспределение строки и вызовет SysAllocStringLen () & C, которые являются API-интерфейсами из oleaut32.dll. Он не использует диспетчер памяти Delphi, но AFAIK он использует диспетчер памяти COM. Поскольку большинство HTML-страниц не используют UTF-16, вы можете получить лучший результат, используя собственный тип строки Delphi и список строк, хотя следует соблюдать осторожность при преобразовании из UTF и фактической кодовой страницы, и преобразование также снизит производительность , Также вы используете функцию VarAsString (), которая, вероятно, преобразует вариант в AnsiString , а затем преобразует в WideString. Проверьте, есть ли в вашей версии Delphi функция VarAsWideString () или что-то подобное, чтобы избежать этого, или используйте автоматическое преобразование Delphi, если вы уверены, что ваш вариант никогда не будет равен NULL.

1 голос
/ 11 июня 2010

Я не могу тратить время прямо сейчас, чтобы дать вам точный код.

Но я думаю, что самое быстрое, что вы можете сделать, это:

  1. Переберите все строки и суммируйте их длину, добавив дополнительные теги таблицы, которые вам понадобятся.

  2. Используйте SetString для выделения одной строки правильной длины.

  3. Повторно переберите все строки и используйте процедуру «Переместить» , чтобы скопировать строку в нужное место в последней строке.

Ключевым моментом является то, что многие конкатенации к строке занимают все больше и больше времени из-за постоянного выделения и освобождения памяти. Одно выделение будет вашим самым большим временем экономии.

1 голос
/ 11 июня 2010

я нашел лучшее решение.Открытый исходный код HtmlParser для Delphi, имеет вспомогательный класс TStringBuilder.Он внутренне используется для построения того, что он называет DomString s, что на самом деле является псевдонимом WideString:

TDomString = WideString;

С небольшим изменением класса:

TStringBuilder = class
public
   constructor Create(ACapacity: Integer);
   function EndWithWhiteSpace: Boolean;
   function TailMatch(const Tail: WideString): Boolean;
   function ToString: WideString;
   procedure AppendText(const TextStr: WideString);
   procedure Append(const value: WideString);
   procedure AppendLine(const value: WideString);
   property Length: Integer read FLength;
end;

Внутренности процедуры становятся:

while not rs.EOF do
begin
   sb.Append('<TR>');

   for i := 0 to rs.Fields.Count-1 do
      sb.Append('<TD>'+VarAsWideString(rs.Fields[i].Value));

   sb.AppendLine('</TR>');

   rs.MoveNext;
end;

Затем код чувствует, что работает бесконечно быстро.Профилирование показывает много улучшение;WideString манипулирование и подсчет длины стали незначительными.На его месте были собственные внутренние операции FastMM.

Примечания

  1. Отличный уловок при ошибочном форсировании всех строк в текущей кодовой страницечем VarAsWideString)
  2. Некоторые закрывающие теги HTML являются необязательными;опущены те, которые логически не имеют смысла.
1 голос
/ 10 июня 2010

Да, ваш алгоритм явно в O (n ^ 2).

Вместо того, чтобы возвращать string, попробуйте вернуть TStringList и замените ваш цикл на

   while not rs.EOF do
   begin
      Result.Add('<TR>');

      for i := 0 to rs.Fields.Count-1 do
         Result.Add( '<TD>'+VarAsString(rs.Fields[i].Value)+'</TD>' );

      Result := Result.Add('</TR>');
      rs.MoveNext;
    end;

Вы можете сохранить Result, используя TStringList.SaveToFile

0 голосов
/ 10 июня 2010

Widestring не считается ссылкой, любая модификация означает манипулирование строкой. Если ваш контент не закодирован в Юникоде , вы можете внутренне использовать нативную строку (счетчик ссылок), чтобы объединить строку, а затем преобразовать ее в широкую строку. Пример выглядит следующим образом:

var
  NativeString: string;
begin
   // ...
   NativeString := '';

   while not rs.EOF do
   begin
     NativeString := NativeString + CRLF + '<TR>';

     for i := 0 to rs.Fields.Count-1 do
       NativeString := NativeString + '<TD>'+VarAsString(rs.Fields[i].Value) + '</TD>';

     NativeString := NativeString + '</TR>';
     rs.MoveNext;
   end;

   Result := WideString(NativeString);

Я также видел другой подход: закодировать Unicode в UTF8String (в качестве подсчитанной ссылки), объединить их и, наконец, преобразовать UTF8String в Widestring. Но я не уверен, что две UTF8String могут быть соединены напрямую. Время на кодирование также следует учитывать.

В любом случае, хотя конкатенация Widestring намного медленнее, чем операции с собственными строками. Но это IMO все еще приемлемо. Следует избегать слишком большой настройки подобных вещей. Серьезно принимая во внимание производительность, вы должны затем обновить Delphi по крайней мере до 2009 года. Затраты на покупку инструмента в долгосрочной перспективе дешевле, чем тяжелые хаки на старых Delphi.

...