Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the use of `const` dogmatic or rational?

Tags:

In Delphi you can speed up your code by passing parameters as const, e.g.

function A(const AStr: string): integer;

//or

function B(AStr: string): integer;

Suppose both functions have the same code inside, the speed difference between them is negligible and I doubt it can even be measured with a cycle-counter like:

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;

The reason for this is that all the const does in function A is to prevent the reference count of AStr to be incremented.
But the increment only takes one cycle of one core of my multicore CPU, so...

Why should I bother with const?

like image 914
Johan Avatar asked Apr 30 '11 22:04

Johan


3 Answers

If there is no other reason for the function to contain an implicit try/finally, and the function itself is not doing much work, the use of const can result in a significant speedup (I once got one function that was using >10% of total runtime in a profiling run down to <2% just by adding a const in the right place).

Also, the reference counting takes much much more than one cycle because it has to be performed with the lock prefix for threadsafety reasons, so we are talking more like 50-100 cycles. More if something in the same cache line has been modified by another core in between.

As for not being able to measure it:

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.

Produces this output on my system:

Const: 6084039
NoConst: 36192232
Const is 5.95 times faster.

EDIT: it gets a bit more interesting if we add some thread contention:

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.

On a 6 core machine, this gives me:

Const: 19968128
NoConst: 1313528420
Const is 65.78 times faster.

EDIT2: replacing the call to Length with a call to Pos (I picked the worst case, search for something not contained in the string):

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

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

results in:

Const: 51792332
NoConst: 1377644831
Const is 26.60 times faster.

for the threaded case, and:

Const: 15912102
NoConst: 44616286
Const is 2.80 times faster.

for the non-threaded case.

like image 88
Thorsten Engler Avatar answered Nov 12 '22 17:11

Thorsten Engler


Using const prevents an implicit try/finally block which on x86 is rather more expensive than reference counting. That's really a separate issue to the semantic meaning of const. It's a shame that performance and semantics are mixed up in this way.

like image 33
David Heffernan Avatar answered Nov 12 '22 17:11

David Heffernan


Don't forget that const isn't only there to provide those tiny performance improvements.

Using const explains to anybody reading or maintaining the code that the value shouldn't be updated, and allows the compiler to catch any accidental attempts to do so.

So making your code more readable and maintainable can also make it marginally faster. What good reasons are there for not using const?

like image 27
LukeH Avatar answered Nov 12 '22 18:11

LukeH