Указатель ^ против s [1]
В функции, которая читает данные с данными (данные, означающие исключительно) с диска, что я должен предпочесть? Что лучше?
A) DiskStream.Read(Pointer(s)^, Count)
or
B) DiskStream.Read(s[1], Count)
Примечание:
Я знаю, что оба имеют тот же результат.
Я знаю, что перед вызовом Read я должен установить SetLength of S.
ОБНОВЛЕНИЕ
S - AnsiString.
Вот полная функция:
{Считывает кучу символов из файла. Почему "ReadChars", а не "ReadString"? Эта функция читает строки С++ (длина строки также не записывалась на диск). Итак, я должен указать количество символов для чтения в качестве параметра. }
function TMyStream.ReadChars(out s: AnsiString; CONST Count: Longint): Boolean;
begin
SetLength(s, Count);
Result:= Read(s[1], Count)= Count;
end;
Тест скорости
В моем тесте скорости первый подход был немного быстрее, чем второй. Я использовал файл 400 Мбайт, из которого я читал строки около 200000 раз. Процесс был установлен на высокий приоритет.
Лучшее время для чтения: 1,35 для варианта В и 1,37 для варианта А.
Средний балл:
В среднем, B забил также 20 мс лучше, чем A.
Тест повторялся 15 раз для каждого варианта.
Разница действительно маленькая. Он может попасть в диапазон ошибок измерения.
Вероятно, это будет важно, если я буду читать строки чаще и из большего файла.
Но на данный момент позвольте сказать, что обе строки кода выполняют одно и то же.
ANSWER
Вариант A - может быть крошечный бит быстрее
Вариант B - (очевидно) гораздо легче читать, и он больше Delphi-ish. Мои предпочтения.
Примечание:
Я видел Embarcadero, используя вариант A в примере TStreamReadBuffer, но с TBytes вместо String.
Ответы
Ответ 1
Помните, что при запуске
1. DiskStream.Read(Pointer(s)^, Count)
2. DiskStream.Read(s[1], Count)
Версия 1. будет быстрее.
Но вы должны быть уверены, что переменная s
явно локальная, или вы вызывали себя UniqueString(s)
перед циклом.
Так как pointer(s)^
не вызовет скрытый RTL-вызов UniqueString?()
с низким уровнем, он будет быстрее, чем s[1]
, , но вы можете переопределить некоторые существующие данные, если строка s
переменная распределяется между текущим контекстом и другим контекстом (например, если последний контент s
был получен из функции из значения свойства, или s
отправляется как параметр другому методу).
На самом деле наиболее правильным способом кодирования этого чтения AnsiString
из содержимого является:
s := '';
SetLength(s,Count);
DiskStream.Read(pointer(s)^,Count);
или
SetString(s,nil,Count);
DiskStream.Read(pointer(s)^,Count);
Вторая версия равна 1-й, но с одной строкой меньше.
Настройка s
to '' вызовет FreeMem()+AllocMem()
вместо ReallocMem()
в SetLength()
, поэтому будет избегать вызова на move()
и, следовательно, будет немного быстрее.
Фактически, вызов UniqueString?()
RTL, сгенерированный s[1]
, будет очень быстрым, так как вы уже вызвали SetLength()
перед его вызовом: поэтому s
уже уникален, а UniqueString?()
вызов RTL будет вернуться почти сразу. После профилирования разница между двумя версиями невелика: почти все время тратится на распределение строк и перемещение контента с диска. Возможно, s[1]
оказывается более "паскальным".
Ответ 2
Определенно обозначение массива. Часть стиля Delphi заключается в том, чтобы сделать ваш код легко читаемым, и легче рассказать, что происходит, когда вы говорите, что именно вы делаете. Приведение строки в указатель, а затем разыменование его выглядит запутанным; зачем ты это делаешь? Это не имеет смысла, если читатель не знает много о внутренних строках.
Ответ 3
Если вы заботитесь об оптимизации, вам следует предпочесть первый вариант. Просто посмотрите на код, сгенерированный компилятором:
Unit7.pas.98: Stream.Read(Pointer(S)^, 10);
00470EA9 8B55FC mov edx,[ebp-$04]
00470EAC B90A000000 mov ecx,$0000000a
00470EB1 8BC6 mov eax,esi
00470EB3 8B18 mov ebx,[eax]
00470EB5 FF530C call dword ptr [ebx+$0c]
Unit7.pas.99: Stream.Read(s[1], 10);
00470EB8 8B5DFC mov ebx,[ebp-$04]
00470EBB 85DB test ebx,ebx
00470EBD 7418 jz $00470ed7
00470EBF 8BC3 mov eax,ebx
00470EC1 83E80A sub eax,$0a
00470EC4 66833802 cmp word ptr [eax],$02
00470EC8 740D jz $00470ed7
00470ECA 8D45FC lea eax,[ebp-$04]
00470ECD 8B55FC mov edx,[ebp-$04]
00470ED0 E8CB3FF9FF call @InternalUStrFromLStr
00470ED5 8BD8 mov ebx,eax
00470ED7 8D45FC lea eax,[ebp-$04]
00470EDA E89950F9FF call @UniqueStringU
00470EDF 8BD0 mov edx,eax
00470EE1 B90A000000 mov ecx,$0000000a
00470EE6 8BC6 mov eax,esi
00470EE8 8B18 mov ebx,[eax]
00470EEA FF530C call dword ptr [ebx+$0c]
UPDATE
Вышеприведенный код генерируется компилятором Delphi 2009. Вы можете улучшить код с помощью директивы {$ STRINGCHECKS OFF}, но у вас все еще есть служебные вызовы функции UniqueStringU
:
Unit7.pas.100: Stream.Read(s[1], 10);
00470EB8 8D45FC lea eax,[ebp-$04]
00470EBB E8B850F9FF call @UniqueStringU
00470EC0 8BD0 mov edx,eax
00470EC2 B90A000000 mov ecx,$0000000a
00470EC7 8BC3 mov eax,ebx
00470EC9 8B18 mov ebx,[eax]
00470ECB FF530C call dword ptr [ebx+$0c]
Ответ 4
Второй вариант определенно больше "Стиль Delphi" (если вы посмотрите на версии Delphi заголовков Windows API, вы увидите, что большинство параметров указателя были преобразованы в параметры var
).
В дополнение к этому, второй вариант не нуждается в трансляции и более читаем IMHO.
Ответ 5
Я бы всегда использовал второй, который поддерживает безопасность типов. Я действительно не покупаю аргумент производительности, так как вы попали на диск в худшем случае, или в кеш-память, или в основную память, все из которых собираются сделать несколько операций с ЦП несколько тривиальными. Правильности следует уделять более высокий приоритет, чем производительность.
Тем не менее, я бы добавил, что это не то, что должно вас беспокоить слишком много, так как вы должны написать этот кусок кода один раз и только один раз. Поместите его в класс помощника и аккуратно заверните. Не стесняйтесь заботиться о оптимизации, переписывайте ее как ассемблер, что бы ни привлекало ваше воображение. Но d on't r epeat y.
Ответ 6
Если есть вероятность, что ваша функция будет вызвана с помощью Count из 0, тогда A) будет работать с Pointer(s)^
, просто оценивая nil
, а B) сбой при исключении проверки диапазона.
Если вы хотите использовать B) и по-прежнему обрабатывать графы 0 изящно, вы должны использовать:
function TMyStream.ReadChars(out s: AnsiString; const Count: Integer): Boolean;
begin
SetLength(s, Count);
Result := (Count = 0) or (Read(s[1], Count) = Count);
end;
Ответ 7
Второй (DiskStream.Read(s [1], Count)). Всякий раз, когда вы сталкиваетесь с нетипизированным параметром var, он читается как "берет адрес того, что передается как параметр". Таким образом, в этом случае вы передаете адрес первого символа строки s, который вы намеревались сделать.