Replies: 1 comment 6 replies
-
We're OK with API divergence to follow language best practices or work within language constraints, and this may be one of those times. With that said, the mutable Java units library is proving annoying to use in the way all mutable APIs are annoying for value types. We should only look into transitioning to a mutable API after we've exhausted internal optimization opportunities.
I only see this impacting Java. Mutable APIs are a compromise that trades off API usage complexity for fewer allocations caused by Java's poor memory model. Python would have negligible benefit memory-wise, and C++ would have no benefit (in fact, it would bring only downsides). PythonPython robot code is a wrapper around dynamically allocated C++ objects. Python uses refcounting instead of a GC to manage memory, so it doesn't have to worry about accruing a bunch of garbage, then spending a lot of time later cleaning it up. I don't know what Python's allocation overhead is, but in my experience, the language's runtime performance is the bottleneck because it's an interpreted language (loops in particular are very slow). C++In C++, there's no downsides to using an immutable API because value types are used for everything; you get the convenience of immutability without any object management overhead. ChassisSpeeds is a POD type with a trivial destructor, so allocation and deallocation are essentially free at the -O2 optimization level. It just requires incrementing or decrementing the stack pointer, which the compiler will elide by just using the stack memory locations directly. Value types are modern best practice in C++, which often lends itself to immutable APIs. JavaOf the three supported languages, Java forces us to make API design compromises the most often. In Java, allocation is expensive because usable value types don't exist. Valhalla's value types are useless because they're limited to 64 bits and aren't elided until the JIT compiler finally runs; pretty much all our objects are bigger than that. We also have no control over when the GC runs, which means it can interrupt things like control loops for indeterminate amounts of time. Determinism is the primary reason teams should be using motor controller PID, despite collocating the controls code and main logic being easier to reason about and debug. We also have no operator overloading, which makes any math-related code ugly and unreadable compared to its C++ and Python equivalent. We've been transitioning more of WPIMath's Java API to JNI over time because the C++ version is much faster and more maintainable, even taking JNI call overhead into account. C# fixes a lot of these issues via methods for pausing the GC and usable value types. Moving to that is a 2027 thing though, because the C# runtime doesn't support arm32. |
Beta Was this translation helpful? Give feedback.
-
WPILIB types do not follow consistent approach on types mutability. Some types (
Rotation2d
,Translation2d
) are immutable, while other (SwerveModuleState
,ChassisSpeeds
) are mutable. Yet majority of methods defined on mutable types also result in new allocations, as if the types exhibit immutable behavior.While immutability is a powerful and generally much desired trait it does come at a cost of additional memory allocations. Java is specifically at disadvantage here due to GC pressures this creates.
As an example:
This interpolation call results in 4 allocations instead of 1. Similar situations are plentiful within the code.
I can see several possible improvements to consider in addressing this situation.
Low-hanging fruit
For immutable types representing mathematical concepts, introduce constant-like properties for typical values:
And internally "unwrap" complex calculation methods (like
interpolate
) to result in at most single allocation for the returned value. This should not introduce breaking changes except for situations where user's code intentionally uses referential integrity. I would argue though such situations should be extremely rare and represent very questionable practice to begin with.Mutable counterparts
Consider introducing mutable counterparts, such as
MutableRotation2d
, to provide an escape hatch where a sequence of calculations needs to be performed in the user's code and only the end result is relevant. This would allow writing low-allocation code where it matters, such as on the hot code path, utilizing Builder/Freeze pattern, with the clear intent to reduce accidental abuse of mutable instance. Schematically:Exact naming of methods is up for discussion.
Possibly breaking changes
For types like
SwerveModuleState
orChassisSpeeds
decide whether to "close" them to stick to immutable pattern or keep them open and either change the semantic of existing methods or introduce new methods that mutate the instance in place. This part is more difficult to get right and more input is needed. For instance this convention can be used, which is mostly compatible with the current state of the API:From our experience this season we had to rely on the mutability a lot in this case to retain values such as sampled
SwerveDriveWheelPositions
andSwerveModuleState
between cycles. That said I can totally see the argument for forcing WPILIB types to be immutable and let users come up with their own internal ways to deal with persistence.Beta Was this translation helpful? Give feedback.
All reactions