使用`const`教条还是理性?

Joh*_*ica 23 delphi parameters const

在Delphi中,您可以通过传递参数来加速代码const,例如

function A(const AStr: string): integer;

//or

function B(AStr: string): integer;
Run Code Online (Sandbox Code Playgroud)

假设两个函数内部具有相同的代码,它们之间的速度差异可以忽略不计,我怀疑它甚至可以用循环计数器测量,如:

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;
Run Code Online (Sandbox Code Playgroud)

其原因是const函数A 中的所有操作都是为了防止引用计数AStr增加.
但是增量只需要我的多核CPU的一个核心的一个周期,所以......

我为什么要打扰const

Tho*_*ler 33

如果函数没有其他原因包含隐式try/finally,并且函数本身没有做太多工作,使用const可以导致显着的加速(我曾经有一个函数使用> 10%通过在正确的位置添加const,分析中的总运行时间低至<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.
Run Code Online (Sandbox Code Playgroud)

在我的系统上生成此输出:

Const: 6084039
NoConst: 36192232
Const is 5.95 times faster.
Run Code Online (Sandbox Code Playgroud)

编辑:如果我们添加一些线程争用,它会变得更有趣:

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.
Run Code Online (Sandbox Code Playgroud)

在6核机器上,这给了我:

Const: 19968128
NoConst: 1313528420
Const is 65.78 times faster.
Run Code Online (Sandbox Code Playgroud)

EDIT2:通过调用Pos替换对Length的调用(我选择了最坏的情况,搜索字符串中未包含的内容):

function ConstLength(const s: string): Integer;
begin
  Result := Pos('x', s);
end;

function NoConstLength(s: string): Integer;
begin
  Result := Pos('x', s);
end;
Run Code Online (Sandbox Code Playgroud)

结果是:

Const: 51792332
NoConst: 1377644831
Const is 26.60 times faster.
Run Code Online (Sandbox Code Playgroud)

对于螺纹外壳,和:

Const: 15912102
NoConst: 44616286
Const is 2.80 times faster.
Run Code Online (Sandbox Code Playgroud)

对于非线程情况.

  • 我强烈反对基准是"非常人为的".因为功能有所作为.它返回字符串的长度.这几乎不算什么呢?但我已经为一个更复杂的案例添加了基准测试结果,但仍显示出显着的速度差异.关于try/finally开销的话题.try/finally的开销在线程和非线程情况之间是相同的,因此性能上的巨大差异可以看出,内核之间的缓存行是乒乓的结果,并且不会消失x64. (7认同)
  • 谢天谢地,尝试终于惩罚将在x86-64中大幅消失. (2认同)

Luk*_*keH 26

不要忘记,const不仅仅是提供那些微小的性能改进.

使用const解释给任何读取维护代码的人不应该更新值,并允许编译器捕获任何意外尝试.

因此,使代码更具可读性和可维护性也可以使其更快.有什么好的理由使用const

  • **不使用const的一个很好的理由是,如果你想在传递字符串之前操作字符串或者用它做某事,你需要创建临时变量.这些额外的变量会使您的程序更长,更难阅读并且打字时间更长.除了每个人都知道默认情况下参数是按值传递的,只将它们声明为`var`使它们可以返回.事实上,"const"存在的唯一原因是因为人们发现`var`传递速度更快并且开始滥用`var`传递以获得(通常)高风险的微小加速. (2认同)

Dav*_*nan 10

使用const可以防止x86上的隐式try/finally块比引用计数更昂贵.对于const的语义来说,这确实是一个单独的问题.令人遗憾的是,性能和语义以这种方式混杂在一起.

  • 然后你可能会认为遗憾的是,地球是围绕太阳旋转的球而不是4只大象背上的圆盘,它们站在一只漂浮在太空中的乌龟身上.:) (6认同)

mar*_*kli 5

String类型是一种特殊情况,因为它由Delphi管理(按需复制),因此不适合回答您的问题.

如果使用比指针,记录或数组大的其他类型测试函数,则应该看到更大的时间差,因为const只传递一个指针,而const不会在传递给函数之前复制记录.

使用关键字const,您可以将优化决策留给编译器.