From 422cc0d96f4d1fd942e3835beb85d3acda904abf Mon Sep 17 00:00:00 2001 From: Milan Hauth Date: Fri, 25 Sep 2020 18:33:20 +0200 Subject: [PATCH] optimize: use mutable data, unswitch loops --- src/compiler/preprocess/index.ts | 17 +-- src/compiler/utils/string_with_sourcemap.ts | 157 ++++++++++++-------- 2 files changed, 99 insertions(+), 75 deletions(-) diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index 4a7578452dd7..d72eb73945bb 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -67,7 +67,7 @@ async function replace_async( ); return ''; }); - let out: StringWithSourcemap; + const out = new StringWithSourcemap(); let last_end = 0; for (const { offset, length, replacement } of await Promise.all( replacements @@ -75,15 +75,13 @@ async function replace_async( // content = source before replacement const content = StringWithSourcemap.from_source( filename, source.slice(last_end, offset), get_location(last_end)); - out = out ? out.concat(content) : content; - out = out.concat(replacement); + out.concat(content).concat(replacement); last_end = offset + length; } // final_content = source after last replacement const final_content = StringWithSourcemap.from_source( filename, source.slice(last_end), get_location(last_end)); - out = out.concat(final_content); - return out; + return out.concat(final_content); } function get_replacement( @@ -100,15 +98,14 @@ function get_replacement( const suffix_with_map = StringWithSourcemap.from_source( filename, suffix, get_location(offset + prefix.length + original.length)); - let processed_map_shifted; + let decoded_map; if (processed.map) { - const decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map; + decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map; if (typeof(decoded_map.mappings) === 'string') decoded_map.mappings = sourcemap_decode(decoded_map.mappings); - const processed_offset = get_location(offset + prefix.length); - processed_map_shifted = sourcemap_add_offset(decoded_map, processed_offset); + sourcemap_add_offset(decoded_map, get_location(offset + prefix.length)); } - const processed_with_map = StringWithSourcemap.from_processed(processed.code, processed_map_shifted); + const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map); return prefix_with_map.concat(processed_with_map).concat(suffix_with_map); } diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts index a8a978acdba7..a5a89eea158d 100644 --- a/src/compiler/utils/string_with_sourcemap.ts +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -4,6 +4,7 @@ type MappingSegment = | [number, number, number, number, number]; type SourceMappings = { + version: number; sources: string[]; names: string[]; mappings: MappingSegment[][]; @@ -18,30 +19,28 @@ function last_line_length(s: string) { return s.length - s.lastIndexOf('\n') - 1; } +// mutate map in-place export function sourcemap_add_offset( map: SourceMappings, offset: SourceLocation -): SourceMappings { - return { - sources: map.sources.slice(), - mappings: map.mappings.map((line, line_idx) => - line.map(seg => { - const new_seg = seg.slice() as MappingSegment; - if (seg.length >= 4) { - new_seg[2] = new_seg[2] + offset.line; - if (line_idx == 0) - new_seg[3] = new_seg[3] + offset.column; - } - return new_seg; - }) - ) - } as SourceMappings; +) { + // shift columns in first line + const m = map.mappings as any; + m[0].forEach(seg => { + if (seg[3]) seg[3] += offset.column; + }); + // shift lines + m.forEach(line => { + line.forEach(seg => { + if (seg[2]) seg[2] += offset.line; + }); + }); } -function merge_tables(this_table: T[], other_table): [T[], number[], boolean] { +function merge_tables(this_table: T[], other_table): [T[], number[], boolean, boolean] { const new_table = this_table.slice(); const idx_map = []; other_table = other_table || []; - let has_changed = false; + let val_changed = false; for (const [other_idx, other_val] of other_table.entries()) { const this_idx = this_table.indexOf(other_val); if (this_idx >= 0) { @@ -50,47 +49,85 @@ function merge_tables(this_table: T[], other_table): [T[], number[], boolean] const new_idx = new_table.length; new_table[new_idx] = other_val; idx_map[other_idx] = new_idx; - has_changed = true; + val_changed = true; } } - if (has_changed) { + let idx_changed = val_changed; + if (val_changed) { if (idx_map.find((val, idx) => val != idx) === undefined) { // idx_map is identity map [0, 1, 2, 3, 4, ....] - has_changed = false; + idx_changed = false; } } - return [new_table, idx_map, has_changed]; + return [new_table, idx_map, val_changed, idx_changed]; +} + +function pushArray(_this: T[], other: T[]) { + for (let i = 0; i < other.length; i++) + _this.push(other[i]); } export class StringWithSourcemap { - readonly string: string; - readonly map: SourceMappings; + string: string; + map: SourceMappings; - constructor(string: string, map: SourceMappings) { + constructor(string = '', map = null) { this.string = string; - this.map = map; + if (map) + this.map = map as SourceMappings; + else + this.map = { + version: 3, + mappings: [], + sources: [], + names: [] + }; } + // concat in-place (mutable), return this (chainable) + // will also mutate the `other` object concat(other: StringWithSourcemap): StringWithSourcemap { // noop: if one is empty, return the other - if (this.string == '') return other; if (other.string == '') return this; + if (this.string == '') { + this.string = other.string; + this.map = other.map; + return this; + } + + this.string += other.string; + + const m1 = this.map as any; + const m2 = other.map as any; // combine sources and names - const [sources, new_source_idx, sources_changed] = merge_tables(this.map.sources, other.map.sources); - const [names, new_name_idx, names_changed] = merge_tables(this.map.names, other.map.names); - - // update source refs and name refs - const other_mappings = - (sources_changed || names_changed) - ? other.map.mappings.slice().map(line => - line.map(seg => { - if (seg[1]) seg[1] = new_source_idx[seg[1]]; - if (seg[4]) seg[4] = new_name_idx[seg[4]]; - return seg; - }) - ) - : other.map.mappings; + const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources); + const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names); + + if (sources_changed) m1.sources = sources; + if (names_changed) m1.names = names; + + // unswitched loops are faster + if (sources_idx_changed && names_idx_changed) { + m2.forEach(line => { + line.forEach(seg => { + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + }); + }); + } else if (sources_idx_changed) { + m2.forEach(line => { + line.forEach(seg => { + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + }); + }); + } else if (names_idx_changed) { + m2.forEach(line => { + line.forEach(seg => { + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + }); + }); + } // combine the mappings @@ -100,35 +137,25 @@ export class StringWithSourcemap { // columns of 2 must be shifted const column_offset = last_line_length(this.string); + if (m2.length > 0 && column_offset > 0) { + // shift columns in first line + m2[0].forEach(seg => { + seg[0] += column_offset; + }); + } + + // combine last line + first line + pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift()); + + // append other lines + pushArray(m1.mappings, m2.mappings); - const first_line: MappingSegment[] = - other_mappings.length == 0 - ? [] - : column_offset == 0 - ? other_mappings[0].slice() as MappingSegment[] - : other_mappings[0].slice().map(seg => { - // shift column - seg[0] += column_offset; - return seg; - }); - - const mappings: MappingSegment[][] = - this.map.mappings.slice(0, -1) - .concat([ - this.map.mappings.slice(-1)[0] // last line - .concat(first_line) - ]) - .concat(other_mappings.slice(1) as MappingSegment[][]); - - return new StringWithSourcemap( - this.string + other.string, - { sources, names, mappings } - ); + return this; } static from_processed(string: string, map?: SourceMappings): StringWithSourcemap { if (map) return new StringWithSourcemap(string, map); - map = { names: [], sources: [], mappings: [] }; + map = { version: 3, names: [], sources: [], mappings: [] }; if (string == '') return new StringWithSourcemap(string, map); // add empty MappingSegment[] for every line const lineCount = string.split('\n').length; @@ -140,7 +167,7 @@ export class StringWithSourcemap { source_file: string, source: string, offset_in_source?: SourceLocation ): StringWithSourcemap { const offset = offset_in_source || { line: 0, column: 0 }; - const map: SourceMappings = { names: [], sources: [source_file], mappings: [] }; + const map: SourceMappings = { version: 3, names: [], sources: [source_file], mappings: [] }; if (source.length == 0) return new StringWithSourcemap(source, map); // we create a high resolution identity map here,