Re: Performance regression in 5.x: fork-heavy $() loops slow down ~2.5x on second run when built with --with-bash-malloc=no

Alex Gao <[email protected]> Thu, 14 May 2026 13:42:39 +0800
Newsgroups gmane.comp.shells.bash.bugs
Message-ID <[email protected]>
在 2026/5/14 11:53, debianyu 写道:
> Machine: x86_64
> OS: linux-gnu
> Compiler: gcc
> Compilation CFLAGS: -O2 -g
> uname output: Linux 4.19.x x86_64 GNU/Linux
> Machine Type: x86_64-pc-linux-gnu
> Bash Version: 5.0 / 5.1 / 5.2 / 5.3 (vanilla upstream sources)
> Patch Level: latest available for each release
> 
> Description:
> 
> In an interactive shell, executing a fork-heavy loop with command substitution twice consecutively exhibits a ~2.5x slowdown on the second and subsequent runs:
> 
> time for i in {1..10000}; do echo $(date) &gt;/dev/null; done   # ~16s
> time for i in {1..10000}; do echo $(date) &gt;/dev/null; done   # ~40s
> time for i in {1..10000}; do echo $(date) &gt;/dev/null; done   # ~40s
> 
> The slowdown persists for the shell's lifetime, including subshells spawned from it.
> 
> Bisecting build configurations and bash versions reveals the slowdown requires both:
> 
> Bash 5.x series (5.0 onward; 4.4 unaffected)
> ./configure --with-bash-malloc=no (bash uses glibc malloc)
> 
> Either condition alone does not trigger the issue:
> 
> | Bash Version | Malloc | Run 1 | Run 2 | Status |
> | 4.4.19 | bash builtin | 15.2 s | 15.4 s | normal |
> | 4.4.19 | glibc | 15.1 s | 15.4 s | normal |
> | 5.0/5.2/5.3 | bash builtin | 17.0 s | 17.1 s | normal |
> | 5.0/5.2/5.3 | glibc | 16.6 s | 40.3 s | regression |
> 
> The regression with --with-bash-malloc=no persists across all 5.x releases, including the current master HEAD:
> 
> | Bash Version (vanilla) | --with-bash-malloc=no Run 1 / Run 2 |
> | 4.4.19 | 15.1 s / 15.4 s (normal) |
> | 5.0 | 16.6 s / 40.3 s (regression) |
> | 5.1 | ~17 s / ~40 s (regression) |
> | 5.2 | ~17 s / ~40 s (regression) |
> | 5.3.0 release tag | 16.4 s / 39.9 s (regression) |
> | master HEAD (5.3.9, commit 637f5c8 with 5.3 patches 1-9) | 16.3 s / 40.0 s (regression) |
> 展开
> 
> No released or development version of bash 5.x resolves this when built with --with-bash-malloc=no.
> 
> Additional Observations:
> Trigger Condition: Loop body must involve both fork and execve. Builtins or fork-only operations (e.g., x=$(:)) do not trigger the issue.
> Environment: Reproduces only in interactive shells (PTY-attached). Non-interactive scripts are unaffected.
> Debugging: Running under strace masks the slowdown.
> Reproduction Steps:
> Build vanilla bash 5.2 with --with-bash-malloc=no.
> Execute in a real PTY interactive shell:
> script -qc "/path/to/bash -i" /dev/null &lt;&lt;'EOF'
> time for i in {1..10000}; do echo $(date) &gt;/dev/null; done
> time for i in {1..10000}; do echo $(date) &gt;/dev/null; done
> EOF
> Rebuild with default --with-bash-malloc=yes to confirm the slowdown disappears.
> Verified Workarounds:
> Build with ./configure --with-bash-malloc=yes (default). Eliminates the regression across all tested 5.x versions.
> Use LD_PRELOAD=/usr/lib64/libjemalloc.so.2 on a --with-bash-malloc=no build. Prevents the slowdown on subsequent runs.
> 
> 
> Thanks
> 
>   Roy

Hi Roy

This looks closely related to the RFC patch I posted earlier:

[PATCH RFC 0/1] avoid repeated export_env rebuild in command 
substitution child
[PATCH RFC 1/1] execute_cmd: avoid rebuilding export environment in 
comsub exec path

Your reproducer also seems to require command subsitution plus 
fork/exec. So this maybe the same underlying issue, or at least a 
closely one.

Could you try your reproducer with that RFC patch applied?

Thanks,
Xiang