Skip to content

Commit

Permalink
update website
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 11, 2023
1 parent e085302 commit c7b995f
Showing 1 changed file with 1 addition and 0 deletions.
1 change: 1 addition & 0 deletions faq/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<li><a href="#upcoming-roadmap">Upcoming roadmap</a></li>
<li><a href="#production-readiness">Production readiness</a></li>
<li><a href="#anti-virus-software">Anti-virus software</a></li>
<li><a href="#outdated-version-of-go">Outdated version of Go</a></li>
<li><a href="#minified-newlines">Minified newlines</a></li>
<li><a href="#top-level-var">Top-level <code>var</code></a></li>
</ul><h2 id="why-is-esbuild-fast"><a class="permalink" href="#why-is-esbuild-fast">#</a>Why is esbuild fast?</h2><p>Several reasons:</p><ul><li><p> It's written in <a href="https://go.dev/">Go</a> and compiles to native code. </p> <p> Most other bundlers are written in JavaScript, but a command-line application is a worst-case performance situation for a JIT-compiled language. Every time you run your bundler, the JavaScript VM is seeing your bundler's code for the first time without any optimization hints. While esbuild is busy parsing your JavaScript, node is busy parsing your bundler's JavaScript. By the time node has finished parsing your bundler's code, esbuild might have already exited and your bundler hasn't even started bundling yet. </p> <p> In addition, Go is designed from the core for parallelism while JavaScript is not. Go has shared memory between threads while JavaScript has to serialize data between threads. Both Go and JavaScript have parallel garbage collectors, but Go's heap is shared between all threads while JavaScript has a separate heap per JavaScript thread. This seems to cut the amount of parallelism that's possible with JavaScript worker threads in half <a href="https://github.com/evanw/esbuild/issues/111#issuecomment-719910381">according to my testing</a>, presumably since half of your CPU cores are busy collecting garbage for the other half. </p></li><li><p> Parallelism is used heavily. </p> <p> The algorithms inside esbuild are carefully designed to fully saturate all available CPU cores when possible. There are roughly three phases: parsing, linking, and code generation. Parsing and code generation are most of the work and are fully parallelizable (linking is an inherently serial task for the most part). Since all threads share memory, work can easily be shared when bundling different entry points that import the same JavaScript libraries. Most modern computers have many cores so parallelism is a big win. </p></li><li><p> Everything in esbuild is written from scratch. </p> <p> There are a lot of performance benefits with writing everything yourself instead of using 3rd-party libraries. You can have performance in mind from the beginning, you can make sure everything uses consistent data structures to avoid expensive conversions, and you can make wide architectural changes whenever necessary. The drawback is of course that it's a lot of work. </p> <p> For example, many bundlers use the official TypeScript compiler as a parser. But it was built to serve the goals of the TypeScript compiler team and they do not have performance as a top priority. Their code makes pretty heavy use of <a href="https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html">megamorphic object shapes</a> and unnecessary <a href="https://github.com/microsoft/TypeScript/issues/39247">dynamic property accesses</a> (both well-known JavaScript speed bumps). And the TypeScript parser appears to still run the type checker even when type checking is disabled. None of these are an issue with esbuild's custom TypeScript parser. </p></li><li><p> Memory is used efficiently. </p> <p> Compilers are ideally mostly O(n) complexity in the length of the input. So if you are processing a lot of data, memory access speed is likely going to heavily affect performance. The fewer passes you have to make over your data (and also the fewer different representations you need to transform your data into), the faster your compiler will go. </p> <p> For example, esbuild only touches the whole JavaScript AST three times: </p> <ol> <li>A pass for lexing, parsing, scope setup, and declaring symbols</li> <li>A pass for binding symbols, minifying syntax, JSX/TS to JS, and ESNext-to-ES2015</li> <li>A pass for minifying identifiers, minifying whitespace, generating code, and generating source maps</li> </ol> <p> This maximizes reuse of AST data while it's still hot in the CPU cache. Other bundlers do these steps in separate passes instead of interleaving them. They may also convert between data representations to glue multiple libraries together (e.g. string→TS→JS→string, then string→JS→older JS→string, then string→JS→minified JS→string) which uses more memory and slows things down. </p> <p> Another benefit of Go is that it can store things compactly in memory, which enables it to use less memory and fit more in the CPU cache. All object fields have types and fields are packed tightly together so e.g. several boolean flags only take one byte each. Go also has value semantics and can embed one object directly in another so it comes &quot;for free&quot; without another allocation. JavaScript doesn't have these features and also has other drawbacks such as JIT overhead (e.g. hidden class slots) and inefficient representations (e.g. non-integer numbers are heap-allocated with pointers). </p></li></ul><p>Each one of these factors is only a somewhat significant speedup, but together they can result in a bundler that is multiple orders of magnitude faster than other bundlers commonly in use today.</p><h2 id="benchmark-details"><a class="permalink" href="#benchmark-details">#</a>Benchmark details</h2><p>Here are the details about each benchmark:</p><figcaption>JavaScript benchmark</figcaption><figure class="bench" style="position:relative;max-width:800px;height:110px;font-size:13px;line-height:20px;"><div style="position:absolute;left:120px;top:0;right:0;height:80px;"><div style="position:absolute;left:0.00%;top:0;width:1px;bottom:0;background:rgba(127,127,127,0.25);"></div><div style="position:absolute;left:21.48%;top:0;width:1px;bottom:0;background:rgba(127,127,127,0.25);"></div><div style="position:absolute;left:42.97%;top:0;width:1px;bottom:0;background:rgba(127,127,127,0.25);"></div><div style="position:absolute;left:64.45%;top:0;width:1px;bottom:0;background:rgba(127,127,127,0.25);"></div><div style="position:absolute;left:85.94%;top:0;width:1px;bottom:0;background:rgba(127,127,127,0.25);"></div><div style="position:absolute;left:0;top:3px;width:0.79%;height:14px;background:rgba(191,191,191,0.2);"></div><div style="position:absolute;left:0;top:3px;width:0.79%;height:14px;background:#FFCF00;" class="bench0-bar0"></div><div style="position:absolute;right:100%;top:0px;width:120px;height:20px;text-align:right;white-space:nowrap;margin-right:8px;font-weight:bold;"><a href="https://esbuild.github.io/">esbuild</a></div><div style="position:absolute;left:0.79%;top:0px;height:20px;margin-left:8px;font-weight:bold;">0.37s</div><div style="position:absolute;left:0;top:23px;width:65.53%;height:14px;background:rgba(191,191,191,0.2);"></div><div style="position:absolute;left:0;top:23px;width:65.53%;height:14px;background:#FFCF00;" class="bench0-bar1"></div><div style="position:absolute;right:100%;top:20px;width:120px;height:20px;text-align:right;white-space:nowrap;margin-right:8px;"><a href="https://parceljs.org/">parcel 2</a></div><div style="position:absolute;left:65.53%;top:20px;height:20px;margin-left:8px;">30.50s</div><div style="position:absolute;left:0;top:43px;width:68.90%;height:14px;background:rgba(191,191,191,0.2);"></div><div style="position:absolute;left:0;top:43px;width:68.90%;height:14px;background:#FFCF00;" class="bench0-bar2"></div><div style="position:absolute;right:100%;top:40px;width:120px;height:20px;text-align:right;white-space:nowrap;margin-right:8px;"><a href="https://rollupjs.org/">rollup</a> + <a href="https://terser.org/">terser</a></div><div style="position:absolute;left:68.90%;top:40px;height:20px;margin-left:8px;">32.07s</div><div style="position:absolute;left:0;top:63px;width:85.29%;height:14px;background:rgba(191,191,191,0.2);"></div><div style="position:absolute;left:0;top:63px;width:85.29%;height:14px;background:#FFCF00;" class="bench0-bar3"></div><div style="position:absolute;right:100%;top:60px;width:120px;height:20px;text-align:right;white-space:nowrap;margin-right:8px;"><a href="https://webpack.js.org/">webpack 5</a></div><div style="position:absolute;left:85.29%;top:60px;height:20px;margin-left:8px;">39.70s</div><div style="position:absolute;left:0.00%;top:84px;width:50px;margin-left:-25px;text-align:center;">0s</div><div style="position:absolute;left:21.48%;top:84px;width:50px;margin-left:-25px;text-align:center;">10s</div><div style="position:absolute;left:42.97%;top:84px;width:50px;margin-left:-25px;text-align:center;">20s</div><div style="position:absolute;left:64.45%;top:84px;width:50px;margin-left:-25px;text-align:center;">30s</div><div style="position:absolute;left:85.94%;top:84px;width:50px;margin-left:-25px;text-align:center;">40s</div></div></figure><p>This benchmark approximates a large JavaScript codebase by duplicating the <a href="https://github.com/mrdoob/three.js">three.js</a> library 10 times and building a single bundle from scratch, without any caches. The benchmark can be run with <code>make bench-three</code> in the <a href="https://github.com/evanw/esbuild">esbuild repo</a>.</p><table>
Expand Down

0 comments on commit c7b995f

Please sign in to comment.