Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement SortedSet for JS/native to improve performance #1167

Merged
merged 2 commits into from Mar 6, 2024

Conversation

m-sasha
Copy link

@m-sasha m-sasha commented Mar 6, 2024

Proposed Changes

Reimplement SortedSet via min-heap + hashmap in the jsNative source-set.

This should give it the following performance:

  • add, remove: O(logN), due to the heap.
  • first: O(1), due to the heap.
  • contains: O(1), thanks to the hash map

Previously the performance was:

  • add, remove: O(N), due to the array.
  • first: О(1)
  • contains: O(logN), due to the binary search in the array.

Testing

Test: Ran the existing tests and added some new ones.

@m-sasha m-sasha requested a review from MatkovIvan March 6, 2024 13:16
Copy link
Member

@MatkovIvan MatkovIvan left a comment

Choose a reason for hiding this comment

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

Looks good aside from minor style nitpicking. Correctness is checked by tests, so it should be good here.

But before merging, I'd like to see comparison before/after to make sure that additional memory consumption is justified.

@m-sasha
Copy link
Author

m-sasha commented Mar 6, 2024

I've run this test on uikitArmSim64 and wasmJs:

    @OptIn(ExperimentalTestApi::class)
    @Test
    fun uiTest() = runComposeUiTest {
        measureTime {
            println("Started")
            var size by mutableStateOf(1.dp)
            setContent {
                Box(Modifier.fillMaxSize()) {
                    Box(Modifier.fillMaxWidth().fillMaxHeight(fraction = 0.5f)) {
                        repeat(20_000) {
                            Box(Modifier.size(size))
                        }
                    }
                    repeat(20_000) {
                        Box(Modifier.size(size))
                    }
                }
            }
            repeat(10) { i ->
                size = (i + 2).dp.also {
                    println("Setting size to $it")
                }
                waitForIdle()
            }
        }.let {
            println(it)
        }
    }

The results are:

Target Implementation Time
uikitArmSim64 Old SortedSet 43 sec.
New SortedSet 22 sec.
New SortedSet* 23 sec.
wasmJs Old SortedSet 16 sec
New SortedSet 3 sec
New SortedSet* 3 sec.
JVM TreeMap 2 sec.

* with mutableScatterMapOf instead of mutableMapOf

Looking at the code, SortedSet is currently used only for MeasureAndLayoutDelegate.relayoutNodes, which I wouldn't expect to be large in normal usage. But someone out there could have abnormal usage, and if we can, why not provide them a faster implementation? The downside is code complexity, of course.

@m-sasha
Copy link
Author

m-sasha commented Mar 6, 2024

The disparity between wasmJS and uikitArmSim64 is suspect. It feels as if we have a badly-performing data structure in the native source-set.

@m-sasha m-sasha merged commit 2d8a99e into jb-main Mar 6, 2024
6 checks passed
@m-sasha m-sasha deleted the m-sasha/native-sorted-set-via-min-heap branch March 6, 2024 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants