Setting record values

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Setting record values

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

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Setting record values

Free Pascal - General mailing list

Am 01.04.2017 05:59 schrieb "Ryan Joseph" <[hidden email]>:
>
> 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.

> 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


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Setting record values

Ryan Joseph

> On Apr 1, 2017, at 2:46 PM, Sven Barth via fpc-pascal <[hidden email]> 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

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Setting record values

Free Pascal - General mailing list
On 01.04.2017 09:59, Ryan Joseph wrote:
>
>> On Apr 1, 2017, at 2:46 PM, Sven Barth via fpc-pascal <[hidden email]> 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
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Setting record values

Ryan Joseph
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.

> On Apr 2, 2017, at 10:51 PM, Sven Barth via fpc-pascal <[hidden email]> 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

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Setting record values

Free Pascal - General mailing list

Am 04.04.2017 05:25 schrieb "Ryan Joseph" <[hidden email]>:
>
> 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


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal