æ°é±éåã ãRustãšWebAssemblyã§ãœãŒã¹ããããé
žåããããšããæçš¿ãçºèŠããŸããã
Twitterã§åºãã source-map
ã©ã€ãã©ãªã®éåžžã®JavaScriptãWebAssemblyã§ã³ã³ãã€ã«ãããRustã«çœ®ãæããããšã«ããããã©ãŒãã³ã¹ã®åäžã«ã€ããŠèª¬æããŸã
ãã®æçš¿ã¯ãç§ãRustãWASMã®å€§ãã¡ã³ã ã£ãããã§ã¯ãªããåæ§ã®ããã©ãŒãã³ã¹ãéæããããã«Javascriptã«æ¬ ããŠããèšèªæ©èœãšæé©åã«åžžã«é¢å¿ããã£ãããã«èå³ãããããŸããã
ããã§ãGitHubããã©ã€ãã©ãªãããŠã³ããŒãããå°ãããã©ãŒãã³ã¹ã¹ã¿ãã£ãè¡ããŸãããããã«ã€ããŠã¯ãããã§ã»ãŒéèªçã«èª¬æããŸãã
å
容
ã³ãŒãæ€çŽ¢
ç§ã®ç ç©¶ã§ã¯ã1æ20æ¥ã®ã³ããã69abb960c97606df99408e6869d66e014aa0fb51ã®ã»ãŒæšæºã®x64.releaseãã«ãV8ã䜿çšããŠããŸãã æšæºæ§æãšã®ç§ã®äžäžèŽã¯ãå¿
èŠã«å¿ããŠãçæããããã·ã³ã³ãŒããããæ·±ãæãäžããããã«ãGNãã©ã°ãä»ããŠéã¢ã»ã³ãã©ããªã³ã«ããããšã ãã§ãã
ââ ~/src/v8/v8 â¹master⺠â°â$ gn args out.gn/x64.release --list --short --overrides-only is_debug = false target_cpu = "x64" use_goma = true v8_enable_disassembler = true
次ã«ã source-map
ã¢ãžã¥ãŒã«ã®source-map
ããååŸããŸããïŒ
çŽç²ãªJavaScriptã§ããŒãžã§ã³ããããã¡ã€ã«ããŸã
ã¯ãªãŒã³ãªJSããŒãžã§ã³ã®ãã³ãããŒã¯ãå®è¡ããã®ã¯ç°¡åã§ããïŒ
ââ ~/src/source-map/bench â¹ c97d38b⺠â°â$ d8 bench-shell-bindings.js Parsing source map console.timeEnd: iteration, 4655.638000 console.timeEnd: iteration, 4751.122000 console.timeEnd: iteration, 4820.566000 console.timeEnd: iteration, 4996.942000 console.timeEnd: iteration, 4644.619000 [Stats samples: 5, total: 23868 ms, mean: 4773.6 ms, stddev: 161.22112144505135 ms]
æåã«ããããšã¯ããã³ãããŒã¯ã®ã·ãªã¢ã«åéšåããªãã«ããããšã§ããã
diff --git a/bench/bench-shell-bindings.js b/bench/bench-shell-bindings.js index 811df40..c97d38b 100644 --- a/bench/bench-shell-bindings.js +++ b/bench/bench-shell-bindings.js @@ -19,5 +19,5 @@ load("./bench.js"); print("Parsing source map"); print(benchmarkParseSourceMap()); print(); -print("Serializing source map"); -print(benchmarkSerializeSourceMap()); +// print("Serializing source map"); +// print(benchmarkSerializeSourceMap());
次ã«ããããLinux perf
ãããã¡ã€ã©ãŒã«æã蟌ã¿ãŸããã
ââ ~/src/source-map/bench â¹perf-work⺠â°â$ perf record -g d8 --perf-basic-prof bench-shell-bindings.js Parsing source map console.timeEnd: iteration, 4984.464000 ^C[ perf record: Woken up 90 times to write data ] [ perf record: Captured and wrote 24.659 MB perf.data (~1077375 samples) ]
--perf-basic-prof
ãã©ã°ãd8
ãã€ããªã«æž¡ããšãV8ããããã³ã°/tmp/perf-$pid.map
ãããã³ã°ãã¡ã€ã«/tmp/perf-$pid.map
ãçæããããšã«/tmp/perf-$pid.map
ã ãã®ãã¡ã€ã«ã«ããã perf report
ã¯JITçæã®ãã·ã³ã³ãŒããçè§£ã§ããŸãã
ããã«ãå®è¡ã®ã¡ã€ã³ã¹ã¬ããã衚瀺ããåŸã®perf report --no-children
ããåŸããã®ã瀺ããŸãã
Overhead Symbol 17.02% *doQuickSort ../dist/source-map.js:2752 11.20% Builtin:ArgumentsAdaptorTrampoline 7.17% *compareByOriginalPositions ../dist/source-map.js:1024 4.49% Builtin:CallFunction_ReceiverIsNullOrUndefined 3.58% *compareByGeneratedPositionsDeflated ../dist/source-map.js:1063 2.73% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 2.11% Builtin:StringEqual 1.93% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.66% *doQuickSort ../dist/source-map.js:2752 1.25% v8::internal::StringTable::LookupStringIfExists_NoAllocate 1.22% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.21% Builtin:StringCharAt 1.16% Builtin:Call_ReceiverIsNullOrUndefined 1.14% v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch 0.90% Builtin:StringPrototypeSlice 0.86% Builtin:KeyedLoadIC_Megamorphic 0.82% v8::internal::(anonymous namespace)::MakeStringThin 0.80% v8::internal::(anonymous namespace)::CopyObjectToObjectElements 0.76% v8::internal::Scavenger::ScavengeObject 0.72% v8::internal::String::VisitFlat<v8::internal::IteratingStringHasher> 0.68% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 0.64% *doQuickSort ../dist/source-map.js:2752 0.56% v8::internal::IncrementalMarking::RecordWriteSlow
å®éã ãOxidizing Source Maps ...ãã§è¿°ã¹ãããã«ããã®ãã³ãããŒã¯ã¯åºæ¬çã«ãœãŒããããŒãããŸãdoQuickSort
颿°ã¯ãããã¡ã€ã«ã®äžéšã«è¡šç€ºããããªã¹ãã®æ°åäžã«è¡šç€ºãããŸãïŒã€ãŸããæ°åæé©åããã³æé©åè§£é€ãããŸããïŒ ã
äžŠã¹æ¿ãã®æé©å-åŒæ°ã®èª¿æŽ
ãããã¡ã€ã©ãŒã§éç«ã£ãŠããç¹ã®1ã€ã¯ãçããããšã³ããªãã€ãŸãBuiltin:ArgumentsAdaptorTrampoline
ãšBuiltin:CallFunction_ReceiverIsNullOrUndefined
ã§ãããããã¯V8å®è£
ã®äžéšã®ããã§ãã perf report
ã«ããããã«ã€ãªããåŒã³åºãã®ãã§ãŒã³ãæããã«ããããã«äŸé Œãããšããããã®é¢æ°ã¯äž»ã«ãœãŒãã³ãŒãããåŒã³åºãããããšãããããŸãã
- Builtin:ArgumentsAdaptorTrampoline + 96.87% *doQuickSort ../dist/source-map.js:2752 + 1.22% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 0.68% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 0.68% Builtin:InterpreterEntryTrampoline + 0.55% *doQuickSort ../dist/source-map.js:2752 - Builtin:CallFunction_ReceiverIsNullOrUndefined + 93.88% *doQuickSort ../dist/source-map.js:2752 + 2.24% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 2.01% Builtin:InterpreterEntryTrampoline + 1.49% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894
ãããŠãããã§ã³ãŒããèŠãŠã¿ãŸãããã ã¯ã€ãã¯ãœãŒãã®å®è£
ã¯lib/quick-sort.js
ããã lib/source-map-consumer.js
ã®ããŒãµãŒã³ãŒãããåŒã³åºãããŸãã
ãœãŒãã«äœ¿çšãããæ¯èŒé¢æ°ïŒã³ã³ãã¬ãŒã¿ïŒã¯ã compareByGeneratedPositionsDeflated
ããã³compareByOriginalPositions
ã§ãã
ãããã®æ¯èŒé¢æ°ã®å®çŸ©ãšã¯ã€ãã¯ãœãŒãå®è£
ã§ã®åŒã³åºãæ¹æ³ãèŠããšãåŒã³åºãã®å Žæã«äžäžèŽã®ã¢ãªãã£ãããããšãããããŸãã
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { // ... } function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { // ... } function doQuickSort(ary, comparator, p, r) { // ... if (comparator(ary[j], pivot) <= 0) { // ... } // ... }
ã©ã€ãã©ãªãœãŒã¹ãquickSort
ãšããã¹ã以å€ã§ã¯ãããã2ã€ã®é¢æ°ã§ã®ã¿quickSort
ãåŒã³åºãããŠããããšãquickSort
ãŸãã
ããããã¢ãªãã£ãä¿®æ£ãããã©ããªãã§ããããïŒ
diff --git a/dist/source-map.js b/dist/source-map.js index ade5bb2..2d39b28 100644 --- a/dist/source-map.js +++ b/dist/source-map.js @@ -2779,7 +2779,7 @@ return /* **** */ (function(modules) { // webpackBootstrap // // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. for (var j = p; j < r; j++) { - if (comparator(ary[j], pivot) <= 0) { + if (comparator(ary[j], pivot, false) <= 0) { i += 1; swap(ary, i, j); }
[泚ïŒã¢ã»ã³ããªããã»ã¹ã«æéããããããªãããã dist/source-map.js
çŽæ¥ç·šéããŸã]
ââ ~/src/source-map/bench â¹perf-work⺠[Fix comparator invocation arity] â°â$ d8 bench-shell-bindings.js Parsing source map console.timeEnd: iteration, 4037.084000 console.timeEnd: iteration, 4249.258000 console.timeEnd: iteration, 4241.165000 console.timeEnd: iteration, 3936.664000 console.timeEnd: iteration, 4131.844000 console.timeEnd: iteration, 4140.963000 [Stats samples: 6, total: 24737 ms, mean: 4122.833333333333 ms, stddev: 132.18789657150916 ms]
ã¢ãªãã£ã®äžäžèŽãç°¡åã«ä¿®æ£ããããšã§ããã³ãããŒã¯ã®V8å€ã4774ããªç§ãã4123ããªç§ã«14ïŒ
æ¹åããŸããã ãã³ãããŒã¯ãå床ãããã¡ã€ã«ãããšã ArgumentsAdaptorTrampoline
ãããããå®å
šã«æ¶ããŠããããšãããããŸãã ãªã圌ã¯åããŠããã«ããã®ã§ããïŒ
ArgumentsAdaptorTrampoline
ã¯ãjavascriptåŒã³åºãã®å¯å€æ§ããµããŒãããããã®V8ã¡ã«ããºã ã§ããããšãããããŸãã2ã€ã ãã§3ã€ã®åŒæ°ãæã€é¢æ°ãåŒã³åºãããšãã§ããŸãããã®å Žåã3çªç®ã®åŒæ°ã«ã¯undefined
å€ãå
¥åãããŸãã V8ã¯ãã¹ã¿ãã¯äžã«æ°ãããã¬ãŒã ãäœæããããã«åŒæ°ãã³ããŒããŠãã¿ãŒã²ãã颿°ãåŒã³åºãããšã§ãããè¡ããŸãã

[ ããã©ãŒãã³ã¹ã¹ã¿ãã¯ã«ã€ããŠèããããšããªãå Žåã¯ã ãŠã£ãããã£ã¢ãšãã©ã³ã·ã¹ã³ãã³ã±ã«ãã³ã®æçš¿ãã芧ãã ããã]
ã³ãŒã«ãã³ãŒãã§ã¯ãã®ãããªã³ã¹ãã¯ç¡èŠã§ããŸãããããã§ã¯ããã³ãããŒã¯ã®èµ·åæã«comparator
ãäœçŸäžåãåŒã³åºããããããåŒæ°ãé©å¿ãããããã®è¿œå ã³ã¹ããçºçããŸããã
æ³šææ·±ãèªè
ã¯ãæé»ã®undefined
以åã«äœ¿çšãããå Žæã«false
ãæç€ºçã«æž¡ãããšã«æ°ä»ããããããŸããã ãããããã©ãŒãã³ã¹ã®åäžã«è²¢ç®ããŠããããã§ãã false
ãvoid 0
眮ãæãããšããããã«æªãå€ãåŸãããŸãã
diff --git a/dist/source-map.js b/dist/source-map.js index 2d39b28..243b2ef 100644 --- a/dist/source-map.js +++ b/dist/source-map.js @@ -2779,7 +2779,7 @@ return /* **** */ (function(modules) { // webpackBootstrap // // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. for (var j = p; j < r; j++) { - if (comparator(ary[j], pivot, false) <= 0) { + if (comparator(ary[j], pivot, void 0) <= 0) { i += 1; swap(ary, i, j); }
ââ ~/src/source-map/bench â¹perf-work U⺠[Fix comparator invocation arity] â°â$ ~/src/v8/v8/out.gn/x64.release/d8 bench-shell-bindings.js Parsing source map console.timeEnd: iteration, 4215.623000 console.timeEnd: iteration, 4247.643000 console.timeEnd: iteration, 4425.871000 console.timeEnd: iteration, 4167.691000 console.timeEnd: iteration, 4343.613000 console.timeEnd: iteration, 4209.427000 [Stats samples: 6, total: 25610 ms, mean: 4268.333333333333 ms, stddev: 106.38947316346669 ms]
ãããããããªãããè°è«ã®é©å¿ã¯V8ã«éåžžã«ç¹æã®ããã ã SpiderMonkeyã®ãã³ãããŒã¯ãéå§ãããšããäžèŽããã¢ãªãã£ããã®èããããã©ãŒãã³ã¹ã®åäžã¯èŠãããŸããã§ããã
ââ ~/src/source-map/bench â¹ d052ea4⺠[Disabled serialization part of the benchmark] â°â$ sm bench-shell-bindings.js Parsing source map [Stats samples: 8, total: 24751 ms, mean: 3093.875 ms, stddev: 327.27966571700836 ms] ââ ~/src/source-map/bench â¹perf-work⺠[Fix comparator invocation arity] â°â$ sm bench-shell-bindings.js Parsing source map [Stats samples: 8, total: 25397 ms, mean: 3174.625 ms, stddev: 360.4636187025859 ms]
[ jsvuããŒã«ã® Matthias Byensã®ãããã§ãSpiderMonkeyã·ã§ã«ã®ã€ã³ã¹ããŒã«ã¯éåžžã«ç°¡åã«ãªããŸãã]
ãœãŒãã³ãŒãã«æ»ããŸãããã ãã³ãããŒã¯ã®ãããã¡ã€ã«ãååºŠäœæãããšã ArgumentsAdaptorTrampoline
ãããã¡ã€ã«ããæ¶ããããšãCallFunction_ReceiverIsNullOrUndefined
ãŸããã CallFunction_ReceiverIsNullOrUndefined
ã¯ãŸã ããã«ãããŸãã ããã¯é©ãããšã§ã¯ãããŸããããªããªããç§ãã¡ã¯ãŸã comparator
åŒã³åºããŠããããã§ãã
äžŠã¹æ¿ãã®æé©å-åçžå
éåžžãäœã颿°ãåŒã³åºããããéãå®è¡ãããŸããïŒ åœŒã®äžåšïŒ
ããã§ã®æãããªãªãã·ã§ã³ã¯ã doQuickSort
æ¯èŒé¢æ°ãåã蟌ãããšdoQuickSort
ã ãã ãã doQuickSort
ãç°ãªã颿°ã§åŒã³åºããããšããäºå®ã«çŽé¢ããŠããŸãã
ãããåé¿ããããã«ãã¯ããŒãã³ã°ã«ãã£ãŠdoQuickSort
ãådoQuickSort
ããããšããŸãã æ¹æ³ã¯æ¬¡ã®ãšããã§ãã
doQuickSort
ã doQuickSort
ããã³ãã®ä»ã®ãã«ããŒãŠãŒãã£ãªãã£ãSortTemplate
颿°ã§ã©ããããããšããå§ããŸãããã
function SortTemplate(comparator) { function swap(ary, x, y) { // ... } function randomIntInRange(low, high) { // ... } function doQuickSort(ary, p, r) { // ... } return doQuickSort; }
次ã«ã SortTemplate
ãæååã«å€æãã Function
ã³ã³ã¹ãã©ã¯ã¿ãŒãä»ããŠFunction
ã«æž¡ããŠè§£æããããšã«ãããäžŠã¹æ¿ãæé ã®ã¯ããŒã³ãäœæã§ããŸãã
function cloneSort(comparator) { let template = SortTemplate.toString(); let templateFn = new Function(`return ${template}`)(); return templateFn(comparator);
cloneSort
ã䜿çšããŠã䜿çšããåã³ã³ãã¬ãŒã¿ã®ãœãŒã颿°ãäœæã§ããŸãã
let sortCache = new WeakMap(); // Cache for specialized sorts. exports.quickSort = function (ary, comparator) { let doQuickSort = sortCache.get(comparator); if (doQuickSort === void 0) { doQuickSort = cloneSort(comparator); sortCache.set(comparator, doQuickSort); } doQuickSort(ary, 0, ary.length - 1); };
ãã³ãããŒã¯ãåèµ·åãããšã次ã®ããšãããããŸãã
ââ ~/src/source-map/bench â¹perf-work⺠[Clone sorting functions for each comparator] â°â$ d8 bench-shell-bindings.js Parsing source map console.timeEnd: iteration, 2955.199000 console.timeEnd: iteration, 3084.979000 console.timeEnd: iteration, 3193.134000 console.timeEnd: iteration, 3480.459000 console.timeEnd: iteration, 3115.011000 console.timeEnd: iteration, 3216.344000 console.timeEnd: iteration, 3343.459000 console.timeEnd: iteration, 3036.211000 [Stats samples: 8, total: 25423 ms, mean: 3177.875 ms, stddev: 181.87633161024556 ms]
å¹³åæéã4268ããªç§ãã3177ããªç§ã«æžå°ããããšãããããŸãïŒ25ïŒ
æ¹åïŒã
ãããã¡ã€ãªã³ã°ã¯æ¬¡ã®å³ã瀺ããŸãã
Overhead Symbol 14.95% *doQuickSort :44 11.49% *doQuickSort :44 3.29% Builtin:StringEqual 3.13% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.86% v8::internal::StringTable::LookupStringIfExists_NoAllocate 1.86% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.72% Builtin:StringCharAt 1.67% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.61% v8::internal::Scavenger::ScavengeObject 1.45% v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch 1.23% Builtin:StringPrototypeSlice 1.17% v8::internal::(anonymous namespace)::MakeStringThin 1.08% Builtin:KeyedLoadIC_Megamorphic 1.05% v8::internal::(anonymous namespace)::CopyObjectToObjectElements 0.99% v8::internal::String::VisitFlat<v8::internal::IteratingStringHasher> 0.86% clear_page_c_e 0.77% v8::internal::IncrementalMarking::RecordWriteSlow 0.48% Builtin:MathRandom 0.41% Builtin:RecordWrite 0.39% Builtin:KeyedLoadIC
comparator
åŒã³åºãã«é¢é£ãããªãŒããŒãããã¯ããããã¡ã€ã«ããå®å
šã«æ¶ããŸããã
ãã®ç¬éãç§ã¯ãããã³ã°ã®ãœãŒãã«æ¯ã¹ãŠããããã³ã°ã®è§£æã«ã©ãã ãæéãããããã«èå³ãæã¡ãŸããã è§£æã³ãŒãã«è¡ãã Date.now()
ããã€ãã®åŒã³åºãã远å ããŸããïŒ
[ performance.now()
ã远å ãããã®ã§ãããSpiderMonkeyã·ã§ã«ã¯ããããµããŒãããŠããŸããã]
diff --git a/dist/source-map.js b/dist/source-map.js index 75ebbdf..7312058 100644 --- a/dist/source-map.js +++ b/dist/source-map.js @@ -1906,6 +1906,8 @@ return /* **** */ (function(modules) { // webpackBootstrap var generatedMappings = []; var mapping, str, segment, end, value; + + var startParsing = Date.now(); while (index < length) { if (aStr.charAt(index)
çµæã¯æ¬¡ã®ãšããã§ãã
ââ ~/src/source-map/bench â¹perf-work U⺠[Clone sorting functions for each comparator] â°â$ d8 bench-shell-bindings.js Parsing source map parse: 1911.846 sortGenerated: 619.5990000000002 sortOriginal: 905.8220000000001 parse: 1965.4820000000004 sortGenerated: 602.1939999999995 sortOriginal: 896.3589999999995 ^C
ãããã£ãŠãè§£æãšãœãŒãã®æéã¯ãV8ãšSpiderMonkeyã§ãã³ãããŒã¯èµ·åã®åå埩ã«ã€ããŠèª¿ã¹ãŸãã

V8ã§ã¯ããããã³ã°ã®ãœãŒããšã»ãŒåãæéããããã³ã°ã®åæã«è²»ããããã§ãã SpiderMonkeyã§ã¯ãè§£æã¯ã¯ããã«é«éã§ãããäžŠã¹æ¿ããé
ããªããŸãã ããã«ãããè§£æã³ãŒãã確èªã§ããŸããã
è§£æã®æé©å-ã»ã°ã¡ã³ããã£ãã·ã¥ã®åé€
ããäžåºŠãããã¡ã€ã«ãèŠãŠã¿ãŸãããã
Overhead Symbol 18.23% *doQuickSort :44 12.36% *doQuickSort :44 3.84% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 3.07% Builtin:StringEqual 1.92% v8::internal::StringTable::LookupStringIfExists_NoAllocate 1.85% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.59% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 1.54% Builtin:StringCharAt 1.52% v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch 1.38% v8::internal::Scavenger::ScavengeObject 1.27% Builtin:KeyedLoadIC_Megamorphic 1.22% Builtin:StringPrototypeSlice 1.10% v8::internal::(anonymous namespace)::MakeStringThin 1.05% v8::internal::(anonymous namespace)::CopyObjectToObjectElements 1.03% v8::internal::String::VisitFlat<v8::internal::IteratingStringHasher> 0.88% clear_page_c_e 0.51% Builtin:MathRandom 0.48% Builtin:KeyedLoadIC 0.46% v8::internal::IteratingStringHasher::Hash 0.41% Builtin:RecordWrite
æ¢ã«ããã£ãŠããJavaScriptã³ãŒããåé€ãããšã次ã®ããã«ãªããŸãã
Overhead Symbol 3.07% Builtin:StringEqual 1.92% v8::internal::StringTable::LookupStringIfExists_NoAllocate 1.54% Builtin:StringCharAt 1.52% v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch 1.38% v8::internal::Scavenger::ScavengeObject 1.27% Builtin:KeyedLoadIC_Megamorphic 1.22% Builtin:StringPrototypeSlice 1.10% v8::internal::(anonymous namespace)::MakeStringThin 1.05% v8::internal::(anonymous namespace)::CopyObjectToObjectElements 1.03% v8::internal::String::VisitFlat<v8::internal::IteratingStringHasher> 0.88% clear_page_c_e 0.51% Builtin:MathRandom 0.48% Builtin:KeyedLoadIC 0.46% v8::internal::IteratingStringHasher::Hash 0.41% Builtin:RecordWrite
åã
ã®ã¬ã³ãŒãã®ã³ãŒã«ãã§ãŒã³ã調ã¹å§ãããšãããããã®å€ããKeyedLoadIC_Megamorphic
ãééããããšãKeyedLoadIC_Megamorphic
ãŸããã
- 1.92% v8::internal::StringTable::LookupStringIfExists_NoAllocate - v8::internal::StringTable::LookupStringIfExists_NoAllocate + 99.80% Builtin:KeyedLoadIC_Megamorphic - 1.52% v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch - v8::internal::(anonymous namespace)::StringTableNoAllocateKey::IsMatch - 98.32% v8::internal::StringTable::LookupStringIfExists_NoAllocate + Builtin:KeyedLoadIC_Megamorphic + 1.68% Builtin:KeyedLoadIC_Megamorphic - 1.27% Builtin:KeyedLoadIC_Megamorphic - Builtin:KeyedLoadIC_Megamorphic + 57.65% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 22.62% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 15.91% *SourceMapConsumer_parseMappings ../dist/source-map.js:1894 + 2.46% Builtin:InterpreterEntryTrampoline + 0.61% BytecodeHandler:Mul + 0.57% *doQuickSort :44 - 1.10% v8::internal::(anonymous namespace)::MakeStringThin - v8::internal::(anonymous namespace)::MakeStringThin - 94.72% v8::internal::StringTable::LookupStringIfExists_NoAllocate + Builtin:KeyedLoadIC_Megamorphic + 3.63% Builtin:KeyedLoadIC_Megamorphic + 1.66% v8::internal::StringTable::LookupString
ãã®ãããªåŒã³åºãã¹ã¿ãã¯ã®ãœãŒãã¯ãã³ãŒããobj[key]
圢åŒã§å€ãã®ããŒãããã³ã°ãå®è¡ããããšãéç¥ããŸãããããã§ã key
ã¯åçã«çæãããæååã§ãã ãœãŒã¹ãèŠããšã 次ã®ã³ãŒããèŠã€ãããŸããã
// Because each offset is encoded relative to the previous one, // many segments often have the same encoding. We can exploit this // fact by caching the parsed variable length fields of each segment, // allowing us to avoid a second parse if we encounter the same // segment again. for (end = index; end < length; end++) { if (this._charIsMappingSeparator(aStr, end)) { break; } } str = aStr.slice(index, end); segment = cachedSegments[str]; if (segment) { index += str.length; } else { segment = []; while (index < end) { base64VLQ.decode(aStr, index, temp); value = temp.value; index = temp.rest; segment.push(value); } // ... cachedSegments[str] = segment; }
ãã®ã³ãŒãã¯ãBase64 VLQãšã³ã³ãŒãã·ãŒã±ã³ã¹ã®ãã³ãŒããæ
åœããŸããã€ãŸããã©ã€ã³A
ã¯[0]
ãšããŠãã³ãŒãããã UAAAA
[10,0,0,0,0]
ãã³ãŒããããŸãã ãšã³ã³ãŒãããã»ã¹èªäœãããããçè§£ãããå Žåã¯ããœãŒã¹ãããã®å
éšã«ã€ããŠãã®æçš¿ãåç
§ããããšããå§ãããŸãã
ãã®ã³ãŒãã¯ãåã·ãŒã±ã³ã¹ãåå¥ã«ãã³ãŒããã代ããã«ã埩å·åãããã»ã°ã¡ã³ãããã£ãã·ã¥ããããšããŸãïŒåºåãæåïŒ ,
ãŸãã¯;
ïŒã楜ãã¿ãçŸåšã®äœçœ®ããåºåãæåãŸã§éšåæååãæœåºãããã£ãã·ã¥ã«ãã®ãããªã»ã°ã¡ã³ãããããã©ããã確èªããŸã-ãããŠãããå Žåããã£ãã·ã¥ãããã»ã°ã¡ã³ããè¿ããŸãããã以å€ã®å Žåã¯ãè§£æããŠãã£ãã·ã¥ã«å
¥ããŸãã
ãã£ãã·ã³ã°ïŒ ã¡ã¢åã§ããããŸã ïŒã¯éåžžã«åŒ·åãªæé©åææ³ã§ãããã ãããã£ãã·ã¥èªäœãåŠçããããã§çµæãèŠã€ããããšããããèªäœãåèšç®ãããããå®äŸ¡ã§ããå Žåã«ã®ã¿æå³ããããŸãã
æœè±¡åæ
ããã2ã€ã®æäœãæœè±¡çã«æ¯èŒããŠã¿ãŸãããã
äžæ¹ã§ãã¯ãªãŒã³ãªåæïŒ
ã»ã°ã¡ã³ããè§£æããŠãåæåã1å調ã¹ãŸãã åæåã«ã€ããŠãbase64æåãæŽæ°å€ã«å€æããããã«ãããã€ãã®æ¯èŒããã³ç®è¡æŒç®ãå®è¡ãããŸãã æ¬¡ã«ãããã€ãã®ãããæŒç®ãå®è¡ããããããã®æ°å€ã1ã€ã®å€§ããªæ°å€ã«çµåãããŸãã æ¬¡ã«ããã³ãŒããããå€ãé
åã«æ ŒçŽãããã»ã°ã¡ã³ãã®æ¬¡ã®éšåã«é²ã¿ãŸãã ã»ã°ã¡ã³ãã¯5ã€ã®èŠçŽ ã«å¶éãããŠããŸãã
äžæ¹ããã£ãã·ã¥ïŒ
- ãã£ãã·ã¥ãããå€ãèŠã€ããããã«ãã»ã°ã¡ã³ãã®ãã¹ãŠã®æåãäžåšããŠãã®çµãããèŠã€ããŸãã
- JS VMã§ã®æååã®å®è£
æ¹æ³ã«å¿ããŠãé
眮ããå Žåã«ãã£ãŠã¯ã³ããŒããå¿
èŠãããéšåæååãæœåºããŸãã
- ãã®è¡ãèŸæžã®ããŒãšããŠäœ¿çšããŸãïŒ
- ãŸããVMããã®æååã®ããã·ã¥ãèšç®ããå¿
èŠãããïŒå床枡ããšãæåã«å¯ŸããŠããŸããŸãªãããåäœã®æäœãå®è¡ããŸãïŒãæååãå
éšåããå¿
èŠããããŸãïŒå®è£
ã«ãã£ãŠç°ãªããŸãïŒã
- 次ã«ãVMã¯ããŒãã«ã§ããã·ã¥ãããã³ã°ãå®è¡ããå¿
èŠããããŸããããã«ã¯ãå€ã«åºã¥ããŠããŒã«ã¢ã¯ã»ã¹ããä»ã®ããŒãšæ¯èŒããå¿
èŠããããŸãïŒåã
ã®æåãå床衚瀺ããå¿
èŠãããå ŽåããããŸãïŒã
äžè¬ã«ãçŽæ¥è§£æãé«éã«ãªãããã«èŠããŸããããã¯ãJS VMãåå¥ã®ç®è¡æŒç®ãšãããæŒç®ã§ããŸãæ©èœããããšãæå³ããŸãããã£ãã·ã¥ã«ããããããã©ããã倿ããã ãã§ãã
ãããã¡ã€ãªã³ã°ãããã確èªããŠããããã§ãïŒ KeyedLoadIC_Megamorphic
ãäžèšã®ã³ãŒãã®cachedSegments[str]
ããã«ãV8ã§ããŒã¢ã¯ã»ã¹ãå®è£
ããããã«äœ¿çšããcachedSegments[str]
ã
ãããã®èгå¯ã«åºã¥ããŠãããã€ãã®å®éšãè¡ããŸããã æåã«ãè§£æã®çµäºæã«cachedSegments
ãã£ãã·ã¥ã®å€§ããã確èªããŸããã å°ããã»ã©ãããå¹ççãªãã£ãã·ã¥ãå¯èœã«ãªããŸãã
ããã¯éåžžã«åŒ·ãæé·ããããšãããããŸãïŒ
Object.keys(cachedSegments).length = 155478
å¥åã®ãã€ã¯ããã³ãããŒã¯
ä»ãç§ã¯å°ããªå¥åã®ãã³ãããŒã¯ãæžãããšã«ããŸããïŒ
// [n] , [v], // .. 0, v, 2*v, ... , 1, 1 + v, 1 + 2*v, ... // [base] - // // // : [v], [cachedSegments] function makeString(n, v, base) { var arr = []; for (var i = 0; i < n; i++) { arr.push([0, base + (i % v), 0, 0].map(base64VLQ.encode).join('')); } return arr.join(';') + ';'; } // [f] [str]. function bench(f, str) { for (var i = 0; i < 1000; i++) { f(str); } } // [f] [str]. // [v] function measure(v, str, f) { var start = Date.now(); bench(f, str); var end = Date.now(); report(`${v}, ${f.name}, ${(end - start).toFixed(2)}`); } async function measureAll() { for (let v = 1; v <= 256; v *= 2) { // 1000 [v] , // [cachedSegments] [v] . let str = makeString(1000, v, 1024 * 1024); let arr = encoder.encode(str); // 10 . for (var j = 0; j < 10; j++) { measure(j, i, str, decodeCached); measure(j, i, str, decodeNoCaching); measure(j, i, str, decodeNoCachingNoStrings); measure(j, i, arr, decodeNoCachingNoStringsPreEncoded); await nextTick(); } } } function nextTick() { return new Promise((resolve) => setTimeout(resolve)); }
Base64 VLQ, .
â decodeCached
, source-map
â :
function decodeCached(aStr) { var length = aStr.length; var cachedSegments = {}; var end, str, segment, value, temp = {value: 0, rest: 0}; const decode = base64VLQ.decode; var index = 0; while (index < length) { // Because each offset is encoded relative to the previous one, // many segments often have the same encoding. We can exploit this // fact by caching the parsed variable length fields of each segment, // allowing us to avoid a second parse if we encounter the same // segment again. for (end = index; end < length; end++) { if (_charIsMappingSeparator(aStr, end)) { break; } } str = aStr.slice(index, end); segment = cachedSegments[str]; if (segment) { index += str.length; } else { segment = []; while (index < end) { decode(aStr, index, temp); value = temp.value; index = temp.rest; segment.push(value); } if (segment.length === 2) { throw new Error('Found a source, but no line and column'); } if (segment.length === 3) { throw new Error('Found a source and line, but no column'); } cachedSegments[str] = segment; } index++; } }
â decodeNoCaching
. , decodeCached
, . . Array
Int32Array
segment
.
function decodeNoCaching(aStr) { var length = aStr.length; var cachedSegments = {}; var end, str, segment, temp = {value: 0, rest: 0}; const decode = base64VLQ.decode; var index = 0, value; var segment = new Int32Array(5); var segmentLength = 0; while (index < length) { segmentLength = 0; while (!_charIsMappingSeparator(aStr, index)) { decode(aStr, index, temp); value = temp.value; index = temp.rest; if (segmentLength >= 5) throw new Error('Too many segments'); segment[segmentLength++] = value; } if (segmentLength === 2) { throw new Error('Found a source, but no line and column'); } if (segmentLength === 3) { throw new Error('Found a source and line, but no column'); } index++; } }
, , decodeNoCachingNoString
JavaScript- , utf8- Uint8Array
. , , , JS VM . String.prototype.charCodeAt
, , JS VM.
: utf8 , . "" , , typed array â string â typed array . , source map array buffer , .
let encoder = new TextEncoder(); function decodeNoCachingNoString(aStr) { decodeNoCachingNoStringPreEncoded(encoder.encode(aStr)); } function decodeNoCachingNoStringPreEncoded(arr) { var length = arr.length; var cachedSegments = {}; var end, str, segment, temp = {value: 0, rest: 0}; const decode2 = base64VLQ.decode2; var index = 0, value; var segment = new Int32Array(5); var segmentLength = 0; while (index < length) { segmentLength = 0; while (arr[index] != 59 && arr[index] != 44) { decode2(arr, index, temp); value = temp.value; index = temp.rest; if (segmentLength < 5) { segment[segmentLength++] = value; } } if (segmentLength === 2) { throw new Error('Found a source, but no line and column'); } if (segmentLength === 3) { throw new Error('Found a source and line, but no column'); } index++; } }
, Chrome Dev
66.0.3343.3
(V8 6.6.189
) Firefox Nightly 60.0a1 (2018-02-11)
:

:
- , , V8, SpiderMonkey. â ;
- SpiderMonkey , , V8 â "--" (.. );
, V8 - charCodeAt
â , Crankshaft charCodeAt
, charCodeAt
, , , .
- V8 :
2018 , , charCodeAt
. Chrome Beta Chrome Dev.

, V8 : charCodeAt
6.5.254.21
6.6.189
. "no cache" "using array", , charCodeAt
V8 , Uint8Array
, . , V8.
, , . ãªããã , V8:
function foo(str, i) { return str.charCodeAt(i); } let str = "fisk"; foo(str, 0); foo(str, 0); foo(str, 0); %OptimizeFunctionOnNextCall(foo); foo(str, 0);
ââ ~/src/v8/v8 â¹master⺠â°â$ out.gn/x64.release/d8
, , V8 charCodeAt
. , , V8, , charCodeAt
.
, source-map
.

, , : .
â
, .
:
originalMappings
compareByOriginalPositions
;generatedMappings
compareByGeneratedPositionsDeflated
.
originalMappings
compareByOriginalPositions
.
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { var cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; } cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; } cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0 || onlyCompareOriginal) { return cmp; } cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; } cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; } return strcmp(mappingA.name, mappingB.name); }
, source
, . source
, . , originalMappings
, , originalMappings
: originalMappings[i]
i
. , originalMappings[i]
, , .
[ , â ]
:
if (typeof mapping.originalLine === 'number') { // originalMappings.push(mapping). // . let currentSource = mapping.source; while (originalMappings.length <= currentSource) { originalMappings.push(null); } if (originalMappings[currentSource] === null) { originalMappings[currentSource] = []; } originalMappings[currentSource].push(mapping); }
:
var startSortOriginal = Date.now(); // : // quickSort(originalMappings, util.compareByOriginalPositions); for (var i = 0; i < originalMappings.length; i++) { if (originalMappings[i] != null) { quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource); } } var endSortOriginal = Date.now();
compareByOriginalPositionsNoSource
compareByOriginalPositions
, source
â , originalMappings[i]
.

V8, SpiderMonkey, V8.
[ originalMappings
: originalMappings
, originalMappings[i]
. , .]
generatedMappings
generatedMappings
compareByGeneratedPositionsDeflated
.
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { var cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; } cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0 || onlyCompareGenerated) { return cmp; } cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; } cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; } cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; } return strcmp(mappingA.name, mappingB.name); }
generatedLine
. , , , , generatedMappings
.
, :
while (index < length) { if (aStr.charAt(index) === ';') { generatedLine++; // ... } else if (aStr.charAt(index) === ',') { // ... } else { mapping = new Mapping(); mapping.generatedLine = generatedLine; // ... } }
generatedLine
, , generatedLine
, , generatedMappings
generatedLine
, . . :
let subarrayStart = 0; while (index < length) { if (aStr.charAt(index) === ';') { generatedLine++; // ... // [subarrayStart, generatedMappings.length]. sortGenerated(generatedMappings, subarrayStart); subarrayStart = generatedMappings.length; } else if (aStr.charAt(index) === ',') { // ... } else { mapping = new Mapping(); mapping.generatedLine = generatedLine; // ... } } // sortGenerated(generatedMappings, subarrayStart);
, , VM Array.prototype.sort
.
[: , ⊠, . , generatedMappings
, , generatedMappings
, .]
const compareGenerated = util.compareByGeneratedPositionsDeflatedNoLine; function sortGenerated(array, start) { let l = array.length; let n = array.length - start; if (n <= 1) { return; } else if (n == 2) { let a = array[start]; let b = array[start + 1]; if (compareGenerated(a, b) > 0) { array[start] = b; array[start + 1] = a; } } else if (n < 20) { for (let i = start; i < l; i++) { for (let j = i; j > start; j--) { let a = array[j - 1]; let b = array[j]; if (compareGenerated(a, b) <= 0) { break; } array[j - 1] = b; array[j] = a; } } } else { quickSort(array, compareGenerated, start); } }
:

, â , generatedMappings
, . ( )

, .
- , ?
, : asm.js / WASM, Rust JavaScript .
â GC
Mapping
, (GC) â , â . .
[ , JavaScript- , . , , , , : , C++ :-(]
Mapping
, , .
function Mapping(memory) { this._memory = memory; this.pointer = 0; } Mapping.prototype = { get generatedLine () { return this._memory[this.pointer + 0]; }, get generatedColumn () { return this._memory[this.pointer + 1]; }, get source () { return this._memory[this.pointer + 2]; }, get originalLine () { return this._memory[this.pointer + 3]; }, get originalColumn () { return this._memory[this.pointer + 4]; }, get name () { return this._memory[this.pointer + 5]; }, set generatedLine (value) { this._memory[this.pointer + 0] = value; }, set generatedColumn (value) { this._memory[this.pointer + 1] = value; }, set source (value) { this._memory[this.pointer + 2] = value; }, set originalLine (value) { this._memory[this.pointer + 3] = value; }, set originalColumn (value) { this._memory[this.pointer + 4] = value; }, set name (value) { this._memory[this.pointer + 5] = value; }, };
, :
BasicSourceMapConsumer.prototype._parseMappings = function (aStr, aSourceRoot) { // 4 . // aStr this._memory = new Int32Array(1 * 1024 * 1024); this._allocationFinger = 0; let mapping = new Mapping(this._memory); // ... while (index < length) { if (aStr.charAt(index) === ';') { // , , // sortGenerated(this._memory, generatedMappings, previousGeneratedLineStart); } else { this._allocateMapping(mapping); // ... // "" . generatedMappings.push(mapping.pointer); if (segmentLength > 1) { // ... originalMappings[currentSource].push(mapping.pointer); } } } // ... for (var i = 0; i < originalMappings.length; i++) { if (originalMappings[i] != null) { quickSort(this._memory, originalMappings[i], util.compareByOriginalPositionsNoSource); } } }; BasicSourceMapConsumer.prototype._allocateMapping = function (mapping) { let start = this._allocationFinger; let end = start + 6; if (end > this._memory.length) { // Do we need to grow memory buffer? let memory = new Int32Array(this._memory.length * 2); memory.set(this._memory); this._memory = memory; } this._allocationFinger = end; let memory = this._memory; mapping._memory = memory; mapping.pointer = start; mapping.name = 0x7fffffff; // Instead of null use INT32_MAX. mapping.source = 0x7fffffff; // Instead of null use INT32_MAX. }; exports.compareByOriginalPositionsNoSource = function (memory, mappingA, mappingB, onlyCompareOriginal) { var cmp = memory[mappingA + 3] - memory[mappingB + 3]; // originalLine if (cmp !== 0) { return cmp; } cmp = memory[mappingA + 4] - memory[mappingB + 4]; // originalColumn if (cmp !== 0 || onlyCompareOriginal) { return cmp; } cmp = memory[mappingA + 1] - memory[mappingB + 1]; // generatedColumn if (cmp !== 0) { return cmp; } cmp = memory[mappingA + 0] - memory[mappingB + 0]; // generatedLine if (cmp !== 0) { return cmp; } return memory[mappingA + 5] - memory[mappingB + 5]; // name };
, . Mapping
, , . VM allocation sinking , scalar replacement . , SpiderMonkey , .
[ JS. , , "" source-map
, WASM]
, GC :


, SpiderMonkey , , .
SpiderMonkey
, SpiderMonkey: 4 64 , , 7 .

- , , .
SpiderMonkey Jan de Mooij , â asm.js 2012 ⊠SpiderMonkey, .
â Uint8Array
.
, Uint8Array
, .

[ source-map
, JavaScript- JSON.decode
. , .]
:
$ d8 bench-shell-bindings.js ... [Stats samples: 5, total: 24050 ms, mean: 4810 ms, stddev: 155.91063145276527 ms] $ sm bench-shell-bindings.js ... [Stats samples: 7, total: 22925 ms, mean: 3275 ms, stddev: 269.5999093306804 ms]
$ d8 bench-shell-bindings.js ... [Stats samples: 22, total: 25158 ms, mean: 1143.5454545454545 ms, stddev: 16.59358125226469 ms] $ sm bench-shell-bindings.js ... [Stats samples: 31, total: 25247 ms, mean: 814.4193548387096 ms, stddev: 5.591064299397745 ms]


4 !
, , originalMappings
, . , originalMappings
:
allGeneratedPositionsFor
, ;eachMapping(..., ORIGINAL_ORDER)
, .
, allGeneratedPositionsFor
originalMappings[i]
, , - .
, V8 19 , V8 19 ( untrusted code mitigations ).

"" source-map
19 , source-map
source-map
, Rust WASM.
Rust parse_mappings
, Rust- , generatedMappings
. JS-, originalMappings[i]
.
( generatedMappings
) generatedMappings
.


, , Rust- generatedMappings
, JS-.
" Rust+WASM " . , , Rust source-map
.
(27-02-2018)
, source-map
Rust+WASM, . :

, WASM+Rust 15% SpiderMonkey V8.
JavaScript
â
â . . perf
â "" , .
. , .
â . , 100 , 3333 30 ?
3 â , .
, : , , ?
VM . !
. . : " !" ã (VM) â , , . , . C++ .
VM
, JavaScript.
, , , - .
/
, , , .
JavaScript , , , VM, â .
VM , . , DevTools.
, , , . ? , . , . , (.. Rust).
: - , , , . , , .
ããšãã
, :
- ;
- , , ;
- , V8.
, , . , "" , "" , . :
. V8 . JS . . (Rust, ) .
.
, , ( ) . . JS VM .
⊠? , , , . , .
æããã«ãåéçºè
ãšåããŒã ã¯N
ãJavaScriptã³ãŒããæ
éã«ãããã¡ã€ãªã³ã°ãèªã¿åããæ€èšããæéãè²»ãããM
ããã¡ãŒã ãèšèªã«æžãæããæéãè²»ããããèªç±ã«éžæã§ããŸãX
ã
ãã ããïŒaïŒéžæè¢ãå®éã«ååšããããšã誰ããå®å
šã«èªèããå¿
èŠããããŸããïŒbïŒèšèªãã¶ã€ããŒãšã¢ãŒãã£ã¹ããååããŠããã®éžæããŸããŸãæç¢ºã«ããªããŠã¯ãªããŸãããã€ãŸããèšèªæ©èœãšããŒã«ã«åãçµã¿ããã°ã«ãŒãïŒ3ãæé©åã®å¿
èŠæ§ãæžãããŸãã