You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<p><code>Map.prototype.upsert</code> is a new method for <ahref="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"><code>Map</code>-object</a> in JavaScript™. The operation simplifies the process of inserting or updating key-value pairs in the <code>Map</code>: it checks for existence of a key and then either <code>insert</code>s or <code>update</code>s a key-value pair. </p>
90
90
<p>This proposal is also intended for <ahref="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"><code>WeakMap</code></a>, which shares a similar API structure. The behavior of <code>upsert</code> in <code>WeakMap</code> would follow the same principles, with the primary difference being the behavior of keys in <code>WeakMap</code>, as they are held weakly and can be garbage collected when no other references to them exist. In this tutorial however, we will focus on the <code>Map</code>-implementation.</p>
@@ -1318,26 +1318,29 @@ <h2>Writing the Specification in Ecmarkup</h2>
1318
1318
<sectionclass="collapsible" id="optimization">
1319
1319
<h2>Optimization</h2>
1320
1320
<divclass="content-body">
1321
-
<p>A proposal goes through several <ahref="https://www.proposals.es/stages" target="_blank">stages</a> before it becomes a part of the ECMAScript® language.
1322
-
Every new feature introduces complexity, which can affect the performance of the SpiderMonkey engine.
1323
-
Therefore optimization becomes crucial when designing and implementing these features.
1324
-
In our case there is especially one line which could use some optimization:</p>
1325
-
<pre><code>4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do</code></pre>
1326
-
<p> As of right now it is implemented like this:</p>
1327
-
<pre><codeclass="language-js">for (var e of allowContentIter(entries)) {
1321
+
<p>When a new feature is being added to the language, we must consider the complexity it introduces - this can affect the performance of the SpiderMonkey engine.
1322
+
Therefore, optimization becomes crucial when designing and implementing proposals.
1323
+
In our case, step 4 of the specification </p>
1324
+
<pre><codeclass="language-lua">4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do
1325
+
</code></pre>
1326
+
<p> could use some optimization.</p>
1327
+
<p> Currently, this step is implemented like this:</p>
1328
+
<pre><codeclass="language-js">// 4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do
1329
+
for (var e of allowContentIter(entries)) {
1328
1330
var eKey = e[0];
1329
1331
var eValue = e[1];
1330
-
//...
1332
+
//...
1331
1333
}
1332
1334
</code></pre>
1333
-
<p> The worst case for this is that is loops through the entire <code>entries</code>. The result is a runtime of <strong><code>O(n)</code></strong> where <code>n</code> is the size of the <code>Map</code>.
1334
-
This is rather slow, considering a lookup in maps should be <strong><code>~O(1)</code></strong>, given an efficient <code>HashTable</code> implementation.
1335
-
Therefore, we decided to try optimizing this line.</p>
1336
-
<p><strong>Demonstration: Create a new file; Runtime.js with the code below and run the script with <code>./mach build</code> and <code>./mach run Runtime.js</code></strong></p>
<p> In the worst case scenario, the loop would go through all of the <code>entries</code>, resulting in linear time complexity (that is, <code>O(n)</code> where <code>n</code> is the size of the <code>Map</code>).
1336
+
This is rather slow, especially considering that a lookup in maps could be done in a constant time (<code>~O(1)</code>), given an efficient <code>HashTable</code> implementation.
1337
+
In this section, we use this fact to optimize the implementation of the step 4 in the specification.</p>
1338
+
<p> Before proceeding, we informally demonstrate the performance of the current design of <code>upsert</code>.
1339
+
In the code below, we measure the runtime of of updating or inserting key-value pairs into a <code>Map</code> object:
1340
+
using the <code>upsert</code> method vs. using <code>has</code>, <code>get</code>, and <code>set</code>.
1341
+
The <code>measureRuntime</code> function is used to execute and log the execution time of each approach over a fixed number of iterations.</p>
1342
+
<p> We can create a new file <code>Runtime.js</code> with the code below and run it with: <code>./mach build</code> and <code>./mach run Runtime.js</code>.</p>
measureRuntime(withUpsert, "Test upsert for " + iterations + " iterations");
1384
-
measureRuntime(withoutUpsert, "Test without upsert for " + iterations + " iterations");
1385
-
</code></pre>
1386
-
</details>
1387
-
1388
-
1389
-
<p> One solution we had, was to check if the entry was in the <code>Map</code>, by using <code>Map::has</code>.
1390
-
The problem with this, is that this method is not currently exposed to self-hosted JavaScript™ code. The reason for this
1391
-
is seemingly because there has not been any need for the <code>Map::has</code> method in self-hosted code previously.</p>
1392
-
<p><strong>Exposing <code>std_Map_has</code> to self-hosted code</strong></p>
1393
-
<p><code>Selfhosting.cpp</code></p>
1394
-
<pre><codeclass="language-cpp">
1385
+
console.log("Starting tests ...");
1386
+
measureRuntime(withUpsert, "Test `upsert` for " + iterations + " iterations");
1387
+
measureRuntime(withoutUpsert, "Test without `upsert` for " + iterations + " iterations");
1388
+
</code></pre>
1389
+
<p> Now we are ready to consider how the step 4 in the specification can be implemented in a more optimal way.
1390
+
One solution we could have is to check if an entry was in the <code>Map</code> using the function <ahref="https://262.ecma-international.org/#sec-map.prototype.has"><code>Map::has</code></a>.
1391
+
The problem with this solution, however, is that this method is not currently exposed to self-hosted JavaScript™ code.
1392
+
An apparent reason for this is that there has not been any need for the <code>Map::has</code> method in self-hosted code previously.</p>
1393
+
<p> Recall the <code>Map</code> methods exposed to self-hosted code in <ahref="https://searchfox.org/mozilla-central/source/js/src/vm/SelfHosting.cpp#2383"><code>SelfHosting.cpp</code></a>:</p>
1394
+
<pre><codeclass="language-cpp"> // This code is from: /js/src/vm/SelfHosting.cpp
<p> Copy the line and paste it into <code>js/src/vm/Selfhosting.cpp</code>, before <code>MapObject::set</code> (to ensure consistency across files).</p>
<p>To ensure consistency across files, we add this line before <code>JS_FN("std_Map_set", MapObject::set, 2, 0),</code>, resulting in the following:</p>
1407
+
<pre><codeclass="language-cpp"> // This code is from: /js/src/vm/SelfHosting.cpp
<p> We also need to make the <code>has</code> method publicly exposed in <code>MapObject.h</code> to use it in self-hosted code.</p>
1422
-
<p> In <code>MapObject.h</code>, move this line from <strong>private</strong> to <strong>public</strong>.</p>
1417
+
<p> We also need to make the method <code>has</code> publicly exposed in <ahref="https://searchfox.org/mozilla-central/source/js/src/builtin/MapObject.h"><code>MapObject.h</code></a>.
1418
+
To do this, we replace the visibility modifier of <ahref="https://searchfox.org/mozilla-central/source/js/src/builtin/MapObject.h#217">the method with signature</a></p>
<summary>(This could be tricky) Let's break down the structure of the file: </summary>
1427
-
1428
-
<pre><codeclass="language-cpp">class MapObject : public NativeObject {
1421
+
<p> from <ahref="https://searchfox.org/mozilla-central/source/js/src/builtin/MapObject.h#186"><code>private</code></a> to <ahref="https://searchfox.org/mozilla-central/source/js/src/builtin/MapObject.h#109"><code>public</code></a>.</p>
1422
+
<pre><codeclass="language-cpp"> // This code is from: /js/src/builtin/MapObject.h
<p>This will enable us to use <ahref="https://262.ecma-international.org/#sec-map.prototype.has"><code>has</code></a> in our optimized implementation: in self-hosted JavaScript™, we will be able to call this method using <code>callFunction</code> and passing <code>std_Map_has</code> as an argument.</p>
1450
+
<h3id="optimizing-the-implementation-of-upsert">Optimizing the implementation of <code>upsert</code></h3>
1451
+
<p> We can now modify our implementation of the <code>upsert</code> method to use <code>std_Map_has</code> instead of a <em><code>for ... of</code></em> loop and <ahref="https://262.ecma-international.org/#sec-samevaluezero"><code>SameValueZero</code></a>.</p>
<p> The <code>std_Map_has</code> method should now be available in self-hosted JavaScript™.</p>
1456
-
<h3id="optimize-the-function">Optimize the function</h3>
1457
-
<p> With <code>has</code> now exposed to self-hosted code, alter your implementation to use <code>std_Map_has</code> instead of a <code>for-of</code> loop
This ensures compatibility and correctness while allowing flexibility in optimization or internal design.</p>
1486
+
<p>We quote here a <ahref="https://github.com/tc39/how-we-work/blame/cc47a79340a773876cb03371dc2d46b9d9ce9695/how-to-read.md#L7">comment</a> on the <ahref="https://github.com/tc39/how-we-work"><em>How We Work</em></a> document by TC39:</p>
1487
+
<blockquote>
1488
+
<p><em>Specification text is meant to be interpreted abstractly. Only the observable semantics, that is, the behavior when JavaScript™ code is executed, need to match the specification. Implementations can use any strategy they want to make that happen, including using different algorithms that reach the same result.</em></p>
1489
+
</blockquote>
1490
+
<p>Further discussion on this can be found <ahref="https://github.com/tc39/how-we-work/issues/104">here</a>.</p>
1491
+
<p>Note also that we don't need to change the specification text to reflect our optimized implementation:
1492
+
the specification is intended to define behavior at an abstract level, providing a precise but implementation-agnostic guide for JavaScript™ engines.
1493
+
This abstraction ensures that the specification remains broadly applicable,
1494
+
leaving room for diverse implementations while guaranteeing consistent observable behavior across compliant engines.
0 commit comments