title: Is js native array.flat slow for depth=1?
body: <p>This <a href="https://gist.github.com/netanel-haber/e8f99db2854fe6de5728f63558a225a1" rel="nofollow noreferrer">gist</a> is a small benchmark I wrote comparing the performance for 4 alternatives for flattening arrays of
  <strong>depth=1</strong> in JS (the code can be copied as-is into the google console).  If I'm not missing anything, the native Array.prototype.flat has the worst performance by far - on the order of 30-50 times slower than any of the alternatives.</p>
  <p>It should be noted that the 4th implementation in this benchmark is consistently the most performant - often achieving a performance that is 70 times better. The code was tested several times in node v12 and the Chrome console.</p>
  <p>This result is accentuated most in a large subset - see the last 2 arrays tested below. This result is very surprising, given the <a href="https://tc39.es/proposal-flatMap/" rel="nofollow noreferrer">spec</a>, and the <a href="https://github.com/v8/v8/blob/a709f779401d43fba823085ab001d30339acfe0b/src/builtins/builtins-array-gen.cc#L1542" rel="nofollow noreferrer">V8 implementation</a> which seems to follow the spec by the letter. My C++ knowledge is non-existent, as is my familiarity with the V8 rabbit hole, but it seems to me that given the recursive definition, once we reach a final depth subarray, no further recursive calls are made for that subarray call (the flag shouldFlatten is false when the decremented depth reaches 0, i.e., the final sub-level) and adding to the flattened result includes iterative looping over each sub-element, and a simple call to <a href="https://tc39.es/ecma262/#sec-createdataproperty" rel="nofollow noreferrer">this method</a>. Therefore, I cannot see a good reason why a.flat should suffer so much on performance.</p>
  <p>I thought perhaps the fact that in the native flat the result's size isn't pre-allocated might explain the difference. The second implementation in this benchmark, which isn't pre-allocated, shows that this alone cannot explain the difference - it is still 5-10 times more performant than the native flat. What could be the reason for this?</p>
  <p>Implementations tested (order is the same in code, stored in the implementations array - the two I wrote are at the end of code snippet): <ol> <li>My own flattening implementation that includes pre-allocating the final flattened length (thus avoiding size all re-allocations). Excuse the imperative style code, I was going for max performance.</li> <li>Simplest naive implementation, looping over each sub-array and pushing into the final array. (thus risking many size re-allocations). </li> <li>Array.prototype.flat (native flat)</li> <li>[ ].concat(...arr) (=spreading array, then concatenating the results together. This is a popular way of accomplishing a depth=1 flattening).</li> </ol></p>
  <p>Arrays tested (order is the same in code, stored in the benchmarks object): <ol> <li>1000 subarrays with 10 elements each. (10 thou total)</li> <li>10 subarrays with 1000 elements each. (10 thou total)</li> <li>10000 subarrays with 1000 elements each. (10 mil total)</li> <li>100 subarrays with 100000 elements each. (10 mil total)</li> </ol></p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>let TenThouWideArray = Array(1000).fill().map(el =&gt; Array(10).fill(1));
  let TenThouNarrowArray = Array(10).fill().map(el =&gt; Array(1000).fill(1));
  let TenMilWideArray = Array(10000).fill().map(el =&gt; Array(1000).fill(1));
  let TenMilNarrowArray = Array(100).fill().map(el =&gt; Array(100000).fill(1));
  
  let benchmarks = { TenThouWideArray, TenThouNarrowArray, TenMilWideArray, TenMilNarrowArray };
  let implementations = [
      flattenPreAllocated,
      flattenNotPreAllocated,
      function nativeFlat(arr) { return Array.prototype.flat.call(arr) },
      function spreadThenConcat(arr) { return [].concat(...arr) }
  ];
  
  let result;
  Object.keys(benchmarks).forEach(arrayName =&gt; {
      console.log(`\n............${arrayName}............\n`)
      implementations.forEach(impl =&gt; {
          console.time(impl.name);
          result = impl(benchmarks[arrayName]);
          console.timeEnd(impl.name);
      })
      console.log(`\n............${arrayName}............\n`)
  })
  
  
  function flattenPreAllocated(arr) {
      let numElementsUptoIndex = Array(arr.length);
      numElementsUptoIndex[0] = 0;
      for (let i = 1; i &lt; arr.length; i++)
          numElementsUptoIndex[i] = numElementsUptoIndex[i - 1] + arr[i - 1].length;
      let flattened = new Array(numElementsUptoIndex[arr.length - 1] + arr[arr.length - 1].length);
      let skip;
      for (let i = 0; i &lt; arr.length; i++) {
          skip = numElementsUptoIndex[i];
          for (let j = 0; j &lt; arr[i].length; j++)
              flattened[skip + j] = arr[i][j];
      }
      return flattened;
  }
  function flattenNotPreAllocated(arr) {
      let flattened = [];
      for (let i = 0; i &lt; arr.length; i++) {
          for (let j = 0; j &lt; arr[i].length; j++) {
              flattened.push(arr[i][j])
          }
      }
      return flattened;
  }</code></pre>
  </div>
  </div>
  </p>
  <p>(<s><strong>Update</strong>: I've created a benchmark on <a href="https://jsperf.com/flat-array-depth-1" rel="nofollow noreferrer">jsperf</a></s> [JSperf is down]).</p>