Mastering Delphi Programming:A Complete Reference Guide
上QQ阅读APP看书,第一时间看更新

Method inlining

Previously in this chapter, I spent quite some time describing the mechanics of enabling and disabling method inlining, but I never said what that actually means. To put it simply, method inlining allows one method to be compiled as if its code would be a part of another.

When you call a method that is not marked as inlineable (doesn't have the inline suffix), the compiler prepares method parameters in an appropriate way (in registers and on the stack, but I'm not going there) and then executes a CALL assembler instruction.

If the called method is marked as inlineable, then the compiler basically just inserts the body of the inlineable method inside the code of the caller. This speeds up the code but also makes it larger as this happens in every place that calls the inlineable method.

Let me give you an example. The following code from the Inlining demo increments a value 10 million times:

function IncrementInline(value: integer): integer; inline;
begin
Result := value + 1;
end;

procedure TfrmInlining.Button2Click(Sender: TObject);
var
value: Integer;
i: Integer;
begin
value := 0;
for i := 1 to 10000000 do
value := IncrementInline(value);
end;

As the IncrementInline is marked as inline (and is therefore inlineable if the compiler settings are not preventing it), the code generated by the compiler doesn't actually CALL into that method 10 million times. The code actually looks more like a code generated by the next example:

procedure TfrmInlining.Button2Click(Sender: TObject);
var
value: Integer;
i: Integer;
begin
value := 0;
for i := 1 to 10000000 do
value := value + 1;
end;

If you run the demo, you'll see that the inlined version executes much faster than the non-inlined code. On my test computer, the non-inlined version needed 53 ms while the inline version executed in 26 ms.

When you call the inline method from the same unit, make sure that the inline method is implemented before it is called. Otherwise, the compiler will just silently use the method as if it was a normal method and won't generate even a hint. In the next example, the method is actually not inlined, although we may expect it to be:

type
TfrmInlining = class(TForm)
// some unimportant stuff ...
procedure Button3Click(Sender: TObject);
private
function IncrementShouldBeInline(value: integer): integer; inline;
public
end;

procedure TfrmInlining.Button3Click(Sender: TObject);
var
value: Integer;
i: Integer;
begin
value := 0;
for i := 1 to 10000000 do
value := IncrementShouldBeInline(value);
end;

function TfrmInlining.IncrementShouldBeInline(value: integer): integer;
begin
Result := value + 1;
end;

Another problem that inline code can cause, is that in some situations the result may even be slower than the non-inlined version. Sometimes the compiler just doesn't do a good enough job. Remember, always measure!

For a long time, the compiler had another problem. When the inlined function returned an interface, it made another, hidden copy of the returned value which was only released at the end of the method that called the inlined function. That could cause the interface to not be destroyed as the programmer expected.

This problem, for example, caused threads in Delphi's TThreadPool object not to be released at the correct time. It was only fixed in the 10.2 Tokyo release where Embarcadero introduced an improved compiler which generates better code.

And now I'm really finished with theory. Let's do some code!