perf(ngcc): use binary search when flattening mappings (#36027)

The `@angular/core` package has a large number of source files
and mappings which exposed performance issues in the new source-map
flattening algorithm.

This change uses a binary search (rather than linear) when finding
matching mappings to merge. Initial measurements indicate that this
reduces processing time for `@angular/core` by about 50%.

PR Close #36027
This commit is contained in:
Pete Bacon Darwin
2020-03-09 17:03:36 +00:00
committed by Andrew Kushnir
parent c852ec9283
commit 348ff0c8ea
2 changed files with 236 additions and 15 deletions

View File

@ -10,15 +10,14 @@ import {encode} from 'sourcemap-codec';
import {absoluteFrom} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {RawSourceMap} from '../../src/sourcemaps/raw_source_map';
import {SourceFile, computeLineLengths, extractOriginalSegments, parseMappings} from '../../src/sourcemaps/source_file';
import {SegmentMarker} from '../../src/sourcemaps/segment_marker';
import {Mapping, SourceFile, computeLineLengths, extractOriginalSegments, findLastMappingIndexBefore, parseMappings} from '../../src/sourcemaps/source_file';
runInEachFileSystem(() => {
describe('SourceFile and utilities', () => {
let _: typeof absoluteFrom;
beforeEach(() => {
_ = absoluteFrom;
});
beforeEach(() => { _ = absoluteFrom; });
describe('parseMappings()', () => {
it('should be an empty array for source files with no source map', () => {
@ -84,6 +83,205 @@ runInEachFileSystem(() => {
});
});
describe('findLastMappingIndexBefore', () => {
it('should find the highest mapping index that has a segment marker below the given one if there is not an exact match',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(2);
});
it('should find the highest mapping index that has a segment marker (when there are duplicates) below the given one if there is not an exact match',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 30};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(3);
});
it('should find the last mapping if the segment marker is higher than all of them', () => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 60};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(4);
});
it('should return -1 if the segment marker is lower than all of them', () => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 5};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 0);
expect(index).toEqual(-1);
});
describe('[exact match inclusive]', () => {
it('should find the matching segment marker mapping index if there is only one of them',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const index = findLastMappingIndexBefore(mappings, marker3, /* exclusive */ false, 0);
expect(index).toEqual(2);
});
it('should find the highest matching segment marker mapping index if there is more than one of them',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 30};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const index = findLastMappingIndexBefore(mappings, marker3, /* exclusive */ false, 0);
expect(index).toEqual(3);
});
});
describe('[exact match exclusive]', () => {
it('should find the preceding mapping index if there is a matching segment marker', () => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const index = findLastMappingIndexBefore(mappings, marker3, /* exclusive */ true, 0);
expect(index).toEqual(1);
});
it('should find the highest preceding mapping index if there is more than one matching segment marker',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 30};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const index = findLastMappingIndexBefore(mappings, marker3, /* exclusive */ false, 0);
expect(index).toEqual(3);
});
});
describe('[with lowerIndex hint', () => {
it('should find the highest mapping index above the lowerIndex hint that has a segment marker below the given one if there is not an exact match',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 35};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 1);
expect(index).toEqual(2);
});
it('should return the lowerIndex mapping index if there is a single exact match and we are not exclusive',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2);
expect(index).toEqual(2);
});
it('should return the lowerIndex mapping index if there are multiple exact matches and we are not exclusive',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 30};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 3);
expect(index).toEqual(3);
});
it('should return -1 if the segment marker is lower than the lowerIndex hint', () => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 25};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ false, 2);
expect(index).toEqual(-1);
});
it('should return -1 if the segment marker is equal to the lowerIndex hint and we are exclusive',
() => {
const marker5: SegmentMarker = {line: 0, column: 50};
const marker4: SegmentMarker = {line: 0, column: 40};
const marker3: SegmentMarker = {line: 0, column: 30};
const marker2: SegmentMarker = {line: 0, column: 20};
const marker1: SegmentMarker = {line: 0, column: 10};
const mappings: Mapping[] = [marker1, marker2, marker3, marker4, marker5].map(
marker => ({ generatedSegment: marker } as Mapping));
const marker: SegmentMarker = {line: 0, column: 30};
const index = findLastMappingIndexBefore(mappings, marker, /* exclusive */ true, 2);
expect(index).toEqual(-1);
});
});
});
describe('SourceFile', () => {
describe('flattenedMappings', () => {
it('should be an empty array for source files with no source map', () => {