Synced arrays have unexpectedly large overhead (in bytes)
JanSharp
Synced arrays have about 40 bytes of overhead which I cannot explain how those bytes could be used internally.
For a long time I thought synced objects had 12 bytes of base overhead, 4 bytes of overhead per synced variable and then the actual variable data. That would explain the 20 bytes for a single synced int... though the single synced byte with 14 is new to me.
Either way, in the case of a script with a single synced byte array, let's assume the 12 + 4 bytes of overhead are correct (base + per variable), so that's 16. Then let's assume it stores the length of the array in an int, even though that could be done in a space optimized way, but let's just go with 4. That's 20 bytes of total expected overhead for a script with a single synced byte array. However as we can see in the demo log output at the end, it's 64 byteCount when syncing an empty byte array.
Ok but where is the issue?
I've created a lockstep networking implementation on top of VRChat's networking. It requires "heartbeats" to be sent by a tick sync script, that's how clients know they're allowed to advance to the next tick(s). This script sends both the current tick as well as all associated input action ids. Must be in the same script to avoid race conditions. That's a variable amount of data, so it must use an array (I ended up just using a single byte array for all synced data). Even if no input actions are sent and it just syncs the current tick, that's 68 bytes per sync, which at a tickrate of 10 equates to 680 bytes per second, which is over 5% of the 11kb global manually synced limit. I wanted a tickrate of 20, but then we're at 10% while the system is effectively idle. Not to mention that to my knowledge avatar syncing also counts to this 11kb limit. Ultimately I feel bad for wasting both server and user bandwidth... but it is outside of my control. The actual payload is just 2 to 3 bytes per sync (because ticks (uint) are serialized in a space optimized way), so 97% to 95.5% is overhead. Reducing the overhead by 66% (getting rid of those 40 unknown bytes) would be huge.
I think there's a high chance for this to be unintended behavior, so the bug report category fits best.
Demo synced array script: https://gist.github.com/JanSharp/4e59bd352d97b4001a82b5b868f13fdf#file-syncedarraydemo-cs
Demo scene and all scripts: https://gist.github.com/JanSharp/4e59bd352d97b4001a82b5b868f13fdf
Potential demo log output: (<dlt> stands for debug log tracker, so not actually relevant)
<dlt> single synced byte variable - result.success: True, result.byteCount: 14
<dlt> single synced int variable - result.success: True, result.byteCount: 20
<dlt> result.success: True, result.byteCount: 64, data.Length: 0
<dlt> result.success: True, result.byteCount: 68, data.Length: 1
<dlt> result.success: True, result.byteCount: 68, data.Length: 2
<dlt> result.success: True, result.byteCount: 68, data.Length: 3
<dlt> result.success: True, result.byteCount: 68, data.Length: 4
<dlt> result.success: True, result.byteCount: 72, data.Length: 5
<dlt> result.success: True, result.byteCount: 72, data.Length: 6
<dlt> result.success: True, result.byteCount: 72, data.Length: 7
<dlt> result.success: True, result.byteCount: 72, data.Length: 8
<dlt> result.success: True, result.byteCount: 76, data.Length: 9
<dlt> result.success: True, result.byteCount: 76, data.Length: 10
Log In
Fax
The additional overhead comes from byte alignment. This improves performance, but adds additional overhead.
This data is compressed before being sent - but I'm not sure if this counts towards Udon's network limit.
JanSharp
I have noticed the byte alignment, it seems to be 4-byte aligned. While I'd argue that networked data should not have byte alignment, it's also not a big deal. It's only a few more bytes - I'd be surprised if all ~40 extra bytes (out of 64) were due to byte alignmet. There isn't enough different values to align to reach that high, unless there's much more than 3 consecutive bytes of padding somewhere.
In regards to compression, it seems the value reported to Udon through the byteCount in OnPostSerialization is before compression took place. Syncing a byte array of 4000 bytes reports a byteCount of 4064. I did think that the base overhead for an array could be high due to the compression header, but this theory appears to not be the case.