Skip to content

WSL2: Redirecting wsl.exe stdout to SMB is bottlenecked by 4 KiB serial writes #41572

Description

@Tatsujinichi

Windows Version

Microsoft Windows [Version 10.0.26200.9445]

WSL Version

2.7.13.0

Are you using WSL 1 or WSL 2?

  • WSL 2
  • WSL 1

Kernel Version

6.18.33.2-2

Distro Version

Ubuntu 26.04 LTS

Other Software

  • Windows cmd.exe for binary stdout redirection; Windows PowerShell 5.1 for orchestration and performance counters.
  • GNU tar 1.35 and zstd 1.5.7 in Ubuntu. The smaller reproduction uses only cat, without compression.
  • SMB 3.x NAS backed by mirrored HDDs, over 2.5 GbE. The NAS configuration was unchanged between comparisons.

Repro Steps

Redirecting a large WSL process's stdout directly into a Windows SMB file produces small, effectively serial writes. A real archive transfer averaged about 13.4 MiB/s even though the source, network, and storage could run substantially faster. A controlled test with the same random input improved from 12.90 to 78.07 MiB/s when only the Windows output handling was changed to accumulate larger writes.

  1. Authenticate to an SMB share using Windows and create a new, empty scratch directory on it. Substitute that directory for \\SERVER\SHARE\scratch below.

  2. From Windows Command Prompt, create an incompressible test file in the WSL filesystem. conv=excl prevents overwriting an existing fixture; choose a different filename if it already exists:

    wsl.exe -d Ubuntu --exec dd if=/dev/urandom of=/tmp/wsl-stdout-repro.bin bs=1M count=128 conv=excl status=none
  3. Still in Command Prompt, redirect the file to a new destination in the empty scratch directory:

    wsl.exe -d Ubuntu --exec cat /tmp/wsl-stdout-repro.bin > "\\SERVER\SHARE\scratch\direct.bin"

    The redirection is performed by cmd.exe, not a PowerShell text pipeline. Observe SMB client bytes per write, write latency, and throughput while this runs. A larger fixture gives a longer sampling window.

  4. For comparison, run the same WSL cat producer with Windows ProcessStartInfo.RedirectStandardOutput = true, consume StandardOutput.BaseStream, and write to a new SMB file using a .NET FileStream with a 1 MiB buffer. The destination buffer accumulates short reads before issuing larger writes. Drain stderr separately, check the producer exit code, and include a final FileStream.Flush(true) in the measurement.

This is the minimal form of the tested cat path. The original workload was wsl.exe ... tar cf - --zstd ... > "\\SERVER\SHARE\archive.tar.zst"; the behavior is not dependent on tar, compression, or copying many separate destination files.

Expected Behavior

Bulk redirected stdout should be able to use larger output writes without requiring a separate Windows buffering program. A larger buffer for redirected output, or an exposed stdout-relay buffer setting, would allow this path to approach the throughput supported by the destination while preserving interactive console behavior.

Actual Behavior

During the original archive transfer, Windows SMB client counters showed:

Metric Observed
Average bytes per write Exactly 4,096
Writes per second 3,413-3,489
Write throughput 13.3-13.6 MiB/s
Average SMB write latency 0.240-0.245 ms
Average write queue length 0.837-0.839
Credit stalls Zero

Windows wsl.exe process I/O counters independently showed approximately 4 KiB per application write. Linux tar and the zstd output thread were waiting on their output pipes.

Controlled comparison using the same 128 MiB random Linux file, with final destination synchronization included and destination length/SHA-256 verified:

Output path Seconds MiB/s
cmd.exe redirection directly from wsl.exe to the UNC file 9.926 12.90
Same WSL producer, Windows binary-stream reader, 1 MiB destination FileStream buffer 1.639 78.07

Those short tests ran while the original backup was active, so they are a controlled comparison of output handling, not sustained disk-speed claims.

After stopping the other backup, additional measurements established that the destination was not limited to 13 MiB/s:

  • Native NAS 32 GiB incompressible direct-I/O writes, including final synchronization: 158.88 MiB/s at queue depth 1 and 189.83 MiB/s at queue depth 16. Physical counters confirmed about 32 GiB written to each mirror disk in each pass, rather than only to RAM.
  • A full archive transfer reached about 151 MiB/s using a 1 MiB Windows pipe and four reusable 1 MiB buffers with overlapping reading/writing. It passed full decompression, zstd checksum verification, and parsing of every tar entry.

That final pipeline also used larger Linux pipes and tar records, so its approximately 11x improvement over the observed original transfer should not be attributed entirely to one buffer change. The smaller same-input comparison above isolates the Windows output handling.

Diagnostic Logs

The counters and controlled measurements are summarized above. No ETW trace or packet capture was collected. Source inspection identifies a path that matches the observed 4 KiB writes:

  1. In release 2.7.13, relay.hpp defines LX_RELAY_BUFFER_SIZE as 0x1000 and uses it as the default CreateThread buffer size.
  2. SvcComm::LaunchProcess creates the stdout relay without overriding that buffer size.
  3. InterruptableRelay reads into that buffer and writes the bytes read before proceeding. InterruptableWrite waits for a pending WriteFile to complete.

Current upstream commit 16337836b11ff565a43f9714490d8dd382798450 still has the 4 KiB constant and the stdout caller using the default.

I could not find a runtime setting for this stdout buffer in the WSL configuration documentation or the corresponding configuration parser. Enlarging Linux pipe capacity or producer writes cannot enlarge this later Windows relay buffer.

Related PR #40217 increases a buffer for WSLC TCP port forwarding; it appears to affect a different path and does not change the stdout call above.

Activity

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

    fixinboundwsl2Issue/feature applies to WSL 2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions