Использование const догматично или рационально?
В Delphi вы можете ускорить ваш код, передав параметры как const
например,
function A(const AStr: string): integer;
//or
function B(AStr: string): integer;
Предположим, что обе функции имеют один и тот же код внутри, разница в скорости между ними незначительна, и я сомневаюсь, что ее можно измерить даже с помощью счетчика циклов, например:
function RDTSC: comp;
var
TimeStamp: record case byte of
1: (Whole: comp);
2: (Lo, Hi: Longint);
end;
begin
asm
db $0F; db $31;
mov [TimeStamp.Lo], eax
mov [TimeStamp.Hi], edx
end;
Result := TimeStamp.Whole;
end;
Причина в том, что все const
делает в функции А, чтобы предотвратить подсчет ссылок AStr
быть увеличен.
Но приращение занимает всего один цикл одного ядра моего многоядерного процессора, так что...
Почему я должен беспокоиться const
?
7 ответов
Если нет другой причины для функции содержать неявное try/finally и сама функция не выполняет много работы, использование const может привести к значительному ускорению (однажды я получил одну функцию, которая использовала>10% общее время выполнения в профилировании уменьшается до <2%, просто добавив const в нужном месте).
Кроме того, подсчет ссылок занимает намного больше, чем один цикл, потому что он должен выполняться с префиксом блокировки по соображениям безопасности потоков, поэтому мы говорим больше как 50-100 циклов. Больше, если что-то в той же строке кэша было изменено другим промежуточным ядром.
Что касается невозможности измерить это:
program Project;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
Math;
function GetThreadTime: Int64;
var
CreationTime, ExitTime, KernelTime, UserTime: TFileTime;
begin
GetThreadTimes(GetCurrentThread, CreationTime, ExitTime, KernelTime, UserTime);
Result := PInt64(@UserTime)^;
end;
function ConstLength(const s: string): Integer;
begin
Result := Length(s);
end;
function NoConstLength(s: string): Integer;
begin
Result := Length(s);
end;
var
s : string;
i : Integer;
j : Integer;
ConstTime, NoConstTime: Int64;
begin
try
// make sure we got an heap allocated string;
s := 'abc';
s := s + '123';
//make sure we minimize thread context switches during the timing
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
j := 0;
ConstTime := GetThreadTime;
for i := 0 to 100000000 do
Inc(j, ConstLength(s));
ConstTime := GetThreadTime - ConstTime;
j := 0;
NoConstTime := GetThreadTime;
for i := 0 to 100000000 do
Inc(j, NoConstLength(s));
NoConstTime := GetThreadTime - NoConstTime;
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
WriteLn('Const: ', ConstTime);
WriteLn('NoConst: ', NoConstTime);
WriteLn('Const is ', (NoConstTime/ConstTime):2:2, ' times faster.');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
if DebugHook <> 0 then
ReadLn;
end.
Производит этот вывод в моей системе:
Const: 6084039
NoConst: 36192232
Const is 5.95 times faster.
РЕДАКТИРОВАТЬ: становится немного интереснее, если мы добавим некоторую конкуренцию в потоках:
program Project;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
Classes,
Math;
function GetThreadTime: Int64;
var
CreationTime, ExitTime, KernelTime, UserTime: TFileTime;
begin
GetThreadTimes(GetCurrentThread, CreationTime, ExitTime, KernelTime, UserTime);
Result := PInt64(@UserTime)^;
end;
function ConstLength(const s: string): Integer;
begin
Result := Length(s);
end;
function NoConstLength(s: string): Integer;
begin
Result := Length(s);
end;
function LockedAdd(var Target: Integer; Value: Integer): Integer; register;
asm
mov ecx, eax
mov eax, edx
lock xadd [ecx], eax
add eax, edx
end;
var
x : Integer;
s : string;
ConstTime, NoConstTime: Integer;
StartEvent: THandle;
ActiveCount: Integer;
begin
try
// make sure we got an heap allocated string;
s := 'abc';
s := s + '123';
ConstTime := 0;
NoConstTime := 0;
StartEvent := CreateEvent(nil, True, False, '');
ActiveCount := 0;
for x := 0 to 2 do
TThread.CreateAnonymousThread(procedure
var
i : Integer;
j : Integer;
ThreadConstTime: Int64;
begin
//make sure we minimize thread context switches during the timing
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_HIGHEST);
InterlockedIncrement(ActiveCount);
WaitForSingleObject(StartEvent, INFINITE);
j := 0;
ThreadConstTime := GetThreadTime;
for i := 0 to 100000000 do
Inc(j, ConstLength(s));
ThreadConstTime := GetThreadTime - ThreadConstTime;
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
LockedAdd(ConstTime, ThreadConstTime);
InterlockedDecrement(ActiveCount);
end).Start;
while ActiveCount < 3 do
Sleep(100);
SetEvent(StartEvent);
while ActiveCount > 0 do
Sleep(100);
WriteLn('Const: ', ConstTime);
ResetEvent(StartEvent);
for x := 0 to 2 do
TThread.CreateAnonymousThread(procedure
var
i : Integer;
j : Integer;
ThreadNoConstTime: Int64;
begin
//make sure we minimize thread context switches during the timing
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_HIGHEST);
InterlockedIncrement(ActiveCount);
WaitForSingleObject(StartEvent, INFINITE);
j := 0;
ThreadNoConstTime := GetThreadTime;
for i := 0 to 100000000 do
Inc(j, NoConstLength(s));
ThreadNoConstTime := GetThreadTime - ThreadNoConstTime;
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
LockedAdd(NoConstTime, ThreadNoConstTime);
InterlockedDecrement(ActiveCount);
end).Start;
while ActiveCount < 3 do
Sleep(100);
SetEvent(StartEvent);
while ActiveCount > 0 do
Sleep(100);
WriteLn('NoConst: ', NoConstTime);
WriteLn('Const is ', (NoConstTime/ConstTime):2:2, ' times faster.');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
if DebugHook <> 0 then
ReadLn;
end.
На 6-ядерном компьютере это дает мне:
Const: 19968128
NoConst: 1313528420
Const is 65.78 times faster.
EDIT2: замена вызова Length на вызов Pos (я выбрал наихудший случай, ищите что-то, что не содержится в строке):
function ConstLength(const s: string): Integer;
begin
Result := Pos('x', s);
end;
function NoConstLength(s: string): Integer;
begin
Result := Pos('x', s);
end;
результаты в:
Const: 51792332
NoConst: 1377644831
Const is 26.60 times faster.
для резьбового корпуса и:
Const: 15912102
NoConst: 44616286
Const is 2.80 times faster.
для не резьбового корпуса.
Не забывай это const
не только для того, чтобы обеспечить эти крошечные улучшения производительности.
С помощью const
Любой, кто читает или поддерживает код, объясняет, что значение не должно обновляться, и позволяет компилятору отлавливать любые случайные попытки сделать это.
Таким образом, делая ваш код более читабельным и обслуживаемым, вы также можете сделать его немного быстрее. Какие есть веские причины не использовать const
?
Использование const предотвращает неявный блок try/finally, который на x86 стоит намного дороже, чем подсчет ссылок. Это действительно отдельная проблема для семантического значения const. Обидно, что производительность и семантика смешаны таким образом.
Тип String является особым случаем, потому что он управляется Delphi (копирование по требованию), и поэтому не идеально подходит для ответа на ваш вопрос.
Если вы тестируете свою функцию с другими типами, которые, например, больше, чем указатель, записи или массивы, вы должны увидеть большую разницу во времени, потому что с const
передается только указатель, без const
запись будет скопирована перед передачей в функцию.
Используя ключевое слово const, вы можете оставить решение по оптимизации компилятору.
В документации сказано:
Использование const позволяет компилятору оптимизировать код для параметров структурированного и строкового типа.
Таким образом, лучше использовать рациональное использование const для строковых параметров просто потому, что в руководстве так сказано.;)
Теперь это может быть достаточно хорошим ответом для спрашивающего, но еще интереснее посмотреть на общий вопрос, использовать ли параметры const или нет.
Опять же, документация гласит, что всего в одном клике от Delphi Language Guide Index
Значения и постоянные (постоянные) параметры передаются по значению или по ссылке, в зависимости от типа и размера параметра:
Обратите внимание на очевидное равенство значений и постоянных параметров в этом предложении. Из этого следует, что использование const для параметров, не являющихся строковым или структурированным типом, не влияет на производительность и размер кода. (Короткий тест, полученный из тестового кода Торстена Энглера, действительно показывает среднее безразличие между параметрами const и без них для параметров порядкового и реального типов.)
Таким образом, оказывается, что использование const имеет значение только для программиста, а не для исполняемого файла.
Как продолжение, и как LukeH уже спросил: Какие есть веские причины не использовать const?
Чтобы следовать собственному синтаксису Delphi:
function FindDragTarget(const Pos: TPoint; AllowDisabled: Boolean): TControl; function UpperCase(const S: string): string; function UpCase(Ch: Char): Char; function EncodeDate(Year, Month, Day: Word): TDateTime;
Поэтому для создания более компактного кода, возможно, немного более читаемый код. Например: использование константных параметров в установщиках свойств действительно излишне, что на удивление часто приводит к объявлениям в одной строке вместо двойных, если вы хотите соблюдать ограничение длины строки.
Удобно предоставлять переменные виртуальным методам и обработчикам событий. Обратите внимание, что ни один из типов обработчиков событий VCL не использует константные параметры (кроме членов с типом строки или записи). Это просто хороший сервис для пользователей вашего кода или ваших компонентов.
Конечно, также могут быть веские причины для использования const:
Как уже ответил LukeH, если вообще нет необходимости менять значение параметра.
Для (личной) защиты, как указано в документации:
Использование const также обеспечивает защиту от непреднамеренной передачи параметра посредством ссылки на другую процедуру.
Частичное происхождение этого ответа: http://www.nldelphi.com/Forum/showthread.php?p=271371.
Как правило, я бы избегал любых оптимизаций (на любом языке), которые не решают реальных проблем, которые вы можете измерить. Профилируйте свой код и исправьте проблемы, которые вы можете увидеть. Оптимизация по теоретическим вопросам - просто трата времени.
Если вы подозреваете, что что-то не так, и это каким-то образом исправляет это / ускоряет его, то это замечательно, но реализация такого рода микрооптимизаций по умолчанию редко стоит времени.
Один из самых важных фактов, которые люди опускают. Инструкция блокировки... очень затратна в многоядерных процессорах инструкции x86. Прочитайте руководство Intel. Стоимость - это когда refcounter var помещается и не находится в кэше процессора, ВСЕ другие ЦП должны быть остановлены для выполнения инструкции.
ура