Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2017-04

Setting record values

6 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph
I’ve been using a design pattern in my code which I think is probably pretty stupid so I’d like to make sure. Assume I have a type like TPoint below and I want to set the value I’ll doing something like point := PointMake(x, y). How does the compiler handle this? It probably has to allocate some memory on the heap so shouldn’t I always be setting values using the alternative TPoint.SetPoint? It’s maybe not a big deal but it’s something I’d like to clear up if it’s inefficient.
function PointMake (_x, _y: integer): TPoint;
begin
  result.x := _x;
  result.y := _y;
end;
procedure TPoint.SetPoint (_x, _y: integer);
begin
  x := _x;
  y := _y;
end;

same outcome but which is more efficient?

1) point.SetPoint(x, y);
2) point := PointMake(x, y);

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
I’ve been using a design pattern in my code which I think is probably

pretty stupid so I’d like to make sure. Assume I have a type like TPoint below and I want to set the value I’ll doing something like point := PointMake(x, y). How does the compiler handle this? It probably has to allocate some memory on the heap so shouldn’t I always be setting values using the alternative TPoint.SetPoint? It’s maybe not a big deal but it’s something I’d like to clear up if it’s inefficient.

Records are only located on the stack (or in case of global variables the data sections). If you want them on the heap then you'd need to explicitly do that using pointers.

Show quoted text 13 lines
function PointMake (_x, _y: integer): TPoint; begin result.x := _x; result.y := _y; end; procedure TPoint.SetPoint (_x, _y: integer); begin x := _x; y := _y; end; same outcome but which is more efficient?

I haven't looked at it in detail, but it could be that both have similar efficiency. You could also add "inline" to the MakePoint function which should get rid of a potential temporary variable if the compiler doesn't do that already anyway.
Alternatively you could also declare a constructor "TPoint.Make" or so (that despite its name doesn't do any dynamic memory allocation either) which you can declare as inline as well.

In the end you can always check the assembler code.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Apr 1, 2017, at 2:46 PM, Sven Barth via fpc-pascal wrote: I haven't looked at it in detail, but it could be that both have similar efficiency. You could also add "inline" to the MakePoint function which should get rid of a potential temporary variable if the compiler doesn't do that already anyway. Alternatively you could also declare a constructor "TPoint.Make" or so (that despite its name doesn't do any dynamic memory allocation either) which you can declare as inline as well.

How is the constructor any different from the function?

In the end you can always check the assembler code.

Not sure how to do that or what to look for. It appears to me without knowing how the compiler works that there would be some allocating and copying of memory which is more overhead than assigning a value directly. Maybe it’s totally trivial but if it is it’s something I should cut out of my design going forward.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
Ryan Joseph wrote 7 lines
> On Apr 1, 2017, at 2:46 PM, Sven Barth via fpc-pascal wrote: > > I haven't looked at it in detail, but it could be that both have similar efficiency. You could also add "inline" to the MakePoint function which should get rid of a potential temporary variable if the compiler doesn't do that already anyway. > Alternatively you could also declare a constructor "TPoint.Make" or so (that despite its name doesn't do any dynamic memory allocation either) which you can declare as inline as well. How is the constructor any different from the function?

In case of constructor vs. global function the encapsulation is more clear. In case of constructor vs. class function there isn't really that much difference except that through the name alone ("constructor blabla") it's clear what the purpose of the function should be.

(Though I now also noticed that you can't use "inline" with constructors...)

> > In the end you can always check the assembler code. Not sure how to do that or what to look for. It appears to me without knowing how the compiler works that there would be some allocating and copying of memory which is more overhead than assigning a value directly. Maybe it’s totally trivial but if it is it’s something I should cut out of my design going forward.

The compiler keeps the assembler code with line information around if you pass "-al".

If you are so concerned about the differences in performance regarding using a setter and a construction function you should really learn at least in principle how typical assembler code generated by the compiler looks like (same for your query about dynamic arrays by the way, though there you should also take a look at the implementation of the RTL).

Here is an example compiled without optimizations aside from inline:

=== code begin ===

# [47] p.&Set(42, 21);
movq $U_$P$TRECFUNCS_$$_P,%rax
movl $21,%edx
movl $42,%esi
movq %rax,%rdi
call P$TRECFUNCS$_$TPOINT_$__$$_SET$LONGINT$LONGINT # [48] p := TPoint.Make(42, 21);
leaq -8(%rbp),%rdi
movl $21,%edx
movl $42,%esi
call P$TRECFUNCS$_$TPOINT_$__$$_MAKE$LONGINT$LONGINT$$TPOINT movq -8(%rbp),%rax
movq %rax,U_$P$TRECFUNCS_$$_P
# [49] p := TPoint.Make2(42, 21);
movl $42,U_$P$TRECFUNCS_$$_P
movl $21,U_$P$TRECFUNCS_$$_P+4
# [50] p := MakePoint(42, 21);
movl $21,%esi
movl $42,%edi
call P$TRECFUNCS_$$_MAKEPOINT$LONGINT$LONGINT$$TPOINT movq %rax,U_$P$TRECFUNCS_$$_P
# [51] p := MakePoint2(42, 21);
movl $42,U_$P$TRECFUNCS_$$_P
movl $21,U_$P$TRECFUNCS_$$_P+4

=== code end ===

"&Set" is essentially your "SetPoint" method. "Make" is a constructor. "Make2" is a static class function with "inline". "MakePoint" is your creation function and "MakePoint2" is the same with an inline modifier.

As you can see the two inline variants ("Make2" and "MakePoint2") are the most effective as there's no call and only the two loads of the values.

Regards,
Sven

Ryan Joseph
to Sven Barth

Thanks for the tips, I appreciate it.

This is all pretty trivial but it’s kind of annoying that using an inline class function is more efficient than a constructor despite having identical functionality. It's tempting to remove the constructors now and replace them with inline functions but it seems like the compiler should be smarter and make this optimization for me.

Show quoted text 48 lines
On Apr 2, 2017, at 10:51 PM, Sven Barth via fpc-pascal wrote: The compiler keeps the assembler code with line information around if you pass "-al". If you are so concerned about the differences in performance regarding using a setter and a construction function you should really learn at least in principle how typical assembler code generated by the compiler looks like (same for your query about dynamic arrays by the way, though there you should also take a look at the implementation of the RTL). Here is an example compiled without optimizations aside from inline: === code begin === # [47] p.&Set(42, 21); movq $U_$P$TRECFUNCS_$$_P,%rax movl $21,%edx movl $42,%esi movq %rax,%rdi call P$TRECFUNCS$_$TPOINT_$__$$_SET$LONGINT$LONGINT # [48] p := TPoint.Make(42, 21); leaq -8(%rbp),%rdi movl $21,%edx movl $42,%esi call P$TRECFUNCS$_$TPOINT_$__$$_MAKE$LONGINT$LONGINT$$TPOINT movq -8(%rbp),%rax movq %rax,U_$P$TRECFUNCS_$$_P # [49] p := TPoint.Make2(42, 21); movl $42,U_$P$TRECFUNCS_$$_P movl $21,U_$P$TRECFUNCS_$$_P+4 # [50] p := MakePoint(42, 21); movl $21,%esi movl $42,%edi call P$TRECFUNCS_$$_MAKEPOINT$LONGINT$LONGINT$$TPOINT movq %rax,U_$P$TRECFUNCS_$$_P # [51] p := MakePoint2(42, 21); movl $42,U_$P$TRECFUNCS_$$_P movl $21,U_$P$TRECFUNCS_$$_P+4 === code end === "&Set" is essentially your "SetPoint" method. "Make" is a constructor. "Make2" is a static class function with "inline". "MakePoint" is your creation function and "MakePoint2" is the same with an inline modifier. As you can see the two inline variants ("Make2" and "MakePoint2") are the most effective as there's no call and only the two loads of the values.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
Thanks for the tips, I appreciate it. This is all pretty trivial but it’s kind of annoying that using an inline

class function is more efficient than a constructor despite having identical functionality. It's tempting to remove the constructors now and replace them with inline functions but it seems like the compiler should be smarter and make this optimization for me.

At least for classes and objects the constructor is a bit more complicated (especially the former ones), so that may be the reason that nothing is done there for record constructors either. I'll check whether I can at least get them to paricipate in auto inlining so that they would at least be automatically inlined at higher optimization levels.

Regards,
Sven