-
Notifications
You must be signed in to change notification settings - Fork 420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change ValueChangedEvent
to a struct
#6114
base: master
Are you sure you want to change the base?
Conversation
My main problem with this change is that I have no idea how to gauge if it does anything good or if it breaks anything (loudly or silently). Like yes it takes 5 minutes to review the diff but this may as well break somewhere in the hundreds of usages of the struct somewhere else due to type semantics changing. And without conclusive profiling I'm not even seeing what we get out of this. It is not obvious to me that the decreased heap pressure is worth trading off for increased number of struct copies. I'm currently running game-side test suites as an initial sweep in a weak attempt to estimate the blast radius of this. But I don't know what comes next. |
@@ -7,7 +7,7 @@ namespace osu.Framework.Bindables | |||
/// An event fired when a value changes, providing the old and new value for reference. | |||
/// </summary> | |||
/// <typeparam name="T">The type of bindable.</typeparam> | |||
public class ValueChangedEvent<T> | |||
public struct ValueChangedEvent<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could probably declare as readonly struct
at this point, but again, not sure what that does or doesn't do (in fact it'd be safer to do so if possible as that decreases the chance of defensive copying happening)
Do you have an example of where this struct is copied? Most every usage I looked at was (as you'd hope) immediate usage and then throw-away. That's the only reason I made this change and am confident in doing so without much thought. |
No but that's part of the point. I'm not sure how I would be able to tell where it is or isn't copied without spending hours combing through every usage. |
Well even if it's copies, ignoring performance issues (since logically i'm sure we can agree any copies would be a an edge case) it should be enough to check for zero mutation of the event's properties to ensure behaviour is not regressed, right? |
Yeah see I'm not sure about that either.
Which suggests that copies are going to be happening as there is no If we were wanting to fix that, we'd have to change the signature of |
I tried to write a benchmark to confirm or deny the above and I'm even more confused than I was before. https://gist.github.com/bdach/80815935278b1ff580cebe16f9931ec3 The results make zero sense to me.
Did I screw up the benchmark somehow? |
Likely due to having to dereference from non-contiguous references.
Because the JIT is smart enough to pass structs by value if they're small enough. You should see an effect by increasing the size of the struct. https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/ |
I spammed a few extra fields... but am equally unsure how to interpret the results...? Okay sure the gen0-2 counts are gone (which is expected? I think?) but also structs are now... slower? Universally, too? I must be somehow benchmarking wrong because this is nonsense. The blogpost linked above even says this:
which confirms my initial thinking, and is kind of where I'm stuck at deciding if it should be |
I would have expected these to go away for even the earlier benchmarks, can you explain why they are still there? o_O |
Nope, I cannot. |
I'm going to drop this into a draft state in the hope of investigating and understanding this for the future. Let's not make rash changes here for now. |
There's very little reason for this to be a class.
It is constructed by each bindable locally when it has listeners, and generally never modified or passed further. Changing to a struct reduces GC overhead.
(honestly we shouldn't be firing these enough for it to be an issue in the first place, but there's some egregious usage of bindables in the game which needs further consideration to fix, and this may alleviate things somewhat).
Not sure whether this needs profiling. A well structured benchmark may show lower GC counts, but allocations should not change.
osu!-side changes: