Использование 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?

  1. Чтобы следовать собственному синтаксису 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;
    
  2. Поэтому для создания более компактного кода, возможно, немного более читаемый код. Например: использование константных параметров в установщиках свойств действительно излишне, что на удивление часто приводит к объявлениям в одной строке вместо двойных, если вы хотите соблюдать ограничение длины строки.

  3. Удобно предоставлять переменные виртуальным методам и обработчикам событий. Обратите внимание, что ни один из типов обработчиков событий VCL не использует константные параметры (кроме членов с типом строки или записи). Это просто хороший сервис для пользователей вашего кода или ваших компонентов.

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

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

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

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

Частичное происхождение этого ответа: http://www.nldelphi.com/Forum/showthread.php?p=271371.

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

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

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

ура

Другие вопросы по тегам