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

时间:2011-04-30 22:03:34

标签: delphi parameters 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;

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

为什么我要打扰const

7 个答案:

答案 0 :(得分:32)

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

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

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替换对Length的调用(我选择了最坏的情况,搜索字符串中未包含的内容):

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.

表示非线程案例。

答案 1 :(得分:25)

不要忘记const不仅可以提供那些微小的性能提升。

使用const向任何人解释正在阅读维护不应更新该值的代码,并允许编译器捕获任何意外尝试这样做。

因此,使您的代码更多可读和可维护也可以使其更快。使用const有什么好的理由?

答案 2 :(得分:10)

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

答案 3 :(得分:5)

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

如果使用大于指针,记录或数组的其他类型测试函数,则应该看到更大的时间差,因为const仅传递指针,而没有{{1}记录将在传递给函数之前被复制。

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

答案 4 :(得分:2)

documentation说:

  

使用const允许编译器优化结构化和字符串类型参数的代码。

因此,使用 const 作为字符串参数更好,更合理,因为手册说明了这一点。 ;)


现在,对于提问者来说,这可能已经足够了,但是查看是否使用const参数的一般问题更为有趣。

再次,documentation只需点击一下即可离开 Delphi语言指南

  

值和常量( const )参数按值或按引用传递,具体取决于参数的类型和大小:

注意这句话中值和常量参数的明显相等。结论是,对于参数使用 const ,不是字符串或结构化类型,在性能和代码大小上没有区别。 (一个简短的测试,源自Thorsten Engler的测试代码,确实显示了有序和不带常数和有效类型参数之间的平均无差异。)

事实证明,使用 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 参数(对于字符串或记录类型的成员除外)。对于您的代码或组件的用户来说,这是一项很好的服务。

  4. 当然,使用 const

    也可能有很好的理由
    1. 正如LukeH已经回答的那样,如果根本不需要改变参数的值。

    2. 对于(个人)保护,例如documentation说:

        

      使用 const 还可以防止通过引用另一个例程无意中传递参数。

    3. 此答案的部分来源:http://www.nldelphi.com

答案 5 :(得分:0)

通常,我会避免任何不能解决您可以测量的实际问题的优化(使用任何语言)。描述您的代码,并修复您实际可以看到的问题。优化理论问题只是浪费你的时间。

如果你怀疑某些东西是错的,并且以某种方式修复它/加速它,那么很好,但默认情况下实现这些微优化很少值得花时间。

答案 6 :(得分:0)

人们遗漏的最重要的事实之一。在x86指令的多核CPU中,互锁...指令非常昂贵。阅读英特尔手册。成本是当放置refcounter var并且它不在cpu缓存中时,必须停止所有其他CPU以执行指令。

干杯