Hey all, we're trying to implement a common Map cl...
# multiplatform
s
Hey all, we're trying to implement a common Map class that uses thread confinement for synchronization. We've settled on either using Mutex or a custom implementation of the Actor pattern. Are there any significant advantages or disadvantages to either of these approaches?
a
You are mixing a lot of things. Mutex, if you mean coroutine Mutex is not exactly about treads. On the other hand, Actor by itself does not handle synchronization. Also there is a reason why default maps are not synchronized - it causes significant loss of performance. I think that the best solution is still regular map with exteranl Mutex synchronization for coroutines.
d
IMO because of Kotlin/Native, I'd say go with the Actor pattern and keep the Map in a single thread. Sharing the Map data across threads will be a painful battle against the runtime, which will cost you in performance.
a
You can actually call external synchronization an Actor if you like.
s
We're using these maps for both our HTTP requester's ratelimit implementation and our state cache, both of which need to be synchronized. This common implementation will only be used for native, JVM will use
ConcurrentHashMap
.
Correction, it'll be used for both.
k
have you seen stately from Touchlab?
☝🏼 1
s
I've seen it mentioned, although I'm hesitant to add another dependency for a single class.
k
i think once you get into the details of the implementation that hesitation will quickly disappear 😛
stately is also small and has a number of other useful APIs
s
I won't lie, I mostly avoid non-Jetbrains multiplatform libraries due to my experience with Klock 😛 but Stately seems pretty attractive at first glance, so we'll give it a look.
🍻 1
p
search for lock-free CAS map. Those are by far the most performant map algorithm available today. However due to K/N threading model you likely need to implement the backing structure in C and likely want to insert manipulate using StableRef or even the hidden private API
Any.share()
k
you'd better have a good reason to go to all that trouble
k
Stately has concurrent maps, but they will be slow
I just pushed a version of Koin that keeps mutable maps in the main thread, as most DI happens in the main thread anyway, and can keep non-frozen state. Background threads can request cross-thread state, but meh
So, like a special case situation where multiple threads are the exception: https://github.com/kpgalligan/koin/tree/kpg/khan
k
slow is relative. they'll be liked greased lightning when you're comparing it to a network request.
k
My plan is to add a more generalized isolated state feature to Stately, where you have a worker that brokers data around. I’ve done some tests and it is significantly faster doing the same things that Stately does now, and way way faster if you can batch operations
k
sounds interesting
k
The current Stately collections are implemented with AtomicReference. It’s not great. Besides performance, you should really clear AtomicReference out when done with it.
Originally the plan was to implement standard collections with isolated state, but then the thought was just to use lambdas in a structured way to get at shared state in an isolated thread
Kind of a weird example, but it would look like this
Copy code
class SharedState: IsolateState<MutableList<String>>({ mutableListOf()}) {
        suspend fun pop():String? = withContext(stateDispatcher) {
            access { isoList ->
                if(isoList.size > 0)
                    isoList.remove(0)
                else
                    null
            }
        }
    }
Move into a state thread (
withContext(stateDispatcher)
), then the
access
gives the state and you can treat it like any mutable state. Anything that happens in there is thread confined, so other threads won’t cause issues. Using just a concurrent list of some type, the size check might not be valid right after you check it, so you need to also add locks or whatever.
Going way up to earlier comments, avoiding non-JB libraries because of Klock is something I’d advise against. There’s good stuff out there.
🙂 1
e
Like Island Time. Shameless plug. 🙂 I'm curious to see what solution ends being most efficient in practice with K/N for something
ConcurrentHashMap
-like. Might play around a little at some point.
s
I'd use Island Time if it had linux-x64 support! :P
l
@kpgalligan Have you considered taking Java's
ConcurrentHashMap
approach by implementing a custom
HashMap
where locking only happens in the section of the map (or bucket) that is being currently modified? It sounds interesting to me and locking can be implemented using
Mutex
to be coroutine-friendly
k
The problem is mutable state in kotlin native. It needs to be frozen, so everything winds up as atomics. Stately’s map is a custom hash map. It’s not a sophisticated implementation of a map, but still. The arrays that implement the map are all atomic reference. The list for the values in those buckets are themselves implemented with atomics. Better use of mutex would speed it up, but marginally. For real performance you need mutable state. For kotlin native, that means either thread confined, or we do it in c++
Having done some tests with the thread confined state, I think that approach is promising. Especially if you’re prepared to think outside the box a bit. If you measure time to insert 100k items in a thread confined map vs a plain old mutable map. It’s pretty slow. If you insert them in 100 blocks of 1000, it’s very fast.