Skip to content

[K/N] Make the full build of a CustomBitSet lazy#5735

Open
troelsbjerre wants to merge 1 commit intomasterfrom
prr/troels/lazy-custom-bitset
Open

[K/N] Make the full build of a CustomBitSet lazy#5735
troelsbjerre wants to merge 1 commit intomasterfrom
prr/troels/lazy-custom-bitset

Conversation

@troelsbjerre
Copy link
Collaborator

This changes the representation of CustomBitSet to initially be an explicit list of set bits, until the cardinality grows beyond a fixed size. This saves a lot of memory, since many of the use cases of CustomBitSet are sparse.

@kotlin-safe-merge
Copy link

kotlin-safe-merge bot commented Mar 11, 2026

Code Owners

Rule Owners Approval
/​kotlin-​native/​ kotlin-native 🔴

@anton-bannykh
Copy link
Contributor

I observe a roughly 25% speedup on a reasonably large KMP project. Good job! 🎉

@anton-bannykh
Copy link
Contributor

So far the biggest question I have is how clearing a bi should be handled. When the bits are only added everything is clear, and there are some nice invariants we could rely on. When a bit is cleared then suddenly we can get same bitsets with different internal representations. That complicated stuff quite a bit.

What do you think? Did not convert a bitset back to the lazy for as shrinks intentionally?

@troelsbjerre
Copy link
Collaborator Author

I don't think the efficiency loss of enforcing an invariant like "for cardinality < k, it's always an IntArraySet, and otherwise a BitSet" would be worth it. Tracking cardinality of the BitSet would complicate the implementation, and switching back and forth between representations would be potentially costly, where "add(1); remove(1); add(1);..." would potentially require allocating a huge LongArray on every add.

@@ -5,20 +5,33 @@

package org.jetbrains.kotlin.backend.konan.util

import it.unimi.dsi.fastutil.ints.IntArraySet
import it.unimi.dsi.fastutil.ints.IntSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine to use, will double check

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth noting that the fastutils library is used elsewhere in the compiler. This is not a new dependecy.

/**
* Provides some bulk operations needed for devirtualization
*/
internal class CustomBitSet private constructor(size: Int, data: LongArray) {
var size = size
private set
private var data = data
private var lazy: IntSet? = IntArraySet()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] When using the private contructor the lazy property still gets initialized. This leads to effective "empty" lazy bitset despite the data array that was passed.

This affects the copy method and the valueOf companion method

@mcpiroman
Copy link
Contributor

You may also be interested in using a library such as https://github.com/RoaringBitmap/RoaringBitmap. As I get it, it implements an optimized bitmap with a dynamic underlying representation, similar to the one proposed here.

@anton-bannykh
Copy link
Contributor

You may also be interested in using a library such as https://github.com/RoaringBitmap/RoaringBitmap. As I get it, it implements an optimized bitmap with a dynamic underlying representation, similar to the one proposed here.

I tried it back in August, didn't observe any benefit for some reason 🤷 . Maybe I used it wrong or something.

@troelsbjerre
Copy link
Collaborator Author

You may also be interested in using a library such as https://github.com/RoaringBitmap/RoaringBitmap. As I get it, it implements an optimized bitmap with a dynamic underlying representation, similar to the one proposed here.

I'll do a quick test to see if there is a noticeable difference. I suspect the general RoaringBitMap might be at a disadvantage for the common case of very sparse BitSets, with only a handful of set bits.

@troelsbjerre
Copy link
Collaborator Author

Changing from CustomBitSet to RoaringBitmaps, the peak memory usage drops by 5%, but the link step takes 11% longer.

This changes the representation of CustomBitSet to initially be an
explicit list of set bits, until the cardinality grows beyond a fixed
size. This saves a lot of memory, since many of the use cases of
CustomBitSet are sparse.
@troelsbjerre troelsbjerre force-pushed the prr/troels/lazy-custom-bitset branch from 174530a to db4252a Compare March 25, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants