Should there be primitive friendly overloads of `d...
# compose
l
Should there be primitive friendly overloads of
derivedStateOf
like for
mutableIntStateOf
,
mutableFloatStateOf
, and
mutableDoubleStateOf
, to avoid autoboxing?
s
Not really, derived state is quite expensive by itself, so you won't be saving much on boxing
l
What makes
derivedStateOf
expensive?
s
Caching / invalidation logic mostly
l
Is it more expensive than using
SnapshotStateObserver
directly?
s
They are orthogonal tbh, you can't use one without the other
well you /can/, but it won't invalidate/track changes correctly
l
blob thinking fast
derivedStateOf
is backed by
SnapshotStateObserver
, right?
Calling
observeReads
on it somewhere?
s
Not really, derived state works independently from it
Derived state is practically a lambda that has some caching mechanisms to avoid invalidating context where it is read in
l
Why
SnapshotStateObserver
wouldn't work the same if you update the final
MutableState
or
Mutable*State
, in the change observer lambda, granted that
SnapshotStateObserver
was started?
s
It is practically a difference between hot and cold observable in this case Derived state is cold, state updated through observer is hot
That allows to skip processing derived states that are never read, for example
l
Same happens with
SnapshotStateObserver
if you don't call
observeReads
at all, right?
But okay, I think I see roughly why one can't implement
derivedStateOf
with just
SnapshotStateObserver
s
You probably could just use something closer to what
snapshotFlow
does.
SnapshotStateObserver
is pretty heavy for observing single state, it is meant to be created once per owner and can create multiple observation contexts
l
All other things equal, if I have to decide between using
derivedStateOf
, and calling this function even if unneeded, which one should I pick, or is there a way to compare the cost calling the function, vs having
derivedStateOf
unread, vs having
derivedStateOf
read and running the computation?
Copy code
fun angle(refAxis: Float, a: Float) = Math.toDegrees(acos(refAxis.toDouble() / a))
s
So derived state is like 100 times slower than reading from a map iirc?
The case above is definitely fine to be just a function
l
Use case is drawing a watch face, so efficiency and power consumption is top-priority
If I understand correcly, computing
angle
eagerly would be more desirable than using a remembered
derivedStateOf
, or using
SnapshotStateObserver
?
s
Yep, it doesn't sound like a lot of math here, and computers are really good at it They are less good at hashmaps and allocations which both state approaches will cause
👍🏼 1
In general, I recommend to benchmark things like that Simple benchmarks are easy to setup and can provide a lot of insight into computational price of caching vs re-executing, for example
l
Are there example benchmarks for this kind of Compose runtime things that don't involve all the Compose UI machinery?
s
They are not really different from any other Android benchmark
We have some internally, but those are too involved I believe
l
I'm not sure how I'd go about testing the cost of
derivedStateOf
when recomposing for example blob thinking fast
Feels like a of setup would be required to test a tiny little thing
s
Right, this one is a bit more complicated, and probably noise from spinning recomposition will be much higher than the actual execution time
l
I would test without recompositions, too
For states read at render time for example
s
I meant something like comparing
state.value
and runtime of the function itself
l
You mean calling
state.value
itself has some cost that might be higher than, say, the
angle
function I showed?
e
likely. math is just cpu cycles, state needs to follow multiple links through memory
s
in derived state case, it needs to verify that state didn't change as well, so it is even more expensive
in fact, state.value in general is quite expensive
l
Are there cases where the cost of
state.value
can vary?
For example, based on which thread it is called on, or at which stage it is called.
s
Yeah, main thread is a bit more optimized + it depends on read observer for the snapshot, so when something observes the state, it is more expensive
l
What makes it that an observed state is more expensive to read from exactly? Is it calling observers synchronously before returning from `value`'s getter?
s
Yep, exactly
l
I was pondering last night, is calling
map { it % 2 }.distinctUntilChanged().collect { }
on a
StateFlow<Int>
cheaper than having a derivedStateOf doing the same over a
MutableIntState
or a
MutableState<Int>
?
s
It depends? How do you use this flow, is it consumed in composition or somewhere else? You might also want to run
LaunchedEffect
for the flow, where coroutines also have some cost associated with it.
l
Outside of a composable function, not in Compose UI
s
Right, I'd probably go with
Flow
here, it's likely to be faster and also more reasonable for non-observable scopes. The only way to figure out what is faster is to run a benchmark though 😄
👍 1
157 Views