Skip to content

Use a cached buffer for serialization#867

Merged
kevingosse merged 2 commits into
masterfrom
kevin/optimizeflushtraces
Aug 25, 2020
Merged

Use a cached buffer for serialization#867
kevingosse merged 2 commits into
masterfrom
kevin/optimizeflushtraces

Conversation

@kevingosse

@kevingosse kevingosse commented Aug 18, 2020

Copy link
Copy Markdown
Contributor

MessagePack has an internal pool of buffers, but they are thread-static. It's bad because:

  • Given that we flush traces only every second, there's little chance that we end up using the same thread
  • Every new buffer will stay alive in the thread TLS, probably long enough to end up in gen 2

We almost never send traces to the agent concurrently (I believe the call to IAgentWriter.Ping() at startup is the only exception so far). Therefore it makes sense to use a single cached buffer, and fallback to allocating a new one in the unlikely case we need it from two different threads at the same time.

I also updated the WriteAndFlushTraces benchmark to use a fake stream, because the MemoryStream was the source of a lot of extra allocations. In the real world we would be writing to a network stream and we have (AFAIK) no control over how much it'll allocate. It's better to focus on measuring what's actionable.

Benchmark: AgentWriterBenchmark.WriteAndFlushTraces

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.329 (2004/?/20H1)
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.302
  [Host]        : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  .NET 4.7.2    : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT

Method Job Runtime Mean Error StdDev Median Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
WriteAndFlushTraces_New .NET 4.7.2 .NET 4.7.2 403.3 μs 8.01 μs 22.61 μs 394.6 μs 0.85 0.08 12.2070 1.4648 - 76.83 KB
WriteAndFlushTraces_Old .NET 4.7.2 .NET 4.7.2 484.1 μs 9.62 μs 18.08 μs 490.7 μs 1.00 0.00 41.0156 41.0156 41.0156 205.65 KB
WriteAndFlushTraces_New .NET Core 3.1 .NET Core 3.1 296.3 μs 5.63 μs 6.70 μs 295.9 μs 0.80 0.03 11.7188 1.4648 - 74.82 KB
WriteAndFlushTraces_Old .NET Core 3.1 .NET Core 3.1 370.6 μs 7.28 μs 7.79 μs 368.6 μs 1.00 0.00 41.5039 41.5039 41.5039 202.85 KB

The main source of allocations remaining on this codepath is GetUniqueTraceIds. I tried replacing the hashset by a Linq Distinct() operation. It reduces heap allocations by about 40% (down to ~45KB) but also increase processing time by about 10% on .NET Framework (up to 440µs, no significant increase on .NET Core), so I don't know if it's worth it.

@kevingosse
kevingosse requested a review from a team as a code owner August 18, 2020 11:58
@kevingosse kevingosse added type:performance Performance, speed, latency, resource usage (CPU, memory) area:benchmarks Benchmarks, throughput tests, Crank, Bombardier, etc labels Aug 18, 2020
@DataDog DataDog deleted a comment from azure-pipelines Bot Aug 18, 2020
@kevingosse

Copy link
Copy Markdown
Contributor Author

/azp run Benchmarks

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@kevingosse

Copy link
Copy Markdown
Contributor Author

/azp run Benchmarks

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@kevingosse
kevingosse force-pushed the kevin/optimizeflushtraces branch from 2d7d7e6 to eecb82a Compare August 19, 2020 11:30

@lucaspimentel lucaspimentel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Only non-blocking comments.

Comment thread benchmarks/Benchmarks.Trace/AgentWriterBenchmark.cs Outdated
buffer = new byte[InitialBufferSize];
}

int length = MessagePackSerializer.Serialize(ref buffer, 0, obj, resolver);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope of this PR, but something to keep in mind: having the size of the payload means we can now send it as a tracer health metric (see TracerMetricNames for examples of other metrics).

@lucaspimentel
lucaspimentel requested a review from a team August 24, 2020 16:45
@kevingosse
kevingosse force-pushed the kevin/optimizeflushtraces branch from eecb82a to e6431b9 Compare August 25, 2020 09:55
@kevingosse
kevingosse merged commit 0d6505f into master Aug 25, 2020
@kevingosse
kevingosse deleted the kevin/optimizeflushtraces branch August 25, 2020 14:10
@zacharycmontoya zacharycmontoya added this to the 1.19.2 milestone Aug 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:benchmarks Benchmarks, throughput tests, Crank, Bombardier, etc type:performance Performance, speed, latency, resource usage (CPU, memory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants