Skip to content

~+700% regression in wasm-opt --nm performance from Emscripten 3.1.38 -> 4.0.19 #8406

Open

Description

@juj

Previously we were using Emscripten 3.1.38.

In some of our code stripping related unit tests, we are running wasm-opt --nm on a .wast file. In old Emscripten 3.1.38, this step took ~11 seconds.

After updating to Emscripten 4.0.19, the same wasm-opt --nm step is now taking about 87 seconds, about ~8x increase from before.

Bisecting the issue, the performance regression has arrived in #6371:

commit 956d2d8 (HEAD)
Author: Thomas Lively tlively@google.com
Date: Thu Apr 25 14:55:12 2024 -0700

[Parser] Enable the new text parser by default (#6371)

The new text parser is faster and more standards compliant than the old text
parser. Enable it by default in wasm-opt and update the tests to reflect the
slightly different results it produces. Besides following the spec, the new
parser differs from the old parser in that it:

 - Does not synthesize `loop` and `try` labels unnecessarily
 - Synthesizes different block names in some cases
 - Parses exports in a different order
 - Parses `nop`s instead of empty blocks for empty control flow arms
 - Does not support parsing Poppy IR
 - Produces different error messages
 - Cannot parse `pop` except as the first instruction inside a `catch`

CC @tlively

A samply profile of the slow behavior can be observed https://share.firefox.dev/4aUcR0c

Image

If I back out to the previous commit and profile that, then I get the following looking profile:

https://share.firefox.dev/4sc8owW

Image

I wonder if there might exist any optimizations that could bring the performance of the new parser up to par with the old one?

Activity

  1. tlively commented on Mar 2, 2026

    @tlively
    Member

    Interesting, as the commit message says, we measured the new parser as being significantly faster than the old parser at the time. I wonder if you're triggering some pathological (e.g. quadratic) behavior. I'll take a closer look at the profiles to see if anything stands out, but it would also be helpful to have a reproducer, if possible. Maybe you could use wasm-reduce to get a shareable module that still causes the performance difference?

  2. tlively commented on Mar 2, 2026

    @tlively
    Member

    And just to verify, these are release builds of wasm-opt, right?

  3. juj commented on Mar 4, 2026

    @juj
    CollaboratorAuthor

    Uploaded the slow parsing .wast file to https://oummg.com/dump/slow_wast.zip . This is what I ran the above profiles on.

    The test cases were run on -DCMAKE_BUILD_TYPE=Release builds.

    For the purposes of profiling with samply, I did a separate -DCMAKE_BUILD_TYPE=RelWithDebInfo build of Binaryen to get the function names show up in the profile - but these were not timed.

  4. MaxGraey commented on Apr 1, 2026

    @MaxGraey
    Contributor

    I looked at the wat file, and it contains some incredibly long identifiers. And in the new parser, this String::isUTF8 is confusing me:
    https://github.com/WebAssembly/binaryen/blob/main/src/parser/lexer.cpp#L871

    it's quite expensive for long identifiers which highly likely ascii-only:

    bool isUTF8(std::string_view str) {
      while (str.size()) {
        auto u = takeWTF8CodePoint(str);
        if (!u || (0xD800 <= *u && *u < 0xE000)) {
          return false;
        }
      }
      return true;
    }

    takeWTF8CodePoint also not cheap and contains loops and substring ops

  5. tlively commented on Apr 2, 2026

    @tlively
    Member

    I'm currently using wasm-reduce to reduce the text module while maintaining at least a 2x slowdown relative to the old parser. The profiling the reduced reproducer will hopefully highlight what's going wrong.

  6. tlively commented on Apr 2, 2026

    @tlively
    Member

    @MaxGraey, the reproducer does not have any identifiers of the form $"...", so it will never run that UTF-8 logic.

  7. MaxGraey commented on Apr 2, 2026

    @MaxGraey
    Contributor

    Yeah, In this case or this potentially hot path or perhaps skipSpace. At first, I actually thought about parsing expressions, because you can easily end up with a combinatorial explosion there if you don’t implement something like a Pratt parser or caching, but it seems to be fine, and the complexity should be nearly sublinear.

  8. tlively commented on Apr 14, 2026

    @tlively
    Member

    I've landed a couple of changes that improve parsing speed: #8597 and #8601. @juj, can you try on your machine with these changes applied? On my machine the regression compared to the old parser has dropped from ~100% to ~20%, so I'm curious how it looks on your machine starting from ~700%.

  9. tlively commented on Jun 4, 2026

    @tlively
    Member

    Just wanted to bump this, @juj. Is there still a significant performance gap on your machine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions