-
I’m making a real-time strategy game where you’ll control a lot of units, and I’ve set up the unit data as arrays of unmanaged structs. This way I can access it as a byte array and e.g. perform CRC operations on the entire gamestate very quickly, which lets me compare it with other clients and make sure they’re in sync. My problem is when I run the game in Release, some of my structs now have random data in them that change the checksums and cause false desync warnings. I found out this is caused by bytes changing within the padding of my structs. I managed to create a somewhat simple repro case that still displays the problem - at least on my machine:
Each run results in the last three bytes changing, e.g:
If I merge Struct and Struct2 or remove/change any of the fields, the output becomes all zeroes. This is the layout of Struct2:
The last three bytes at the end is the padding that is causing my grief. Interestingly, the first padding (between A and B) seems to get cleared, or I’m just being lucky that it's zeroed. So my questions:
|
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 18 replies
-
This looks like a gap in JIT: Struct2 Foo() => default;
Struct2 Foo2() => new Struct2(default, default); ; Method CSPlayground.Program:Foo():CSPlayground.Struct2:this (FullOpts)
G_M000_IG01: ;; offset=0x0000
vzeroupper
G_M000_IG02: ;; offset=0x0003
vxorps xmm0, xmm0, xmm0
vmovdqu xmmword ptr [rdx], xmm0
vmovdqu xmmword ptr [rdx+0x04], xmm0
mov rax, rdx
G_M000_IG03: ;; offset=0x0013
ret
; Total bytes of code: 20 ; Method CSPlayground.Program:Foo2():CSPlayground.Struct2:this (FullOpts)
G_M000_IG01: ;; offset=0x0000
sub rsp, 24
vzeroupper
G_M000_IG02: ;; offset=0x0007
vxorps xmm0, xmm0, xmm0
vmovdqu xmmword ptr [rsp], xmm0
vmovdqu xmm0, xmmword ptr [rsp]
vmovdqu xmmword ptr [rdx], xmm0
mov eax, dword ptr [rsp+0x10]
mov dword ptr [rdx+0x10], eax
mov byte ptr [rdx+0x10], 0
mov rax, rdx
G_M000_IG03: ;; offset=0x0027
add rsp, 24
ret
; Total bytes of code: 44 Then
It seems that @EgorBo any idea about the expected behavior here? |
Beta Was this translation helpful? Give feedback.
-
I don't think I could enforce using only that, and it would be too error prone to try to remember it. I realize that I am relying on undefined behavior here. It's a shame that there aren't stronger guarantees, it's extremely useful to be able to cast data to I understand the risk of a performance regression if you decide to zero out the padding, but at least from my (admittedly brief) testing it's rare for structures to have this problem. Like I mentioned in my example above, the problem disappears if I try to merge the two structs, or if I change any of the contents. Perhaps it's possible to direct the changes to only these corner cases, meaning any performance regressions would not be noticeable in most code? |
Beta Was this translation helpful? Give feedback.
-
Yeah, it's not optimal - but I'm targeting only windows, so I think I might get away with it. I guess I'd still be vulnerable to changes across .NET versions if I output the serialized state as save data. I would prefer if there was a performant way to do this that was also portable, but e.g. writing each struct field individually is not even close to being fast enough for my needs - I'll have thousands of units with several dozens of data arrays per unit, and serialization should take no more than a couple of milliseconds. I'm still considering using If I may dream for a bit, it might be useful to have a |
Beta Was this translation helpful? Give feedback.
-
I appreciate everyone's input about this issue! In the end I decided to enforce |
Beta Was this translation helpful? Give feedback.
I appreciate everyone's input about this issue! In the end I decided to enforce
Pack=1
on my unit data structs to avoid the padding garbage.