Windows Version
Microsoft Windows [Version 10.0.26200.9445]
WSL Version
2.7.13.0
Are you using WSL 1 or WSL 2?
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.
-
Authenticate to an SMB share using Windows and create a new, empty scratch directory on it. Substitute that directory for \\SERVER\SHARE\scratch below.
-
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
-
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.
-
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:
- In release
2.7.13, relay.hpp defines LX_RELAY_BUFFER_SIZE as 0x1000 and uses it as the default CreateThread buffer size.
SvcComm::LaunchProcess creates the stdout relay without overriding that buffer size.
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.
Windows Version
Microsoft Windows [Version 10.0.26200.9445]
WSL Version
2.7.13.0
Are you using WSL 1 or WSL 2?
Kernel Version
6.18.33.2-2
Distro Version
Ubuntu 26.04 LTS
Other Software
cmd.exefor binary stdout redirection; Windows PowerShell 5.1 for orchestration and performance counters.cat, without compression.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.
Authenticate to an SMB share using Windows and create a new, empty scratch directory on it. Substitute that directory for
\\SERVER\SHARE\scratchbelow.From Windows Command Prompt, create an incompressible test file in the WSL filesystem.
conv=exclprevents 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=noneStill in Command Prompt, redirect the file to a new destination in the empty scratch directory:
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.For comparison, run the same WSL
catproducer with WindowsProcessStartInfo.RedirectStandardOutput = true, consumeStandardOutput.BaseStream, and write to a new SMB file using a .NETFileStreamwith 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 finalFileStream.Flush(true)in the measurement.This is the minimal form of the tested
catpath. The original workload waswsl.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:
Windows
wsl.exeprocess 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:
cmd.exeredirection directly fromwsl.exeto the UNC fileFileStreambufferThose 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:
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:
2.7.13,relay.hppdefinesLX_RELAY_BUFFER_SIZEas0x1000and uses it as the defaultCreateThreadbuffer size.SvcComm::LaunchProcesscreates the stdout relay without overriding that buffer size.InterruptableRelayreads into that buffer and writes the bytes read before proceeding.InterruptableWritewaits for a pendingWriteFileto complete.Current upstream commit
16337836b11ff565a43f9714490d8dd382798450still 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.