æ°é±éåã ã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ãæé©åã®å¿
èŠæ§ãæžãããŸãã