elm-geometry
elm-geometry copied to clipboard
Remove 'clever' optimization in rotation functions?
Currently, rotateAround
functions are optimized for partial application - they cache computed sines/cosines etc. in a returned lambda, which is then just immediately called and discarded if rotateAround
is called with three arguments instead of two. This makes cases like
List.map (Point3d.rotateAround axis angle) points
more efficient since sines/cosines etc. are only calculated once, but
Point3d.rotateAround axis angle point
less efficent since a lambda function is allocated and then immediately discarded.
It may be better to switch to the 'naive' implementation, and use the 'frame trick' for efficient transformations where necessary. This would make efficient single calculations possible (currently there's no easy way to avoid the lambda-allocation overhead), and there would be a consistent story for optimization - just use frames!
I stumbled across this and was interested in the numbers, so here is a quick benchmark
I'm not sure how to weigh this really, but it's interesting to know. Remarkably, the speed increase for optimized
doesn't scale that much with the size of the list: it is ~40% for a 5-element list, but only about ~44% for a 1000-element list. Looks like there is some jit optimization/caching going on.
code (Doesn't actually run in ellie because of the elm: Map.!: given key is not an element in the map
compiler bug)
Very useful, thanks @folkertdev! I'm glad that it's not an order of magnitude difference either way, so if we make a change at some point it's not like anyone's performance is going to completely fall off a cliff.
I was thinking it would be really cool if the Elm compiler could apply this optimization automatically - if a function is ever partially applied, eagerly evaluate anything that only depends on the arguments that have been supplied, then cache those so they don't have to be re-evaluated each time the partially applied function is called. Then functions like rotateAround
could be written in the "naive" way and the compiler would effectively produce the "optimized" version if they were partially applied.
Possible approach: add Rotation2d
and Rotation3d
types which internally store pre-computed rotation matrix components. Then keep the existing rotateAround
functions but add (e.g.)
Point3d.rotateBy : Rotation3d -> Point3d -> Point3d
with
Rotation3d.around : Axis3d -> Angle -> Rotation3d
-- Possibly also
Rotation3d.counterclockwiseAround : Axis3d -> Rotation3d
Rotation3d.clockwiseAround : Axis3d -> Rotation3d
then Point3d.rotateAround
could be implemented as
rotateAround : Axis3d -> Angle -> Point3d -> Point3d
rotateAround axis angle point =
rotateBy (Rotation3d.around axis angle) point
with the downside that this now requires a Rotation3d
object allocation...perhaps keep the current implementation of rotateAround
(minus the 'clever' optimization) for fixed-size types like points, line segments and triangles and redirect to rotateBy
with a newly-constructed Rotation3d
value for types like Polyline3d
where the allocation is more likely to be worthwhile.