www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - pass a struct by value/ref and size of the struct

reply ref2401 <refactor24 gmail.com> writes:
I have got a plenty of structs in my project. Their size varies 
from 12 bytes to 128 bytes.
Is there a rule of thumb that states which structs I pass by 
value and which I should pass by reference due to their size?

Thanks.
Mar 21 2016
next sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Monday, 21 March 2016 at 23:31:06 UTC, ref2401 wrote:
 I have got a plenty of structs in my project. Their size varies 
 from 12 bytes to 128 bytes.
 Is there a rule of thumb that states which structs I pass by 
 value and which I should pass by reference due to their size?

 Thanks.
Not really. It depends on the platform - most especially on the details of the cache hierarchy, I think. The best answer to this kind of thing is generally: Do some benchmarks. (Although, you'll have to be somewhat skilled at benchmarking to get useful results, since the performance difference in most code is probably not that large either way.) Note, also, that functions which are doing so little work with their arguments that you can actually notice the speed difference of pass-by-ref versus pass-by-value for small data structures are likely to be inlined anyway - in which case it makes no difference which you chose (I think...). If you really just want a number, though - maybe try pass-by-reference for anything larger than 32 bytes.
Mar 21 2016
prev sibling next sibling parent reply ZombineDev <petar.p.kirov gmail.com> writes:
On Monday, 21 March 2016 at 23:31:06 UTC, ref2401 wrote:
 I have got a plenty of structs in my project. Their size varies 
 from 12 bytes to 128 bytes.
 Is there a rule of thumb that states which structs I pass by 
 value and which I should pass by reference due to their size?

 Thanks.
If the object is larger than the size of a register on the target machine, it is implicitly passed by ref (i.e. struct fields are accessed by offset from the stack pointer). So the question is: does the compiler need to create temporaries and is this an expensive operation? In C++ the problem is that there are lots of non-POD types which have expensive copy constructors (like std::vector) and that's why taking objects by const& is good guideline. In D structs are implicitly movable (can be memcpy-ed around without their postblit this(this) function called) and that's why I think that passing by value shouldn't be as large problem as in C++, especially if you are using a good optimizing compiler such as LDC or GDC. Anyway, modern hardware in combination with compiler optimizations can often suprise you, so I recommend profiling your code and doing microbenchmarks to figure out where you may have performance problems. In my experience, large amounts of small memory allocations is orders of magnitude larger problem than the copying of large value types. The next thing to look for is inefficient memory layout with lots of indirections.
Mar 22 2016
next sibling parent Johan Engelen <j j.nl> writes:
On Tuesday, 22 March 2016 at 07:35:49 UTC, ZombineDev wrote:
 
 If the object is larger than the size of a register on the 
 target machine, it is implicitly passed by ref (i.e. struct 
 fields are accessed by offset from the stack pointer).
(Oops, sorry ZombineDev, should've read your reply first)
Mar 22 2016
prev sibling parent reply kinke <noone nowhere.com> writes:
On Tuesday, 22 March 2016 at 07:35:49 UTC, ZombineDev wrote:
 If the object is larger than the size of a register on the 
 target machine, it is implicitly passed by ref
That's incorrect. As Johan pointed out, this is somewhat true for the Win64 ABI (but it firstly copies the argument before passing a pointer to it!), but it's not for the 32-bit x86 and x86_64 System V (used on all non-Windows platforms) ABIs. System V is especially elaborate and may pass structs up to twice the size of a register in 2 registers. Bigger structs passed by value are blitted into the function arguments stack in memory. They are then accessed by the callee via a stack offset, that's correct, but I wouldn't call that implicit-by-ref-passing, as copying does take place, unless the optimizer decides it's unnecessary. So passing structs > 64-bit by value on Win64 never pays off (there's always an indirection); using `const ref(T)` where possible makes sure you at least elide the copy. But then again, you'll very soon find out that it's not really an option as rvalues cannot be passed byref in D, something a lot of people [including myself if not already obvious :)] hate about D.
Mar 23 2016
parent reply ZombineDev <petar.p.kirov gmail.com> writes:
On Wednesday, 23 March 2016 at 19:39:49 UTC, kinke wrote:
 On Tuesday, 22 March 2016 at 07:35:49 UTC, ZombineDev wrote:
 If the object is larger than the size of a register on the 
 target machine, it is implicitly passed by ref
That's incorrect. As Johan pointed out, this is somewhat true for the Win64 ABI (but it firstly copies the argument before passing a pointer to it!), but it's not for the 32-bit x86 and x86_64 System V (used on all non-Windows platforms) ABIs. System V is especially elaborate and may pass structs up to twice the size of a register in 2 registers. Bigger structs passed by value are blitted into the function arguments stack in memory. They are then accessed by the callee via a stack offset, that's correct, but I wouldn't call that implicit-by-ref-passing, as copying does take place, unless the optimizer decides it's unnecessary. So passing structs > 64-bit by value on Win64 never pays off (there's always an indirection); using `const ref(T)` where possible makes sure you at least elide the copy. But then again, you'll very soon find out that it's not really an option as rvalues cannot be passed byref in D, something a lot of people [including myself if not already obvious :)] hate about D.
Thank you and Johan for the detailed explanation. You're efforts on improving LDC are much appreciated. My intentions were to say that taking structs by value shouldn't be as expensive as in C++, because of the way D handles copy construction, especially if there is no user-defined postblit (which is quite common), and also because separate compilation is used more rarely. But probably I shouldn't have said that about the size of registers as it depends very much on the ABI and it's not true in general (as you pointed out). BTW, how does LDC handle the `auto ref` and `in` parameter attributes for templated functions (that obviously have their source code available)? Can they be used to prevent indirection when the structure can be passed in registers and to prevent copying when passing by registers is not possible?
Apr 02 2016
parent reply ZombineDev <petar.p.kirov gmail.com> writes:
On Saturday, 2 April 2016 at 09:28:58 UTC, ZombineDev wrote:
 On Wednesday, 23 March 2016 at 19:39:49 UTC, kinke wrote:
 On Tuesday, 22 March 2016 at 07:35:49 UTC, ZombineDev wrote:
 If the object is larger than the size of a register on the 
 target machine, it is implicitly passed by ref
That's incorrect. As Johan pointed out, this is somewhat true for the Win64 ABI (but it firstly copies the argument before passing a pointer to it!), but it's not for the 32-bit x86 and x86_64 System V (used on all non-Windows platforms) ABIs. System V is especially elaborate and may pass structs up to twice the size of a register in 2 registers. Bigger structs passed by value are blitted into the function arguments stack in memory. They are then accessed by the callee via a stack offset, that's correct, but I wouldn't call that implicit-by-ref-passing, as copying does take place, unless the optimizer decides it's unnecessary. So passing structs > 64-bit by value on Win64 never pays off (there's always an indirection); using `const ref(T)` where possible makes sure you at least elide the copy. But then again, you'll very soon find out that it's not really an option as rvalues cannot be passed byref in D, something a lot of people [including myself if not already obvious :)] hate about D.
Thank you and Johan for the detailed explanation. You're efforts on improving LDC are much appreciated. My intentions were to say that taking structs by value shouldn't be as expensive as in C++, because of the way D handles copy construction, especially if there is no user-defined postblit (which is quite common), and also because separate compilation is used more rarely. But probably I shouldn't have said that about the size of registers as it depends very much on the ABI and it's not true in general (as you pointed out). BTW, how does LDC handle the `auto ref` and `in` parameter attributes for templated functions (that obviously have their source code available)? Can they be used to prevent indirection when the structure can be passed in registers and to prevent copying when passing by registers is not possible?
I find that (at least from a usability standpoint) auto ref works quite well: // UniqueRef has disabled this(this) UniqueRef!Resource r; void use()(auto ref UniqueRef!Resource r); // borrows use(r); // passes ownership of rvalues use(UniqueRef!Resource()) // transfers ownership use(move(r)); // r == UniqueRef.init here //============= auto ref add(V)(auto ref V v1, auto ref V v2); // default this(this) Vec3f vec1; // accepts both lvalues and rvalues auto res = add(vec1, Vec3f(1, 2, 3.14));
Apr 02 2016
parent Namespace <rswhite4 gmail.com> writes:
 auto ref add(V)(auto ref V v1, auto ref V v2);

 // default this(this)
 Vec3f vec1;

 // accepts both lvalues and rvalues
 auto res = add(vec1, Vec3f(1, 2, 3.14));
auto ref produces template bloat. That is no real solution for the rvalue ref problem. But there is (more or less) a workaround: ---- import std.stdio; import std.algorithm : sum; struct Matrix4x4 { private Matrix4x4* _ref; float[16] values = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; ref Matrix4x4 byRef() { _ref = &this; return *_ref; } double sum() const { return this.values[].sum; } } void foo(ref const Matrix4x4 mat) { writeln("foo: ", mat.sum); } void main() { Matrix4x4 m; foo(m); foo(Matrix4x4().byRef); } ----
Apr 02 2016
prev sibling parent Johan Engelen <j j.nl> writes:
On Monday, 21 March 2016 at 23:31:06 UTC, ref2401 wrote:
 I have got a plenty of structs in my project. Their size varies 
 from 12 bytes to 128 bytes.
 Is there a rule of thumb that states which structs I pass by 
 value and which I should pass by reference due to their size?
Note that the compiler may do things different from what you may have expected. For example for C code, the platform ABI may already dictate passing of your structs by pointer reference, even though your code says "by value". See: https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx MSVC will pass structs that are larger than 64 bits (8 bytes) by reference in C++ code. Your D compiler may decide to do the same.
Mar 22 2016