From fad10838736507ae88a69acc236330f45feca1a2 Mon Sep 17 00:00:00 2001 From: JoostK Date: Tue, 19 Jan 2021 21:43:11 +0100 Subject: [PATCH] perf(core): simplify bloom bucket computation (#40489) The injector system uses a bloom filter to determine if a token is possibly defined in the node injector tree, which is stored across multiple bloom buckets that each represent 32 bits of the full 256-bit wide bloom hash. This means that a computation is required to determine the exact bloom bucket which is responsible for storing any given 32-bit interval, which was previously computed using three bitmask operations and three branches to derive the bloom bucket offset. This commit exploits the observation that all bits beyond the low 5 bits of the bloom hash are an accurate representation for the bucket offset, if shifted right such that those bits become the least significant bits. This reduces the three bitmask operations and three branches with a single shift operation, while additionally offering a code size improvement. PR Close #40489 --- goldens/size-tracking/aio-payloads.json | 2 +- .../size-tracking/integration-payloads.json | 2 +- packages/core/src/render3/di.ts | 48 +++++++------------ 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 369ce99ace..0520b53e03 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 3033, - "main-es2015": 448090, + "main-es2015": 447514, "polyfills-es2015": 52493 } } diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index e1d22c89e1..38791a2991 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -39,7 +39,7 @@ "master": { "uncompressed": { "runtime-es2015": 2285, - "main-es2015": 241738, + "main-es2015": 241202, "polyfills-es2015": 36709, "5-es2015": 745 } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 0decb16f94..fa1f98e62e 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -87,6 +87,13 @@ export function setIncludeViewProviders(v: boolean): boolean { const BLOOM_SIZE = 256; const BLOOM_MASK = BLOOM_SIZE - 1; +/** + * The number of bits that is represented by a single bloom bucket. JS bit operations are 32 bits, + * so each bucket represents 32 distinct tokens which accounts for log2(32) = 5 bits of a bloom hash + * number. + */ +const BLOOM_BUCKET_BITS = 5; + /** Counter used to generate unique IDs for directives. */ let nextNgElementId = 0; @@ -116,27 +123,17 @@ export function bloomAdd( // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each), // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter. - const bloomBit = id & BLOOM_MASK; + const bloomHash = id & BLOOM_MASK; // Create a mask that targets the specific bit associated with the directive. // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. - const mask = 1 << bloomBit; + const mask = 1 << bloomHash; - // Use the raw bloomBit number to determine which bloom filter bucket we should check - // e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc - const b7 = bloomBit & 0x80; - const b6 = bloomBit & 0x40; - const b5 = bloomBit & 0x20; - const tData = tView.data as number[]; - - if (b7) { - b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) : - (b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask)); - } else { - b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) : - (b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask)); - } + // Each bloom bucket in `tData` represents `BLOOM_BUCKET_BITS` number of bits of `bloomHash`. + // Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset that the mask + // should be written to. + (tView.data as number[])[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)] |= mask; } /** @@ -675,22 +672,11 @@ export function bloomHasToken(bloomHash: number, injectorIndex: number, injector // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. const mask = 1 << bloomHash; - const b7 = bloomHash & 0x80; - const b6 = bloomHash & 0x40; - const b5 = bloomHash & 0x20; - // Our bloom filter size is 256 bits, which is eight 32-bit bloom filter buckets: - // bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc. - // Get the bloom filter value from the appropriate bucket based on the directive's bloomBit. - let value: number; - - if (b7) { - value = b6 ? (b5 ? injectorView[injectorIndex + 7] : injectorView[injectorIndex + 6]) : - (b5 ? injectorView[injectorIndex + 5] : injectorView[injectorIndex + 4]); - } else { - value = b6 ? (b5 ? injectorView[injectorIndex + 3] : injectorView[injectorIndex + 2]) : - (b5 ? injectorView[injectorIndex + 1] : injectorView[injectorIndex]); - } + // Each bloom bucket in `injectorView` represents `BLOOM_BUCKET_BITS` number of bits of + // `bloomHash`. Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset + // that should be used. + const value = injectorView[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)]; // If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on, // this injector is a potential match.