From 3fbcc1f4b974c407c6e836541a0c5f94467d12c6 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 18 Nov 2024 11:34:53 -0700 Subject: [PATCH 1/2] rustdoc-search: use smart binary search in bitmaps Addresses a comment from [jsha's benchmarking], where the `contains` function showed up in the profiler. This commit pulls it from about 5% of the runtime to about 0.5%. [jsha's benchmarking]: https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/search.20profiling/near/481868761 --- src/librustdoc/html/static/js/search.js | 42 +++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index c1a021e9f8d9b..bee5327f6fca2 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1053,9 +1053,17 @@ class RoaringBitmap { contains(keyvalue) { const key = keyvalue >> 16; const value = keyvalue & 0xFFFF; - for (let i = 0; i < this.keys.length; ++i) { - if (this.keys[i] === key) { - return this.containers[i].contains(value); + let left = 0; + let right = this.keys.length - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const x = this.keys[mid]; + if (x < key) { + left = mid + 1; + } else if (x > key) { + right = mid - 1; + } else { + return this.containers[mid].contains(value); } } return false; @@ -1068,11 +1076,18 @@ class RoaringBitmapRun { this.array = array; } contains(value) { - const l = this.runcount * 4; - for (let i = 0; i < l; i += 4) { + let left = 0; + let right = this.runcount - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const i = mid * 4; const start = this.array[i] | (this.array[i + 1] << 8); const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8); - if (value >= start && value <= (start + lenm1)) { + if ((start + lenm1) < value) { + left = mid + 1; + } else if (start > value) { + right = mid - 1; + } else { return true; } } @@ -1085,10 +1100,17 @@ class RoaringBitmapArray { this.array = array; } contains(value) { - const l = this.cardinality * 2; - for (let i = 0; i < l; i += 2) { - const start = this.array[i] | (this.array[i + 1] << 8); - if (value === start) { + let left = 0; + let right = this.cardinality - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const i = mid * 2; + const x = this.array[i] | (this.array[i + 1] << 8); + if (x < value) { + left = mid + 1; + } else if (x > value) { + right = mid - 1; + } else { return true; } } From 826d023561178fdc7bf961beba820fd460b0d89a Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 18 Nov 2024 13:37:53 -0700 Subject: [PATCH 2/2] rustdoc-search: add descriptive comments to bitmap class --- src/librustdoc/html/static/js/search.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index bee5327f6fca2..9e5cf49721139 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -988,6 +988,12 @@ class VlqHexDecoder { } class RoaringBitmap { constructor(str) { + // https://github.com/RoaringBitmap/RoaringFormatSpec + // + // Roaring bitmaps are used for flags that can be kept in their + // compressed form, even when loaded into memory. This decoder + // turns the containers into objects, but uses byte array + // slices of the original format for the data payload. const strdecoded = atob(str); const u8array = new Uint8Array(strdecoded.length); for (let j = 0; j < strdecoded.length; ++j) { @@ -1053,6 +1059,13 @@ class RoaringBitmap { contains(keyvalue) { const key = keyvalue >> 16; const value = keyvalue & 0xFFFF; + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Format is required by specification to be sorted. + // Because keys are 16 bits and unique, length can't be + // bigger than 2**16, and because we have 32 bits of safe int, + // left + right can't overflow. let left = 0; let right = this.keys.length - 1; while (left <= right) { @@ -1076,6 +1089,11 @@ class RoaringBitmapRun { this.array = array; } contains(value) { + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Since runcount is stored as 16 bits, left + right + // can't overflow. let left = 0; let right = this.runcount - 1; while (left <= right) { @@ -1100,6 +1118,11 @@ class RoaringBitmapArray { this.array = array; } contains(value) { + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Since cardinality can't be higher than 4096, left + right + // cannot overflow. let left = 0; let right = this.cardinality - 1; while (left <= right) {