BufferedStream LOH allocations

Open
#113,438 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Stale
Tech stack
csharp
Domain
performance

Research direction

Start with System.Private.CoreLib/src/System/IO/BufferedStream.cs, especially the source comments about SOH and LOH allocation. Review how BufferedStream creates, uses, and releases its byte[] backing buffer, then inspect related tests in the repository before deciding how large buffers could use ArrayPool.Shared. Done should preserve existing buffering behavior while avoiding the reported repeated LOH allocations; the issue provides measurements and configuration for validation.

Written by the indexing model from the issue text.

Description

area-System.IO tenet-performance
Description

Using some measurements, BrotliStream seems to more efficiently when the written data is in larger chunks. This is also described in issue https://github.com/dotnet/runtime/issues/36245

Hence, it seems reasonable to combined writing with BufferedStream, that can buffer data in chunks to the brotli compression.

Based on measurements with a certain type of data, Brotli stream was the most efficient with writes of ~300KB chunks.

However, when such value is set as the buffer size in BufferedStream ctor, the buffer is allocated on the LOH. This results that after a dozen or so compressed streams written (each with a new BufferedStream), a Gen2 GC compaction is triggered. On a hot path, eventually causing performance issues as the GC spends a lot of time collecting garbage.

Configuration

.NET 9, x64, JIT

Regression?

No

Analysis

It seems each new BufferedStream is allocating a byte[] as a backing buffer. Ideally this could be backed by ArrayPool<byte>.Shared instead of new allocations - at least for the case when the buffer is allocated on the LOH. It seems BufferedStream already has knowledge about SOH and LOH as noted in some source code comments.

Workaround
  1. Pooling BufferedStream objects.
  2. Creating a custom stream object that buffers backed by ArrayPool. However, having a general implementation seems to be relatively complicated.
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.