Использование const догматично или рационально? - PullRequest
22 голосов
/ 01 мая 2011

В 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?

Ответы [ 7 ]

32 голосов
/ 01 мая 2011

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

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

РЕДАКТИРОВАТЬ 2: замена вызова 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.

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

25 голосов
/ 01 мая 2011

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

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

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

10 голосов
/ 01 мая 2011

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

5 голосов
/ 03 мая 2011

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

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

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

2 голосов
/ 26 июня 2011

Документация гласит:

Использование 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 (для элементов, отличных от строк или записей). Это просто хороший сервис для пользователей вашего кода или ваших компонентов.

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

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

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

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

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

0 голосов
/ 01 мая 2011

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

Приветствия

0 голосов
/ 01 мая 2011

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

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

...