在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
?
答案 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)
使用const允许编译器优化结构化和字符串类型参数的代码。
因此,使用 const 作为字符串参数更好,更合理,因为手册说明了这一点。 ;)
现在,对于提问者来说,这可能已经足够了,但是查看是否使用const参数的一般问题更为有趣。
再次,documentation只需点击一下即可离开 Delphi语言指南:
值和常量( const )参数按值或按引用传递,具体取决于参数的类型和大小:
注意这句话中值和常量参数的明显相等。结论是,对于参数使用 const ,不是字符串或结构化类型,在性能和代码大小上没有区别。 (一个简短的测试,源自Thorsten Engler的测试代码,确实显示了有序和不带常数和有效类型参数之间的平均无差异。)
事实证明,使用 const 只会对程序员产生影响,而不是可执行文件。
作为后续行动,正如LukeH已经问到的那样:不使用 const 有什么好的理由?
遵循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;
因此,生成更紧凑的代码可能会稍微更易读。例如:在属性设置器中使用常量参数确实是多余的,如果您想遵守行长度限制,这通常会导致单行声明而不是双行声明。
为虚拟方法和事件处理程序提供舒适的变量。请注意,没有一个VCL事件处理程序类型使用 const 参数(对于字符串或记录类型的成员除外)。对于您的代码或组件的用户来说,这是一项很好的服务。
当然,使用 const :
也可能有很好的理由正如LukeH已经回答的那样,如果根本不需要改变参数的值。
对于(个人)保护,例如documentation说:
使用 const 还可以防止通过引用另一个例程无意中传递参数。
此答案的部分来源:http://www.nldelphi.com。
答案 5 :(得分:0)
通常,我会避免任何不能解决您可以测量的实际问题的优化(使用任何语言)。描述您的代码,并修复您实际可以看到的问题。优化理论问题只是浪费你的时间。
如果你怀疑某些东西是错的,并且以某种方式修复它/加速它,那么很好,但默认情况下实现这些微优化很少值得花时间。
答案 6 :(得分:0)
人们遗漏的最重要的事实之一。在x86指令的多核CPU中,互锁...指令非常昂贵。阅读英特尔手册。成本是当放置refcounter var并且它不在cpu缓存中时,必须停止所有其他CPU以执行指令。
干杯