Подтвердить что ты не робот

Является ли использование `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 выполняет в функции A, чтобы предотвратить увеличение счетчика ссылок AStr.
Но приращение занимает всего один цикл одного ядра моего многоядерного процессора, поэтому...

Зачем мне беспокоиться const?

4b9b3361

Ответ 1

Если нет другой причины, по которой функция содержит неявный try/finally, а сама функция не выполняет много работы, использование const может привести к значительному ускорению (у меня когда-то была одна функция, которая использовалa > 10% от общей продолжительности выполнения при профилировании до 2%, просто добавив константу в нужное место).

Кроме того, подсчет ссылок занимает гораздо больше одного цикла, потому что он должен выполняться с префиксом блокировки по причинам, связанным с потоками, поэтому мы говорим больше, чем 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.

EDIT: он становится немного интереснее, если мы добавим некоторую дискуссию по теме:

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: заменив вызов на длину вызовом 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.

для случая без резьбы.

Ответ 2

Не забывайте, что const не только обеспечивает минимальные улучшения производительности.

Использование const объясняет любому, кто читает или поддерживает код, что значение не должно обновляться, и позволяет компилятору поймать любые случайные попытки сделать это.

Таким образом, сделать ваш код более удобочитаемым и поддерживаемым может также сделать его немного быстрее. Каковы веские причины для использования const?

Ответ 3

Использование const предотвращает неявный блок try/finally, который на x86 намного дороже, чем подсчет ссылок. Это действительно отдельная проблема для семантического значения const. Стыдно, что производительность и семантика смешиваются таким образом.

Ответ 4

Тип String - это особый случай, поскольку он управляется Delphi (копия по требованию) и поэтому не идеальна для ответа на ваш вопрос.

Если вы тестируете свою функцию с другими типами, которые больше, чем указатель, например, записи или массивы, вы должны увидеть большую разницу во времени, потому что при const передается только указатель, без const запись будет перед тем как перейти к функции.

Используя ключевое слово const, вы можете оставить решение оптимизации компилятору.

Ответ 5

В документации говорится:

Использование const позволяет компилятору оптимизировать код для структурированных и строковых параметров.

Итак, лучше, таким образом, рационально использовать const для строковых параметров, просто потому, что руководство так говорит.;)


Теперь это может быть достаточно хорошим ответом для вопросника, но еще интереснее рассмотреть общий вопрос: использовать константные параметры или нет.

Опять же, документация говорит всего за один клик от индекса указателя языка Delphi:

Значение и константа (const) передаются по значению или по ссылке в зависимости от типа и размера параметра:

Обратите внимание на кажущееся равенство значения и постоянных параметров в этом предложении. Это делает вывод о том, что использование const для параметров, не строковое или структурированное, не влияет на производительность и размер кода. (Короткий тест, полученный из тестового кода Thorsten Engler, действительно показывает среднее безразличие между с и без 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 (кроме строк или записей). Это просто приятное обслуживание для пользователей вашего кода или ваших компонентов.

Конечно, могут быть и прекрасные причины использования const:

  • Как уже ответил LukeH, если вообще не нужно менять значение параметра.

  • Для (личной) защиты, например, документация говорит:

    Использование const также обеспечивает защиту от непреднамеренной передачи параметра ссылкой на другую процедуру.

Частичное происхождение этого ответа: http://www.nldelphi.com.

Ответ 6

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

Если вы подозреваете, что что-то не так, и это каким-то образом исправляет/ускоряет его, то здорово, но по умолчанию реализует такие микро оптимизации, редко стоит того времени.

Ответ 7

Один из самых важных фактов, что люди опущены. Команда Interlock... очень дорого стоит в многоядерных процессорах инструкции x86. Прочтите руководство Intel. Стоимость заключается в том, когда refcounter var помещается в место, и он не находится в кэше процессора, все остальные процессоры должны быть остановлены для выполнения инструкции.

Приветствия