Skip to content

Commit

Permalink
refactor(ivy): double size of DI bloom filter (angular#22775)
Browse files Browse the repository at this point in the history
PR Close angular#22775
  • Loading branch information
kara authored and leo6104 committed Mar 25, 2018
1 parent fef5ddc commit 472e3a6
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 31 deletions.
61 changes: 37 additions & 24 deletions packages/core/src/render3/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const NG_ELEMENT_ID = '__NG_ELEMENT_ID__';
* directives that will share slots, and thus, the fewer false positives when checking for
* the existence of a directive.
*/
const BLOOM_SIZE = 128;
const BLOOM_SIZE = 256;

/** Counter used to generate unique IDs for directives. */
let nextNgElementId = 0;
Expand All @@ -66,9 +66,9 @@ export function bloomAdd(injector: LInjector, type: Type<any>): void {
id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
}

// We only have BLOOM_SIZE (128) slots in our bloom filter (4 buckets * 32 bits each),
// so all unique IDs must be modulo-ed into a number from 0 - 127 to fit into the filter.
// This means that after 128, some directives will share slots, leading to some false positives
// 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.
// This means that after 255, some directives will share slots, leading to some false positives
// when checking for a directive's presence.
const bloomBit = id % BLOOM_SIZE;

Expand All @@ -78,20 +78,14 @@ export function bloomAdd(injector: LInjector, type: Type<any>): void {
const mask = 1 << bloomBit;

// 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]
if (bloomBit < 64) {
if (bloomBit < 32) {
// Then use the mask to flip on the bit (0-31) associated with the directive in that bucket
injector.bf0 |= mask;
} else {
injector.bf1 |= mask;
}
// e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
if (bloomBit < 128) {
// Then use the mask to flip on the bit (0-31) associated with the directive in that bucket
bloomBit < 64 ? (bloomBit < 32 ? (injector.bf0 |= mask) : (injector.bf1 |= mask)) :
(bloomBit < 96 ? (injector.bf2 |= mask) : (injector.bf3 |= mask));
} else {
if (bloomBit < 96) {
injector.bf2 |= mask;
} else {
injector.bf3 |= mask;
}
bloomBit < 192 ? (bloomBit < 160 ? (injector.bf4 |= mask) : (injector.bf5 |= mask)) :
(bloomBit < 224 ? (injector.bf6 |= mask) : (injector.bf7 |= mask));
}
}

Expand Down Expand Up @@ -119,10 +113,18 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
bf1: 0,
bf2: 0,
bf3: 0,
bf4: 0,
bf5: 0,
bf6: 0,
bf7: 0,
cbf0: parentInjector == null ? 0 : parentInjector.cbf0 | parentInjector.bf0,
cbf1: parentInjector == null ? 0 : parentInjector.cbf1 | parentInjector.bf1,
cbf2: parentInjector == null ? 0 : parentInjector.cbf2 | parentInjector.bf2,
cbf3: parentInjector == null ? 0 : parentInjector.cbf3 | parentInjector.bf3,
cbf4: parentInjector == null ? 0 : parentInjector.cbf4 | parentInjector.bf4,
cbf5: parentInjector == null ? 0 : parentInjector.cbf5 | parentInjector.bf5,
cbf6: parentInjector == null ? 0 : parentInjector.cbf6 | parentInjector.bf6,
cbf7: parentInjector == null ? 0 : parentInjector.cbf7 | parentInjector.bf7,
injector: null,
templateRef: null,
viewContainerRef: null,
Expand Down Expand Up @@ -461,11 +463,17 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
// match.
let injector: LInjector|null = startInjector;
while (injector) {
// Our bloom filter size is 128 bits, which is four 32-bit bloom filter buckets:
// bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127]
// 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 = bloomBit < 64 ? (bloomBit < 32 ? injector.bf0 : injector.bf1) :
(bloomBit < 96 ? injector.bf2 : injector.bf3);
let value: number;
if (bloomBit < 128) {
value = bloomBit < 64 ? (bloomBit < 32 ? injector.bf0 : injector.bf1) :
(bloomBit < 96 ? injector.bf2 : injector.bf3);
} else {
value = bloomBit < 192 ? (bloomBit < 160 ? injector.bf4 : injector.bf5) :
(bloomBit < 224 ? injector.bf6 : injector.bf7);
}

// If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on,
// this injector is a potential match.
Expand All @@ -474,9 +482,14 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
}

// If the current injector does not have the directive, check the bloom filters for the ancestor
// injectors (cbf0 - cbf3). These filters capture *all* ancestor injectors.
value = bloomBit < 64 ? (bloomBit < 32 ? injector.cbf0 : injector.cbf1) :
(bloomBit < 96 ? injector.cbf2 : injector.cbf3);
// injectors (cbf0 - cbf7). These filters capture *all* ancestor injectors.
if (bloomBit < 128) {
value = bloomBit < 64 ? (bloomBit < 32 ? injector.cbf0 : injector.cbf1) :
(bloomBit < 96 ? injector.cbf2 : injector.cbf3);
} else {
value = bloomBit < 192 ? (bloomBit < 160 ? injector.cbf4 : injector.cbf5) :
(bloomBit < 224 ? injector.cbf6 : injector.cbf7);
}

// If the ancestor bloom filter value has the bit corresponding to the directive, traverse up to
// find the specific injector. If the ancestor bloom filter does not have the bit, we can abort.
Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/render3/interfaces/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,27 @@ export interface LInjector {
* array at this level unless it's probable the directive is in it.
*
* - bf0: Check directive IDs 0-31 (IDs are % 128)
* - bf1: Check directive IDs 33-63
* - bf1: Check directive IDs 32-63
* - bf2: Check directive IDs 64-95
* - bf3: Check directive IDs 96-127
* - bf4: Check directive IDs 128-159
* - bf5: Check directive IDs 160 - 191
* - bf6: Check directive IDs 192 - 223
* - bf7: Check directive IDs 224 - 255
*
* See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters.
*/
bf0: number;
bf1: number;
bf2: number;
bf3: number;
bf4: number;
bf5: number;
bf6: number;
bf7: number;

/**
* cbf0 - cbf3 properties determine whether a directive is available through a
* cbf0 - cbf7 properties determine whether a directive is available through a
* parent injector. They refer to the merged values of parent bloom filters. This
* allows us to skip looking up the chain unless it's probable that directive exists
* up the chain.
Expand All @@ -56,6 +64,11 @@ export interface LInjector {
cbf1: number;
cbf2: number;
cbf3: number;
cbf4: number;
cbf5: number;
cbf6: number;
cbf7: number;

injector: Injector|null;

/** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */
Expand Down
39 changes: 34 additions & 5 deletions packages/core/test/render3/di_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,36 +487,65 @@ describe('di', () => {
di.bf1 = 0;
di.bf2 = 0;
di.bf3 = 0;
di.bf4 = 0;
di.bf5 = 0;
di.bf6 = 0;
di.bf7 = 0;
di.bf3 = 0;
di.cbf0 = 0;
di.cbf1 = 0;
di.cbf2 = 0;
di.cbf3 = 0;
di.cbf4 = 0;
di.cbf5 = 0;
di.cbf6 = 0;
di.cbf7 = 0;
});

function bloomState() { return [di.bf3, di.bf2, di.bf1, di.bf0]; }
function bloomState() {
return [di.bf7, di.bf6, di.bf5, di.bf4, di.bf3, di.bf2, di.bf1, di.bf0];
}

it('should add values', () => {
bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any);
expect(bloomState()).toEqual([0, 0, 0, 1]);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 32 + 1 } as any);
expect(bloomState()).toEqual([0, 0, 2, 1]);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 64 + 2 } as any);
expect(bloomState()).toEqual([0, 4, 2, 1]);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 96 + 3 } as any);
expect(bloomState()).toEqual([8, 4, 2, 1]);
expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 128 + 4 } as any);
expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 160 + 5 } as any);
expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 192 + 6 } as any);
expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]);
bloomAdd(di, { __NG_ELEMENT_ID__: 224 + 7 } as any);
expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]);
});

it('should query values', () => {
bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 32 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 64 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 96 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 127 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 161 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 188 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 223 } as any);
bloomAdd(di, { __NG_ELEMENT_ID__: 255 } as any);

expect(bloomFindPossibleInjector(di, 0)).toEqual(di);
expect(bloomFindPossibleInjector(di, 1)).toEqual(null);
expect(bloomFindPossibleInjector(di, 32)).toEqual(di);
expect(bloomFindPossibleInjector(di, 64)).toEqual(di);
expect(bloomFindPossibleInjector(di, 96)).toEqual(di);
expect(bloomFindPossibleInjector(di, 127)).toEqual(di);
expect(bloomFindPossibleInjector(di, 161)).toEqual(di);
expect(bloomFindPossibleInjector(di, 188)).toEqual(di);
expect(bloomFindPossibleInjector(di, 223)).toEqual(di);
expect(bloomFindPossibleInjector(di, 255)).toEqual(di);
});
});

Expand Down

0 comments on commit 472e3a6

Please sign in to comment.