Does structs generate inaccessible copies?
Consider the following large structure for the asked questions
Does a local copy is created when returning from a method?
In my understanding of how structure returning methods are handled, the current method will allocate the necessary memory to store the called method's return within the stack. But, the called method also has it's own stack, so, in the next scenarios, does the struct gets copied back in forth or is it internally passed by reference?
When
Main
invokes CreateMe
is the structure allocated inside the creator method or does the compiler detects that no one can access it so it makes new()
allocate directly into x
's var memory?
What if the variable is accessed in the creator method but only returned
Since the value temp
is only modified and then returned, is the memory for it allocated in CreateMe
's stack and then copied to the caller method stack or accessed directly?
Does a a copy of a struct is created when passing through an init accessor?
When creating a Data
instance, the IL method init_Payload
is called. So the stack traces looks something like the following .With that in mind, does the large structure gets copied 2 times or the compiler optmises that in some way?
Main() -> CreateMe() -> init_Payload
new Data() | => new(); | arg: value6 Replies
Found this article that reinforces the usage of fields instead of properties in these specific cases discussed in it as being one of possible solutions to reduce copy overhead https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/
Sergey Tepliakov
Developer Support
The โinโ-modifier and the readonly structs in C# - Developer Suppor...
C# 7.2 got two very important features for high-performance scenarios โ the readonly structs and the in parameters. But to understand why this additions are so important and how theyโre related to each other we should look back in history. As you probably know, the .NET ecosystem has two family of types โ the value [โฆ]
try #allow-unsafe-blocks
@sibber ๐ต๐ธ ยฏ\_(ใ)_/ยฏ
Does a local copy is created when returning from a method?Looks like the caller allocates it. I'm not sure what the guarantees are there, if any. In practice a small method like that will be inlined, so it's moot. https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgAYBYAKDIEYbjyACYugOgCUBXAOwwCW+GOwDCEfAAcBAGxhQAyvIBuAsDFwBuGjuoBtAJK8ZA3jACCUKNgCeACjqkAHAEoAuowDMzXBijcwDGYEOnJSFAAhAQwI7gAzOPlmGgBvGmZmYBsMGGYYcm1qAF9dYm8yZlFU9IzmPQBZGAwACwgAEwMpGTtGlvbOyRkAeUlBCF5cdgA5CCMTXlMAc3cajLYkYNDwqJj4xKhK2GwcxrsXZgBeAD5mMwB3M8LV1joN4hRmeuxTM+e06lqtWU2AOCEuhxgxxgpxchUBJWKQA==
What if the variable is accessed in the creator method but only returnedSame: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgAYBYAKDIEYbjyACYugOgCUBXAOwwCW+GOwDCEfAAcBAGxhQAyvIBuAsDFwBuGjuoBtAJK8ZA3jACCUKNgCeACjqkAHAEoAuowDMzXBijcwDGYEOnJSFAAhAQwI7gAzOPlmGgBvGmZmYBsMGGYYcm1qAF9dYm8yZlFU9IzmPQBZGAwACwgAEwMpGTtGlvbOyRkAeUlBCF5cdgA5CCMTXlMAc3cajLYkYNDwqJj4xKhK2GwcxrsXVeY06lra5WwDnKlmAF5mMwB3TbDI6NiEpLOhRuGUekj0AFY3C9mOQQnQgcDiAB2ZighEZErUXQ3dasFDMerYUxnC5XYHMO4HBDQ0RHE4wQEXTFFIA==
What if the variable is accessed in the creator method but only returnedIn the
init
method, it's passed a pointer to the struct (a struct that large can't be passed in registers sensibly), but it then looks like it makes a defensive copy. It looks like Main
is doing a bunch of additional copying too, although I haven't spent enough time staring at the assembly to figure out what.
https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgAYBYAKDIEYbjyACYugOgCUBXAOwwCW+GOwDCEfAAcBAGxhQAyvIBuAsDFwBuGjuoBtAJK8ZA3jACCUKNgCeACjqkAHAEoAuowDMzXBijcwDGYEOnJSFAAhAQwI7gAzOPlmGgBvGmZmYBsMGGYYcm1qAF9dYm8yZlFU9IzmPQBZGAwACwgAEwMpGTtGlvbOyRkAeUlBCF5cdgA5CCMTXlMAc3cajLYkYNDwqJj4xKhK2GwcxrsXZgBeAD5mMwB3M8La5Opn9dYUZnrsUzOX57Sr2ezAAIsdsMw2uDLrcYA8XKtaoDgc8AAq2GQQbBtGGiI4nGBnREZIpPWolYq6I5tcYyGw+PwBIJgjDYapA6m0+khMKRaKxBJJSQYrFtMk1MqbXk7AX7ZjomyY7H/ZjI2oNJqtDpdHqa/pdEZjCbTWbGUxLFZAjLmoLXZjCxWimHKbAybgwMkUik0IA==@canton7, thanks for the return.
I'm not really educated on reading those, can you enlighten more about what is happening in the JIT behind the scenes.
Im trying to figure out practices to avoid Inacessible copies when handling structs in methods and stuff
but as far as i understood, the only certain way is to pass everything
ref
In the first one:
sub rsp, 0xa8
allocates 0xa8 bytes on the stack (by subtracting 0xa8 from rsp, the stack pointer). That's then copied to rcx
: under the calling convention which 64-bit Windows uses, the first integer argument to a function is passed in rcx
, so this pointer to the space to store the return value is being passed as a silent first argument. CreateMe
when zeros the memory pointed to by rcx
(using those SIMD instructions to set lots of bytes at once).
So the caller is allocating stack space, passing a pointer to that space into CreateMe
, and CreateMe
is initialising it. CreateMe
isn't creating a local copy first.
The second one is pretty much identical.
In the third one:
Main
this time allocates 0x128 bytes of stack (sub rsp, 0x128
). My x64 assembly isn't great, but it looks like it's doing the new Data()
bit by zeroing out a lot of that stack, then, it calls CreateMe()
(which works the same as before: caller passes a pointer to stack space, callee initialises it). With both data
and the x1024BitBuffer
instances next to each other on the stack, it calls into set_Payload
, which copies the x1024BitBuffer
instance into the field in data
.
With structs, you need to bear in mind the rules for when defensive copied have to be made, and the things which influence that (readonly
, etc). But, also bear in mind that it took only around 4 instructions to initialise your 128-byte array, and 8 instructions to copy it. Each of those vmovdqu
instructions has a latency of 1, so this isn't a huge cost