SlideShare a Scribd company logo
1 of 58
Download to read offline
BEYOND SIMPLE BENCHMARKS
A PRACTICAL GUIDE TO OPTIMIZING CODE
 | ✉ | 
danielmarbach daniel.marbach@particular.net Daniel Marbach
"SIMPLE"
Microsoft Teams’ Infrastructure and Azure Communication Services’ Journey to .NET 6
"We were able to see Azure Compute cost reduction of up to
50% per month, on average we observed 24% monthly cost
reduction after migrating to .NET 6. The reduction in cores
reduced Azure spend by 24%."
PERFORMANCE AWARE
BEAR AWARE
BE CURIOUS....
UNDERSTAND THE CONTEXT
How is this code going to be executed at
scale, and what would the memory
characteristics be (gut feeling)
Are there simple low-hanging fruits I can
apply to accelerate this code?
Are there things I can move away from the
hot path by simply restructuring a bit my
code?
What part is under my control and what isn't
really?
What optimizations can I apply, and when
should I stop?
THE PERFORMANCE LOOP
Profile at least CPU and memory using
a profiling harness
Improve parts of the hot path
Benchmark and compare
Profile improvements again with the
harness and make adjustments where
necessary
Ship and focus your attention to other
parts
Queue Code
NSERVICEBUS
go.particular.net/webinar-2023-quickstart
Queue
Message Pump
Behaviors
Code
...
NSERVICEBUS PIPELINE
ASP.NET CORE MIDDLEWARE
public class RequestCultureMiddleware {
_next = next;
public async Task InvokeAsync(HttpContext context) {
await _next(context);
1
private readonly RequestDelegate _next;
2
3
public RequestCultureMiddleware(RequestDelegate next) {
4
5
}
6
7
8
// Do work that does something before
9
10
// Do work that does something after
11
}
12
}
13
public class Behavior : Behavior<IIncomingLogicalMessageContext> {
public override Task
Invoke(IIncomingLogicalMessageContext context, Func<Task> next) {
await next();
1
2
3
// Do work that does something before
4
5
// Do work that does something after
6
}
7
}
8
BEHAVIORS
PROFILING THE PIPELINE
THE HARNESS
Compiled and executed in Release mode
Runs a few seconds and keeps overhead
minimal
Disabled Tiered JIT
<TieredCompilation>false</TieredCompilation>
Emits full symbols
<DebugType>pdbonly</DebugType
<DebugSymbols>true</DebugSymbols>
var endpointConfiguration = new EndpointConfiguration("PublishSample");
endpointConfiguration.UseSerialization<JsonSerializer>();
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.EnableInstallers();
endpointConfiguration.SendFailedMessagesTo("error");
var endpointInstance = await Endpoint.Start(endpointConfiguration);
Console.WriteLine("Attach the profiler and hit <enter>.");
Console.ReadLine();
1
2
3
4
5
6
7
8
9
10
11
12
13
var tasks = new List<Task>(1000);
14
for (int i = 0; i < 1000; i++)
15
{
16
tasks.Add(endpointInstance.Publish(new MyEvent()));
17
}
18
await Task.WhenAll(tasks);
19
20
Console.WriteLine("Publish 1000 done. Get a snapshot");
21
Console.ReadLine();
22
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
var endpointConfiguration = new EndpointConfiguration("PublishSample");
1
endpointConfiguration.UseSerialization<JsonSerializer>();
2
3
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
4
endpointConfiguration.UsePersistence<InMemoryPersistence>();
5
endpointConfiguration.EnableInstallers();
6
endpointConfiguration.SendFailedMessagesTo("error");
7
8
var endpointInstance = await Endpoint.Start(endpointConfiguration);
9
10
Console.WriteLine("Attach the profiler and hit <enter>.");
11
Console.ReadLine();
12
13
var tasks = new List<Task>(1000);
14
for (int i = 0; i < 1000; i++)
15
{
16
tasks.Add(endpointInstance.Publish(new MyEvent()));
17
}
18
await Task.WhenAll(tasks);
19
20
Console.WriteLine("Publish 1000 done. Get a snapshot");
21
Console.ReadLine();
22
endpointConfiguration.UseSerialization<JsonSerializer>();
var endpointConfiguration = new EndpointConfiguration("PublishSample");
1
2
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
3
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
4
endpointConfiguration.UsePersistence<InMemoryPersistence>();
5
endpointConfiguration.EnableInstallers();
6
endpointConfiguration.SendFailedMessagesTo("error");
7
8
var endpointInstance = await Endpoint.Start(endpointConfiguration);
9
10
Console.WriteLine("Attach the profiler and hit <enter>.");
11
Console.ReadLine();
12
13
var tasks = new List<Task>(1000);
14
for (int i = 0; i < 1000; i++)
15
{
16
tasks.Add(endpointInstance.Publish(new MyEvent()));
17
}
18
await Task.WhenAll(tasks);
19
20
Console.WriteLine("Publish 1000 done. Get a snapshot");
21
Console.ReadLine();
22
endpointConfiguration.UsePersistence<InMemoryPersistence>();
var endpointConfiguration = new EndpointConfiguration("PublishSample");
1
endpointConfiguration.UseSerialization<JsonSerializer>();
2
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
3
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
4
5
endpointConfiguration.EnableInstallers();
6
endpointConfiguration.SendFailedMessagesTo("error");
7
8
var endpointInstance = await Endpoint.Start(endpointConfiguration);
9
10
Console.WriteLine("Attach the profiler and hit <enter>.");
11
Console.ReadLine();
12
13
var tasks = new List<Task>(1000);
14
for (int i = 0; i < 1000; i++)
15
{
16
tasks.Add(endpointInstance.Publish(new MyEvent()));
17
}
18
await Task.WhenAll(tasks);
19
20
Console.WriteLine("Publish 1000 done. Get a snapshot");
21
Console.ReadLine();
22
var tasks = new List<Task>(1000);
for (int i = 0; i < 1000; i++)
{
tasks.Add(endpointInstance.Publish(new MyEvent()));
}
await Task.WhenAll(tasks);
var endpointConfiguration = new EndpointConfiguration("PublishSample");
1
endpointConfiguration.UseSerialization<JsonSerializer>();
2
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
3
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
4
endpointConfiguration.UsePersistence<InMemoryPersistence>();
5
endpointConfiguration.EnableInstallers();
6
endpointConfiguration.SendFailedMessagesTo("error");
7
8
var endpointInstance = await Endpoint.Start(endpointConfiguration);
9
10
Console.WriteLine("Attach the profiler and hit <enter>.");
11
Console.ReadLine();
12
13
14
15
16
17
18
19
20
Console.WriteLine("Publish 1000 done. Get a snapshot");
21
Console.ReadLine();
22
var endpointConfiguration = new EndpointConfiguration("PublishSample");
endpointConfiguration.UseSerialization<JsonSerializer>();
var transport = endpointConfiguration.UseTransport<MsmqTransport>();
transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample");
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.EnableInstallers();
endpointConfiguration.SendFailedMessagesTo("error");
var endpointInstance = await Endpoint.Start(endpointConfiguration);
Console.WriteLine("Attach the profiler and hit <enter>.");
Console.ReadLine();
var tasks = new List<Task>(1000);
for (int i = 0; i < 1000; i++)
{
tasks.Add(endpointInstance.Publish(new MyEvent()));
}
await Task.WhenAll(tasks);
Console.WriteLine("Publish 1000 done. Get a snapshot");
Console.ReadLine();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyEventHandler : IHandleMessages<MyEvent> {
public Task Handle(MyEvent message, IMessageHandlerContext context)
{
Console.WriteLine("Event received");
return Task.CompletedTask;
}
}
 Profiling the pipeline
PUBLISH
MEMORY CHARACTERISTICS
 Profiling the pipeline
RECEIVE
MEMORY CHARACTERISTICS
 Profiling the pipeline
BEHAVIORCHAIN
MEMORY CHARACTERISTICS
 Profiling the pipeline
CONTEXT MATTERS
MEMORY CHARACTERISTICS
 Profiling the pipeline
MEMORY CHARACTERISTICS
 Profiling the pipeline
MEMORY CHARACTERISTICS
 Profiling the pipeline
CPU CHARACTERISTICS
 Profiling the pipeline
CPU CHARACTERISTICS
 Profiling the pipeline
CPU CHARACTERISTICS
PUBLISH
 Profiling the pipeline
CPU CHARACTERISTICS
RECEIVE
 Profiling the pipeline
CPU CHARACTERISTICS
 Profiling the pipeline
TESTING
 10X faster execution with
compiled expression trees
 How we achieved 5X faster
pipeline execution by removing
closure allocations
IMPROVING
go.particular.net/webinar-2023-pipeline
BENCHMARKING THE PIPELINE
 Benchmarking the pipeline
Copy and paste relevant code
Adjust it to the bare essentials to create a
controllable environment
EXTRACT CODE
Trim down to relevant behaviors
Replaced dependency injection container
with creating relevant classes
Replaced IO-operations with completed tasks
EXTRACT CODE
 Benchmarking the pipeline
Get started with small steps
Culture change takes time
Make changes gradually
PERFORMANCE CULTURE
[ShortRunJob]
[MemoryDiagnoser]
public class PipelineExecution {
[Params(10, 20, 40)]
public int PipelineDepth { get; set; }
[GlobalSetup]
public void SetUp() {
behaviorContext = new BehaviorContext();
pipelineModificationsBeforeOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
}
pipelineModificationsAfterOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
}
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
}
[Benchmark(Baseline = true)]
public async Task Before() {
await pipelineBeforeOptimizations.Invoke(behaviorContext);
}
[Benchmark]
public async Task After() {
await pipelineAfterOptimizations.Invoke(behaviorContext);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 Benchmarking the pipeline
[GlobalSetup]
public void SetUp() {
behaviorContext = new BehaviorContext();
pipelineModificationsBeforeOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
}
pipelineModificationsAfterOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
}
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
}
[ShortRunJob]
1
[MemoryDiagnoser]
2
public class PipelineExecution {
3
4
[Params(10, 20, 40)]
5
public int PipelineDepth { get; set; }
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[Benchmark(Baseline = true)]
33
public async Task Before() {
34
await pipelineBeforeOptimizations.Invoke(behaviorContext);
35
}
36
37
[Benchmark]
38
public async Task After() {
39
await pipelineAfterOptimizations.Invoke(behaviorContext);
40
}
41
}
42
 Benchmarking the pipeline
[Params(10, 20, 40)]
public int PipelineDepth { get; set; }
for (int i = 0; i < PipelineDepth; i++)
for (int i = 0; i < PipelineDepth; i++)
[ShortRunJob]
1
[MemoryDiagnoser]
2
public class PipelineExecution {
3
4
5
6
7
8
[GlobalSetup]
9
public void SetUp() {
10
behaviorContext = new BehaviorContext();
11
12
pipelineModificationsBeforeOptimizations = new PipelineModifications();
13
14
{
15
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
16
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
17
}
18
19
pipelineModificationsAfterOptimizations = new PipelineModifications();
20
21
{
22
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
23
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
24
}
25
26
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
27
pipelineModificationsBeforeOptimizations);
28
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
29
pipelineModificationsAfterOptimizations);
30
}
31
32
[Benchmark(Baseline = true)]
33
public async Task Before() {
34
await pipelineBeforeOptimizations.Invoke(behaviorContext);
35
}
36
37
[Benchmark]
38
public async Task After() {
39
await pipelineAfterOptimizations.Invoke(behaviorContext);
40
}
41
}
42
[ShortRunJob]
[MemoryDiagnoser]
1
2
public class PipelineExecution {
3
4
[Params(10, 20, 40)]
5
public int PipelineDepth { get; set; }
6
7
8
[GlobalSetup]
9
public void SetUp() {
10
behaviorContext = new BehaviorContext();
11
12
pipelineModificationsBeforeOptimizations = new PipelineModifications();
13
for (int i = 0; i < PipelineDepth; i++)
14
{
15
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
16
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
17
}
18
19
pipelineModificationsAfterOptimizations = new PipelineModifications();
20
for (int i = 0; i < PipelineDepth; i++)
21
{
22
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
23
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
24
}
25
26
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
27
pipelineModificationsBeforeOptimizations);
28
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
29
pipelineModificationsAfterOptimizations);
30
}
31
32
[Benchmark(Baseline = true)]
33
public async Task Before() {
34
await pipelineBeforeOptimizations.Invoke(behaviorContext);
35
}
36
37
[Benchmark]
38
public async Task After() {
39
await pipelineAfterOptimizations.Invoke(behaviorContext);
40
}
41
}
42
[Benchmark(Baseline = true)]
public async Task Before() {
await pipelineBeforeOptimizations.Invoke(behaviorContext);
}
[Benchmark]
public async Task After() {
await pipelineAfterOptimizations.Invoke(behaviorContext);
}
[ShortRunJob]
1
[MemoryDiagnoser]
2
public class PipelineExecution {
3
4
[Params(10, 20, 40)]
5
public int PipelineDepth { get; set; }
6
7
8
[GlobalSetup]
9
public void SetUp() {
10
behaviorContext = new BehaviorContext();
11
12
pipelineModificationsBeforeOptimizations = new PipelineModifications();
13
for (int i = 0; i < PipelineDepth; i++)
14
{
15
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
16
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
17
}
18
19
pipelineModificationsAfterOptimizations = new PipelineModifications();
20
for (int i = 0; i < PipelineDepth; i++)
21
{
22
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
23
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
24
}
25
26
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
27
pipelineModificationsBeforeOptimizations);
28
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
29
pipelineModificationsAfterOptimizations);
30
}
31
32
33
34
35
36
37
38
39
40
41
}
42
[ShortRunJob]
[MemoryDiagnoser]
public class PipelineExecution {
[Params(10, 20, 40)]
public int PipelineDepth { get; set; }
[GlobalSetup]
public void SetUp() {
behaviorContext = new BehaviorContext();
pipelineModificationsBeforeOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior()));
}
pipelineModificationsAfterOptimizations = new PipelineModifications();
for (int i = 0; i < PipelineDepth; i++)
{
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(),
typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization()));
}
pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
}
[Benchmark(Baseline = true)]
public async Task Before() {
await pipelineBeforeOptimizations.Invoke(behaviorContext);
}
[Benchmark]
public async Task After() {
await pipelineAfterOptimizations.Invoke(behaviorContext);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 Benchmarking the pipeline
Single Responsibility Principle
No side effects
Prevents dead code elimination
Delegates heavy lifting to the
framework
Is explicit
No implicit casting
No var
Avoid running any other resource-
heavy processes while benchmarking
PRACTICES
 Benchmarking the pipeline
Benchmarking is really hard
BenchmarkDotNet will protect you from the common pitfalls
because it does all the dirty work for you
[ShortRunJob]
[MemoryDiagnoser]
public class Step1_PipelineWarmup {
// rest almost the same
[Benchmark(Baseline = true)]
public BaseLinePipeline<IBehaviorContext> Before() {
var pipelineBeforeOptimizations = new
BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
return pipelineBeforeOptimizations;
}
[Benchmark]
public PipelineOptimization<IBehaviorContext> After() {
var pipelineAfterOptimizations = new
PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
return pipelineAfterOptimizations;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 Benchmarking the pipeline
[ShortRunJob]
[MemoryDiagnoser]
public class Step2_PipelineException {
[GlobalSetup]
public void SetUp() {
...
var stepdId = PipelineDepth + 1;
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepdId.ToString(), typeof(Throwing), "1", b
=> new Throwing()));
...
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdId.ToString(), typeof(Throwing), "1", b
=> new Throwing()));
pipelineBeforeOptimizations = new Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
}
[Benchmark(Baseline = true)]
public async Task Before() {
try
{
await pipelineBeforeOptimizations.Invoke(behaviorContext).ConfigureAwait(false);
}
catch (InvalidOperationException)
{
}
}
[Benchmark]
public async Task After() {
try
{
await pipelineAfterOptimizations.Invoke(behaviorContext).ConfigureAwait(false);
}
catch (InvalidOperationException)
{
}
}
class Throwing : Behavior<IBehaviorContext> {
public override Task Invoke(IBehaviorContext context, Func<Task> next)
{
throw new InvalidOperationException();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 Benchmarking the pipeline
[ShortRunJob]
[MemoryDiagnoser]
public class Step2_PipelineException {
[GlobalSetup]
public void SetUp() {
...
var stepdId = PipelineDepth + 1;
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepd
Id.ToString(), typeof(Throwing), "1", b => new Throwing()));
...
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdI
d.ToString(), typeof(Throwing), "1", b => new Throwing()));
pipelineBeforeOptimizations = new
Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
pipelineModificationsBeforeOptimizations);
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>
(null, new SettingsHolder(),
pipelineModificationsAfterOptimizations);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepd
Id.ToString(), typeof(Throwing), "1", b => new Throwing()));
pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdI
d.ToString(), typeof(Throwing), "1", b => new Throwing()));
[ShortRunJob]
1
[MemoryDiagnoser]
2
public class Step2_PipelineException {
3
[GlobalSetup]
4
public void SetUp() {
5
...
6
var stepdId = PipelineDepth + 1;
7
8
9
...
10
11
12
pipelineBeforeOptimizations = new
Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(),
13
pipelineModificationsBeforeOptimizations);
14
pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>
(null, new SettingsHolder(),
15
pipelineModificationsAfterOptimizations);
16
}
17
}
18
 Benchmarking the pipeline
[ShortRunJob]
[MemoryDiagnoser]
public class Step2_PipelineException {
[GlobalSetup]
public void SetUp() {
...
}
[Benchmark(Baseline = true)]
public async Task Before() {
try
{
await pipelineBeforeOptimizations.Invoke(behaviorContext);
}
catch (InvalidOperationException)
{
}
}
[Benchmark]
public async Task After() {
try
{
await pipelineAfterOptimizations.Invoke(behaviorContext);
}
catch (InvalidOperationException)
{
}
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 Benchmarking the pipeline
 Benchmarking the pipeline
PROFILING THE PIPELINE (AGAIN)
 Profiling the pipeline (again)
PUBLISH
MEMORY CHARACTERISTICS
BEFORE AFTER
RECEIVE
MEMORY CHARACTERISTICS
 Profiling the pipeline (again)
AFTER
BEFORE
MEMORY CHARACTERISTICS
 Profiling the pipeline (again)
RECEIVE AFTER
BEFORE
MEMORY CHARACTERISTICS
 Profiling the pipeline (again)
AFTER
BEFORE
CPU CHARACTERISTICS
PUBLISH
 Profiling the pipeline (again)
CPU CHARACTERISTICS
RECEIVE
 Profiling the pipeline (again)
NServiceBus Pipeline
NServiceBus Transport
MSMQ
GETTING LOWER ON THE STACK
NServiceBus Pipeline
NServiceBus Transport
Azure.Messaging.ServiceBus
Microsoft.Azure.Amqp
GETTING LOWER ON THE STACK
THE HARNESS
await using var serviceBusClient = new ServiceBusClient(connectionString);
await using var sender = serviceBusClient.CreateSender(destination);
var messages = new List<ServiceBusMessage>(1000);
for (int i = 0; i < 1000; i++) {
messages.Add(new ServiceBusMessage(UTF8.GetBytes($"Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i}")));
if (i % 100 == 0) {
await sender.SendMessagesAsync(messages);
messages.Clear();
}
}
await sender.SendMessagesAsync(messages);
WriteLine("Messages sent");
Console.WriteLine("Take snapshot");
Console.ReadLine();
var countDownEvent = new CountdownEvent(1000);
var processorOptions = new ServiceBusProcessorOptions {
AutoCompleteMessages = true,
MaxConcurrentCalls = 100,
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10),
ReceiveMode = ServiceBusReceiveMode.PeekLock,
};
await using var receiver = serviceBusClient.CreateProcessor(destination, processorOptions);
receiver.ProcessMessageAsync += async messageEventArgs => {
var message = messageEventArgs.Message;
await Out.WriteLineAsync(
$"Received message with '{message.MessageId}' and content '{UTF8.GetString(message.Body)}' / binary {message.Body}");
countDownEvent.Signal();
};
// rest omitted
await receiver.StartProcessingAsync();
countDownEvent.Wait();
Console.WriteLine("Take snapshot");
Console.ReadLine();
await receiver.StopProcessingAsync();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Getting lower on the stack
THE HARNESS
await using var serviceBusClient = new ServiceBusClient(connectionString);
await using var sender = serviceBusClient.CreateSender(destination);
var messages = new List<ServiceBusMessage>(1000);
for (int i = 0; i < 1000; i++) {
messages.Add(new ServiceBusMessage(UTF8.GetBytes($"Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep
Dive {i} Deep Dive {i}")));
if (i % 100 == 0) {
await sender.SendMessagesAsync(messages);
messages.Clear();
}
}
await sender.SendMessagesAsync(messages);
WriteLine("Messages sent");
Console.WriteLine("Take snapshot");
Console.ReadLine();
Getting lower on the stack
THE HARNESS
receiver.ProcessMessageAsync += async messageEventArgs => {
var message = messageEventArgs.Message;
await Out.WriteLineAsync(
$"Received message with '{message.MessageId}' and content '{UTF8.GetString(message.Body)}' / binary
{message.Body}");
countDownEvent.Signal();
};
var countDownEvent = new CountdownEvent(1000);
1
2
var processorOptions = new ServiceBusProcessorOptions
3
{
4
AutoCompleteMessages = true,
5
MaxConcurrentCalls = 100,
6
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10),
7
ReceiveMode = ServiceBusReceiveMode.PeekLock,
8
};
9
10
await using var receiver = serviceBusClient.CreateProcessor(destination, processorOptions);
11
12
13
14
15
16
17
// rest omitted
18
await receiver.StartProcessingAsync();
19
20
countDownEvent.Wait();
21
22
Console.WriteLine("Take snapshot");
23
Console.ReadLine();
24
25
await receiver.StopProcessingAsync();
26
Getting lower on the stack
MEMORY CHARACTERISTICS
Getting lower on the stack
MEMORY CHARACTERISTICS
Getting lower on the stack
PREVENTING REGRESSIONS
C:ProjectsperformancesrctoolsResultsComparer>
dotnet run --base "C:resultsbefore"
--diff "C:resultsafter" --threshold 2%
1
2
Guidance
Tool
Preventing Regressions
ResultComparer C:Projectsperformancesrcbenchmarksmicro>
dotnet run -c Release -f net8.0 
--artifacts "C:resultsbefore"
1
2
C:Projectsperformancesrcbenchmarksmicro>
dotnet run -c Release -f net8.0 
--artifacts "C:resultsafter"
1
2
"Two subsequent builds on the same revision can have ranges of
1.5..2 seconds and 12..36 seconds. CPU-bound benchmarks are
much more stable than Memory/Disk-bound benchmarks, but
the “average” performance levels still can be up to three times
different across builds."
Andrey Akinshin - Performance stability of GitHub Actions
BEYOND SIMPLE BENCHMARKS
A PRACTICAL GUIDE TO OPTIMIZING CODE
 | ✉ | 
danielmarbach daniel.marbach@particular.net Daniel Marbach
github.com/danielmarbach/BeyondSimpleBenchmarks
Use the performance loop to improve your code where it
matters
Combine it with profiling to observe how the small changes
add up
Optimize until you hit a diminishing point of return
You'll learn a ton about potential improvements for a new
design

More Related Content

Similar to Beyond simple benchmarks—a practical guide to optimizing code

Samsung WebCL Prototype API
Samsung WebCL Prototype APISamsung WebCL Prototype API
Samsung WebCL Prototype API
Ryo Jin
 

Similar to Beyond simple benchmarks—a practical guide to optimizing code (20)

[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
 
C#on linux
C#on linuxC#on linux
C#on linux
 
Better Open Source Enterprise C++ Web Services
Better Open Source Enterprise C++ Web ServicesBetter Open Source Enterprise C++ Web Services
Better Open Source Enterprise C++ Web Services
 
maxbox starter72 multilanguage coding
maxbox starter72 multilanguage codingmaxbox starter72 multilanguage coding
maxbox starter72 multilanguage coding
 
Angular 1 + es6
Angular 1 + es6Angular 1 + es6
Angular 1 + es6
 
Advanced iOS Debbuging (Reloaded)
Advanced iOS Debbuging (Reloaded)Advanced iOS Debbuging (Reloaded)
Advanced iOS Debbuging (Reloaded)
 
Samsung WebCL Prototype API
Samsung WebCL Prototype APISamsung WebCL Prototype API
Samsung WebCL Prototype API
 
Nagios Conference 2012 - Dave Josephsen - Stop Being Lazy
Nagios Conference 2012 - Dave Josephsen - Stop Being LazyNagios Conference 2012 - Dave Josephsen - Stop Being Lazy
Nagios Conference 2012 - Dave Josephsen - Stop Being Lazy
 
[W3C HTML5 2016] Angular + ES6
[W3C HTML5 2016] Angular + ES6[W3C HTML5 2016] Angular + ES6
[W3C HTML5 2016] Angular + ES6
 
2016 W3C Conference #4 : ANGULAR + ES6
2016 W3C Conference #4 : ANGULAR + ES62016 W3C Conference #4 : ANGULAR + ES6
2016 W3C Conference #4 : ANGULAR + ES6
 
Taming startup dynamics - Magnus Jungsbluth & Domagoj Cosic
Taming startup dynamics - Magnus Jungsbluth & Domagoj CosicTaming startup dynamics - Magnus Jungsbluth & Domagoj Cosic
Taming startup dynamics - Magnus Jungsbluth & Domagoj Cosic
 
Running Vue Storefront in production (PWA Magento webshop)
Running Vue Storefront in production (PWA Magento webshop)Running Vue Storefront in production (PWA Magento webshop)
Running Vue Storefront in production (PWA Magento webshop)
 
How to make a high-quality Node.js app, Nikita Galkin
How to make a high-quality Node.js app, Nikita GalkinHow to make a high-quality Node.js app, Nikita Galkin
How to make a high-quality Node.js app, Nikita Galkin
 
"Load Testing Distributed Systems with NBomber 4.0", Anton Moldovan
"Load Testing Distributed Systems with NBomber 4.0",  Anton Moldovan"Load Testing Distributed Systems with NBomber 4.0",  Anton Moldovan
"Load Testing Distributed Systems with NBomber 4.0", Anton Moldovan
 
PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerC...
PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerC...PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerC...
PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerC...
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
 
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
 
Ceilometer + Heat = Alarming
Ceilometer + Heat = Alarming Ceilometer + Heat = Alarming
Ceilometer + Heat = Alarming
 
TestUpload
TestUploadTestUpload
TestUpload
 
Building a Serverless company with Node.js, React and the Serverless Framewor...
Building a Serverless company with Node.js, React and the Serverless Framewor...Building a Serverless company with Node.js, React and the Serverless Framewor...
Building a Serverless company with Node.js, React and the Serverless Framewor...
 

More from Particular Software

More from Particular Software (20)

Scaling for Success: Lessons from handling peak loads on Azure with NServiceBus
Scaling for Success: Lessons from handling peak loads on Azure with NServiceBusScaling for Success: Lessons from handling peak loads on Azure with NServiceBus
Scaling for Success: Lessons from handling peak loads on Azure with NServiceBus
 
An exception occurred - Please try again
An exception occurred - Please try againAn exception occurred - Please try again
An exception occurred - Please try again
 
Tales from the trenches creating complex distributed systems
Tales from the trenches  creating complex distributed systemsTales from the trenches  creating complex distributed systems
Tales from the trenches creating complex distributed systems
 
Got the time?
Got the time?Got the time?
Got the time?
 
Implementing outbox model-checking first
Implementing outbox   model-checking firstImplementing outbox   model-checking first
Implementing outbox model-checking first
 
Reports from the field azure functions in practice
Reports from the field   azure functions in practiceReports from the field   azure functions in practice
Reports from the field azure functions in practice
 
Finding your service boundaries - a practical guide
Finding your service boundaries - a practical guideFinding your service boundaries - a practical guide
Finding your service boundaries - a practical guide
 
Decomposing .NET Monoliths with NServiceBus and Docker
Decomposing .NET Monoliths with NServiceBus and DockerDecomposing .NET Monoliths with NServiceBus and Docker
Decomposing .NET Monoliths with NServiceBus and Docker
 
DIY Async Message Pump: Lessons from the trenches
DIY Async Message Pump: Lessons from the trenchesDIY Async Message Pump: Lessons from the trenches
DIY Async Message Pump: Lessons from the trenches
 
Share the insight of ServiceInsight
Share the insight of ServiceInsightShare the insight of ServiceInsight
Share the insight of ServiceInsight
 
What to consider when monitoring microservices
What to consider when monitoring microservicesWhat to consider when monitoring microservices
What to consider when monitoring microservices
 
Making communications across boundaries simple with NServiceBus
Making communications across boundaries simple with NServiceBusMaking communications across boundaries simple with NServiceBus
Making communications across boundaries simple with NServiceBus
 
Making communication across boundaries simple with Azure Service Bus
Making communication across boundaries simple with Azure Service BusMaking communication across boundaries simple with Azure Service Bus
Making communication across boundaries simple with Azure Service Bus
 
How to avoid microservice pitfalls
How to avoid microservice pitfallsHow to avoid microservice pitfalls
How to avoid microservice pitfalls
 
Connect front end to back end using SignalR and Messaging
Connect front end to back end using SignalR and MessagingConnect front end to back end using SignalR and Messaging
Connect front end to back end using SignalR and Messaging
 
Async/Await: NServiceBus v6 API Update
Async/Await: NServiceBus v6 API UpdateAsync/Await: NServiceBus v6 API Update
Async/Await: NServiceBus v6 API Update
 
Async/Await: TPL & Message Pumps
Async/Await: TPL & Message Pumps Async/Await: TPL & Message Pumps
Async/Await: TPL & Message Pumps
 
Async/Await Best Practices
Async/Await Best PracticesAsync/Await Best Practices
Async/Await Best Practices
 
Making workflow implementation easy with CQRS
Making workflow implementation easy with CQRSMaking workflow implementation easy with CQRS
Making workflow implementation easy with CQRS
 
Cqrs but different
Cqrs but differentCqrs but different
Cqrs but different
 

Recently uploaded

TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 

Recently uploaded (20)

TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 

Beyond simple benchmarks—a practical guide to optimizing code

  • 1. BEYOND SIMPLE BENCHMARKS A PRACTICAL GUIDE TO OPTIMIZING CODE  | ✉ |  danielmarbach daniel.marbach@particular.net Daniel Marbach
  • 3.
  • 4. Microsoft Teams’ Infrastructure and Azure Communication Services’ Journey to .NET 6 "We were able to see Azure Compute cost reduction of up to 50% per month, on average we observed 24% monthly cost reduction after migrating to .NET 6. The reduction in cores reduced Azure spend by 24%."
  • 7. BE CURIOUS.... UNDERSTAND THE CONTEXT How is this code going to be executed at scale, and what would the memory characteristics be (gut feeling) Are there simple low-hanging fruits I can apply to accelerate this code? Are there things I can move away from the hot path by simply restructuring a bit my code? What part is under my control and what isn't really? What optimizations can I apply, and when should I stop?
  • 8. THE PERFORMANCE LOOP Profile at least CPU and memory using a profiling harness Improve parts of the hot path Benchmark and compare Profile improvements again with the harness and make adjustments where necessary Ship and focus your attention to other parts
  • 11. ASP.NET CORE MIDDLEWARE public class RequestCultureMiddleware { _next = next; public async Task InvokeAsync(HttpContext context) { await _next(context); 1 private readonly RequestDelegate _next; 2 3 public RequestCultureMiddleware(RequestDelegate next) { 4 5 } 6 7 8 // Do work that does something before 9 10 // Do work that does something after 11 } 12 } 13
  • 12. public class Behavior : Behavior<IIncomingLogicalMessageContext> { public override Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next) { await next(); 1 2 3 // Do work that does something before 4 5 // Do work that does something after 6 } 7 } 8 BEHAVIORS
  • 14. THE HARNESS Compiled and executed in Release mode Runs a few seconds and keeps overhead minimal Disabled Tiered JIT <TieredCompilation>false</TieredCompilation> Emits full symbols <DebugType>pdbonly</DebugType <DebugSymbols>true</DebugSymbols> var endpointConfiguration = new EndpointConfiguration("PublishSample"); endpointConfiguration.UseSerialization<JsonSerializer>(); var transport = endpointConfiguration.UseTransport<MsmqTransport>(); transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); endpointConfiguration.UsePersistence<InMemoryPersistence>(); endpointConfiguration.EnableInstallers(); endpointConfiguration.SendFailedMessagesTo("error"); var endpointInstance = await Endpoint.Start(endpointConfiguration); Console.WriteLine("Attach the profiler and hit <enter>."); Console.ReadLine(); 1 2 3 4 5 6 7 8 9 10 11 12 13 var tasks = new List<Task>(1000); 14 for (int i = 0; i < 1000; i++) 15 { 16 tasks.Add(endpointInstance.Publish(new MyEvent())); 17 } 18 await Task.WhenAll(tasks); 19 20 Console.WriteLine("Publish 1000 done. Get a snapshot"); 21 Console.ReadLine(); 22 var transport = endpointConfiguration.UseTransport<MsmqTransport>(); var endpointConfiguration = new EndpointConfiguration("PublishSample"); 1 endpointConfiguration.UseSerialization<JsonSerializer>(); 2 3 transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); 4 endpointConfiguration.UsePersistence<InMemoryPersistence>(); 5 endpointConfiguration.EnableInstallers(); 6 endpointConfiguration.SendFailedMessagesTo("error"); 7 8 var endpointInstance = await Endpoint.Start(endpointConfiguration); 9 10 Console.WriteLine("Attach the profiler and hit <enter>."); 11 Console.ReadLine(); 12 13 var tasks = new List<Task>(1000); 14 for (int i = 0; i < 1000; i++) 15 { 16 tasks.Add(endpointInstance.Publish(new MyEvent())); 17 } 18 await Task.WhenAll(tasks); 19 20 Console.WriteLine("Publish 1000 done. Get a snapshot"); 21 Console.ReadLine(); 22 endpointConfiguration.UseSerialization<JsonSerializer>(); var endpointConfiguration = new EndpointConfiguration("PublishSample"); 1 2 var transport = endpointConfiguration.UseTransport<MsmqTransport>(); 3 transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); 4 endpointConfiguration.UsePersistence<InMemoryPersistence>(); 5 endpointConfiguration.EnableInstallers(); 6 endpointConfiguration.SendFailedMessagesTo("error"); 7 8 var endpointInstance = await Endpoint.Start(endpointConfiguration); 9 10 Console.WriteLine("Attach the profiler and hit <enter>."); 11 Console.ReadLine(); 12 13 var tasks = new List<Task>(1000); 14 for (int i = 0; i < 1000; i++) 15 { 16 tasks.Add(endpointInstance.Publish(new MyEvent())); 17 } 18 await Task.WhenAll(tasks); 19 20 Console.WriteLine("Publish 1000 done. Get a snapshot"); 21 Console.ReadLine(); 22 endpointConfiguration.UsePersistence<InMemoryPersistence>(); var endpointConfiguration = new EndpointConfiguration("PublishSample"); 1 endpointConfiguration.UseSerialization<JsonSerializer>(); 2 var transport = endpointConfiguration.UseTransport<MsmqTransport>(); 3 transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); 4 5 endpointConfiguration.EnableInstallers(); 6 endpointConfiguration.SendFailedMessagesTo("error"); 7 8 var endpointInstance = await Endpoint.Start(endpointConfiguration); 9 10 Console.WriteLine("Attach the profiler and hit <enter>."); 11 Console.ReadLine(); 12 13 var tasks = new List<Task>(1000); 14 for (int i = 0; i < 1000; i++) 15 { 16 tasks.Add(endpointInstance.Publish(new MyEvent())); 17 } 18 await Task.WhenAll(tasks); 19 20 Console.WriteLine("Publish 1000 done. Get a snapshot"); 21 Console.ReadLine(); 22 var tasks = new List<Task>(1000); for (int i = 0; i < 1000; i++) { tasks.Add(endpointInstance.Publish(new MyEvent())); } await Task.WhenAll(tasks); var endpointConfiguration = new EndpointConfiguration("PublishSample"); 1 endpointConfiguration.UseSerialization<JsonSerializer>(); 2 var transport = endpointConfiguration.UseTransport<MsmqTransport>(); 3 transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); 4 endpointConfiguration.UsePersistence<InMemoryPersistence>(); 5 endpointConfiguration.EnableInstallers(); 6 endpointConfiguration.SendFailedMessagesTo("error"); 7 8 var endpointInstance = await Endpoint.Start(endpointConfiguration); 9 10 Console.WriteLine("Attach the profiler and hit <enter>."); 11 Console.ReadLine(); 12 13 14 15 16 17 18 19 20 Console.WriteLine("Publish 1000 done. Get a snapshot"); 21 Console.ReadLine(); 22 var endpointConfiguration = new EndpointConfiguration("PublishSample"); endpointConfiguration.UseSerialization<JsonSerializer>(); var transport = endpointConfiguration.UseTransport<MsmqTransport>(); transport.Routing().RegisterPublisher(typeof(MyEvent), "PublishSample"); endpointConfiguration.UsePersistence<InMemoryPersistence>(); endpointConfiguration.EnableInstallers(); endpointConfiguration.SendFailedMessagesTo("error"); var endpointInstance = await Endpoint.Start(endpointConfiguration); Console.WriteLine("Attach the profiler and hit <enter>."); Console.ReadLine(); var tasks = new List<Task>(1000); for (int i = 0; i < 1000; i++) { tasks.Add(endpointInstance.Publish(new MyEvent())); } await Task.WhenAll(tasks); Console.WriteLine("Publish 1000 done. Get a snapshot"); Console.ReadLine(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyEventHandler : IHandleMessages<MyEvent> { public Task Handle(MyEvent message, IMessageHandlerContext context) { Console.WriteLine("Event received"); return Task.CompletedTask; } }  Profiling the pipeline
  • 27.  10X faster execution with compiled expression trees  How we achieved 5X faster pipeline execution by removing closure allocations IMPROVING go.particular.net/webinar-2023-pipeline
  • 29.  Benchmarking the pipeline Copy and paste relevant code Adjust it to the bare essentials to create a controllable environment EXTRACT CODE
  • 30. Trim down to relevant behaviors Replaced dependency injection container with creating relevant classes Replaced IO-operations with completed tasks EXTRACT CODE  Benchmarking the pipeline
  • 31. Get started with small steps Culture change takes time Make changes gradually PERFORMANCE CULTURE
  • 32. [ShortRunJob] [MemoryDiagnoser] public class PipelineExecution { [Params(10, 20, 40)] public int PipelineDepth { get; set; } [GlobalSetup] public void SetUp() { behaviorContext = new BehaviorContext(); pipelineModificationsBeforeOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); } pipelineModificationsAfterOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); } pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsAfterOptimizations); } [Benchmark(Baseline = true)] public async Task Before() { await pipelineBeforeOptimizations.Invoke(behaviorContext); } [Benchmark] public async Task After() { await pipelineAfterOptimizations.Invoke(behaviorContext); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42  Benchmarking the pipeline
  • 33. [GlobalSetup] public void SetUp() { behaviorContext = new BehaviorContext(); pipelineModificationsBeforeOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); } pipelineModificationsAfterOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); } pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsAfterOptimizations); } [ShortRunJob] 1 [MemoryDiagnoser] 2 public class PipelineExecution { 3 4 [Params(10, 20, 40)] 5 public int PipelineDepth { get; set; } 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [Benchmark(Baseline = true)] 33 public async Task Before() { 34 await pipelineBeforeOptimizations.Invoke(behaviorContext); 35 } 36 37 [Benchmark] 38 public async Task After() { 39 await pipelineAfterOptimizations.Invoke(behaviorContext); 40 } 41 } 42  Benchmarking the pipeline
  • 34. [Params(10, 20, 40)] public int PipelineDepth { get; set; } for (int i = 0; i < PipelineDepth; i++) for (int i = 0; i < PipelineDepth; i++) [ShortRunJob] 1 [MemoryDiagnoser] 2 public class PipelineExecution { 3 4 5 6 7 8 [GlobalSetup] 9 public void SetUp() { 10 behaviorContext = new BehaviorContext(); 11 12 pipelineModificationsBeforeOptimizations = new PipelineModifications(); 13 14 { 15 pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 16 typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); 17 } 18 19 pipelineModificationsAfterOptimizations = new PipelineModifications(); 20 21 { 22 pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 23 typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); 24 } 25 26 pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), 27 pipelineModificationsBeforeOptimizations); 28 pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), 29 pipelineModificationsAfterOptimizations); 30 } 31 32 [Benchmark(Baseline = true)] 33 public async Task Before() { 34 await pipelineBeforeOptimizations.Invoke(behaviorContext); 35 } 36 37 [Benchmark] 38 public async Task After() { 39 await pipelineAfterOptimizations.Invoke(behaviorContext); 40 } 41 } 42 [ShortRunJob] [MemoryDiagnoser] 1 2 public class PipelineExecution { 3 4 [Params(10, 20, 40)] 5 public int PipelineDepth { get; set; } 6 7 8 [GlobalSetup] 9 public void SetUp() { 10 behaviorContext = new BehaviorContext(); 11 12 pipelineModificationsBeforeOptimizations = new PipelineModifications(); 13 for (int i = 0; i < PipelineDepth; i++) 14 { 15 pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 16 typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); 17 } 18 19 pipelineModificationsAfterOptimizations = new PipelineModifications(); 20 for (int i = 0; i < PipelineDepth; i++) 21 { 22 pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 23 typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); 24 } 25 26 pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), 27 pipelineModificationsBeforeOptimizations); 28 pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), 29 pipelineModificationsAfterOptimizations); 30 } 31 32 [Benchmark(Baseline = true)] 33 public async Task Before() { 34 await pipelineBeforeOptimizations.Invoke(behaviorContext); 35 } 36 37 [Benchmark] 38 public async Task After() { 39 await pipelineAfterOptimizations.Invoke(behaviorContext); 40 } 41 } 42 [Benchmark(Baseline = true)] public async Task Before() { await pipelineBeforeOptimizations.Invoke(behaviorContext); } [Benchmark] public async Task After() { await pipelineAfterOptimizations.Invoke(behaviorContext); } [ShortRunJob] 1 [MemoryDiagnoser] 2 public class PipelineExecution { 3 4 [Params(10, 20, 40)] 5 public int PipelineDepth { get; set; } 6 7 8 [GlobalSetup] 9 public void SetUp() { 10 behaviorContext = new BehaviorContext(); 11 12 pipelineModificationsBeforeOptimizations = new PipelineModifications(); 13 for (int i = 0; i < PipelineDepth; i++) 14 { 15 pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 16 typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); 17 } 18 19 pipelineModificationsAfterOptimizations = new PipelineModifications(); 20 for (int i = 0; i < PipelineDepth; i++) 21 { 22 pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), 23 typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); 24 } 25 26 pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), 27 pipelineModificationsBeforeOptimizations); 28 pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), 29 pipelineModificationsAfterOptimizations); 30 } 31 32 33 34 35 36 37 38 39 40 41 } 42 [ShortRunJob] [MemoryDiagnoser] public class PipelineExecution { [Params(10, 20, 40)] public int PipelineDepth { get; set; } [GlobalSetup] public void SetUp() { behaviorContext = new BehaviorContext(); pipelineModificationsBeforeOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BaseLineBehavior), i.ToString(), b => new BaseLineBehavior())); } pipelineModificationsAfterOptimizations = new PipelineModifications(); for (int i = 0; i < PipelineDepth; i++) { pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(i.ToString(), typeof(BehaviorOptimization), i.ToString(), b => new BehaviorOptimization())); } pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsAfterOptimizations); } [Benchmark(Baseline = true)] public async Task Before() { await pipelineBeforeOptimizations.Invoke(behaviorContext); } [Benchmark] public async Task After() { await pipelineAfterOptimizations.Invoke(behaviorContext); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42  Benchmarking the pipeline
  • 35. Single Responsibility Principle No side effects Prevents dead code elimination Delegates heavy lifting to the framework Is explicit No implicit casting No var Avoid running any other resource- heavy processes while benchmarking PRACTICES  Benchmarking the pipeline
  • 36. Benchmarking is really hard BenchmarkDotNet will protect you from the common pitfalls because it does all the dirty work for you
  • 37. [ShortRunJob] [MemoryDiagnoser] public class Step1_PipelineWarmup { // rest almost the same [Benchmark(Baseline = true)] public BaseLinePipeline<IBehaviorContext> Before() { var pipelineBeforeOptimizations = new BaseLinePipeline<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); return pipelineBeforeOptimizations; } [Benchmark] public PipelineOptimization<IBehaviorContext> After() { var pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsAfterOptimizations); return pipelineAfterOptimizations; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  Benchmarking the pipeline
  • 38. [ShortRunJob] [MemoryDiagnoser] public class Step2_PipelineException { [GlobalSetup] public void SetUp() { ... var stepdId = PipelineDepth + 1; pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepdId.ToString(), typeof(Throwing), "1", b => new Throwing())); ... pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdId.ToString(), typeof(Throwing), "1", b => new Throwing())); pipelineBeforeOptimizations = new Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsAfterOptimizations); } [Benchmark(Baseline = true)] public async Task Before() { try { await pipelineBeforeOptimizations.Invoke(behaviorContext).ConfigureAwait(false); } catch (InvalidOperationException) { } } [Benchmark] public async Task After() { try { await pipelineAfterOptimizations.Invoke(behaviorContext).ConfigureAwait(false); } catch (InvalidOperationException) { } } class Throwing : Behavior<IBehaviorContext> { public override Task Invoke(IBehaviorContext context, Func<Task> next) { throw new InvalidOperationException(); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47  Benchmarking the pipeline
  • 39. [ShortRunJob] [MemoryDiagnoser] public class Step2_PipelineException { [GlobalSetup] public void SetUp() { ... var stepdId = PipelineDepth + 1; pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepd Id.ToString(), typeof(Throwing), "1", b => new Throwing())); ... pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdI d.ToString(), typeof(Throwing), "1", b => new Throwing())); pipelineBeforeOptimizations = new Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), pipelineModificationsBeforeOptimizations); pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext> (null, new SettingsHolder(), pipelineModificationsAfterOptimizations); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pipelineModificationsBeforeOptimizations.Additions.Add(RegisterStep.Create(stepd Id.ToString(), typeof(Throwing), "1", b => new Throwing())); pipelineModificationsAfterOptimizations.Additions.Add(RegisterStep.Create(stepdI d.ToString(), typeof(Throwing), "1", b => new Throwing())); [ShortRunJob] 1 [MemoryDiagnoser] 2 public class Step2_PipelineException { 3 [GlobalSetup] 4 public void SetUp() { 5 ... 6 var stepdId = PipelineDepth + 1; 7 8 9 ... 10 11 12 pipelineBeforeOptimizations = new Step1.PipelineOptimization<IBehaviorContext>(null, new SettingsHolder(), 13 pipelineModificationsBeforeOptimizations); 14 pipelineAfterOptimizations = new PipelineOptimization<IBehaviorContext> (null, new SettingsHolder(), 15 pipelineModificationsAfterOptimizations); 16 } 17 } 18  Benchmarking the pipeline
  • 40. [ShortRunJob] [MemoryDiagnoser] public class Step2_PipelineException { [GlobalSetup] public void SetUp() { ... } [Benchmark(Baseline = true)] public async Task Before() { try { await pipelineBeforeOptimizations.Invoke(behaviorContext); } catch (InvalidOperationException) { } } [Benchmark] public async Task After() { try { await pipelineAfterOptimizations.Invoke(behaviorContext); } catch (InvalidOperationException) { } } ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  Benchmarking the pipeline
  • 43.  Profiling the pipeline (again) PUBLISH MEMORY CHARACTERISTICS BEFORE AFTER
  • 44. RECEIVE MEMORY CHARACTERISTICS  Profiling the pipeline (again) AFTER BEFORE
  • 45. MEMORY CHARACTERISTICS  Profiling the pipeline (again) RECEIVE AFTER BEFORE
  • 46. MEMORY CHARACTERISTICS  Profiling the pipeline (again) AFTER BEFORE
  • 51. THE HARNESS await using var serviceBusClient = new ServiceBusClient(connectionString); await using var sender = serviceBusClient.CreateSender(destination); var messages = new List<ServiceBusMessage>(1000); for (int i = 0; i < 1000; i++) { messages.Add(new ServiceBusMessage(UTF8.GetBytes($"Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i}"))); if (i % 100 == 0) { await sender.SendMessagesAsync(messages); messages.Clear(); } } await sender.SendMessagesAsync(messages); WriteLine("Messages sent"); Console.WriteLine("Take snapshot"); Console.ReadLine(); var countDownEvent = new CountdownEvent(1000); var processorOptions = new ServiceBusProcessorOptions { AutoCompleteMessages = true, MaxConcurrentCalls = 100, MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10), ReceiveMode = ServiceBusReceiveMode.PeekLock, }; await using var receiver = serviceBusClient.CreateProcessor(destination, processorOptions); receiver.ProcessMessageAsync += async messageEventArgs => { var message = messageEventArgs.Message; await Out.WriteLineAsync( $"Received message with '{message.MessageId}' and content '{UTF8.GetString(message.Body)}' / binary {message.Body}"); countDownEvent.Signal(); }; // rest omitted await receiver.StartProcessingAsync(); countDownEvent.Wait(); Console.WriteLine("Take snapshot"); Console.ReadLine(); await receiver.StopProcessingAsync(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Getting lower on the stack
  • 52. THE HARNESS await using var serviceBusClient = new ServiceBusClient(connectionString); await using var sender = serviceBusClient.CreateSender(destination); var messages = new List<ServiceBusMessage>(1000); for (int i = 0; i < 1000; i++) { messages.Add(new ServiceBusMessage(UTF8.GetBytes($"Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i} Deep Dive {i}"))); if (i % 100 == 0) { await sender.SendMessagesAsync(messages); messages.Clear(); } } await sender.SendMessagesAsync(messages); WriteLine("Messages sent"); Console.WriteLine("Take snapshot"); Console.ReadLine(); Getting lower on the stack
  • 53. THE HARNESS receiver.ProcessMessageAsync += async messageEventArgs => { var message = messageEventArgs.Message; await Out.WriteLineAsync( $"Received message with '{message.MessageId}' and content '{UTF8.GetString(message.Body)}' / binary {message.Body}"); countDownEvent.Signal(); }; var countDownEvent = new CountdownEvent(1000); 1 2 var processorOptions = new ServiceBusProcessorOptions 3 { 4 AutoCompleteMessages = true, 5 MaxConcurrentCalls = 100, 6 MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10), 7 ReceiveMode = ServiceBusReceiveMode.PeekLock, 8 }; 9 10 await using var receiver = serviceBusClient.CreateProcessor(destination, processorOptions); 11 12 13 14 15 16 17 // rest omitted 18 await receiver.StartProcessingAsync(); 19 20 countDownEvent.Wait(); 21 22 Console.WriteLine("Take snapshot"); 23 Console.ReadLine(); 24 25 await receiver.StopProcessingAsync(); 26 Getting lower on the stack
  • 56. PREVENTING REGRESSIONS C:ProjectsperformancesrctoolsResultsComparer> dotnet run --base "C:resultsbefore" --diff "C:resultsafter" --threshold 2% 1 2 Guidance Tool Preventing Regressions ResultComparer C:Projectsperformancesrcbenchmarksmicro> dotnet run -c Release -f net8.0 --artifacts "C:resultsbefore" 1 2 C:Projectsperformancesrcbenchmarksmicro> dotnet run -c Release -f net8.0 --artifacts "C:resultsafter" 1 2
  • 57. "Two subsequent builds on the same revision can have ranges of 1.5..2 seconds and 12..36 seconds. CPU-bound benchmarks are much more stable than Memory/Disk-bound benchmarks, but the “average” performance levels still can be up to three times different across builds." Andrey Akinshin - Performance stability of GitHub Actions
  • 58. BEYOND SIMPLE BENCHMARKS A PRACTICAL GUIDE TO OPTIMIZING CODE  | ✉ |  danielmarbach daniel.marbach@particular.net Daniel Marbach github.com/danielmarbach/BeyondSimpleBenchmarks Use the performance loop to improve your code where it matters Combine it with profiling to observe how the small changes add up Optimize until you hit a diminishing point of return You'll learn a ton about potential improvements for a new design