skip to content
Relatively General .NET

Recording

by Oren Eini

posted on: August 28, 2023

I spoke for over an hour about how you can build high-performance systems and how we utilize these techniques inside of RavenDB.

ASP.NET Core Clean Architecture Template v8 Released

by Ardalis

posted on: August 28, 2023

I've just published a new version of my Clean Architecture Solution Template for ASP.NET Core applications. This is version 8 of the…Keep Reading →

How to convert YAML to JSON with PowerShell Core

by Gérald Barré

posted on: August 28, 2023

PowerShell Core doesn't provide a built-in cmdlet to parse YAML. However, there is a PowerShell module called powershell-yaml that provides the ConvertFrom-Yaml and ConvertTo-Yaml cmdlets. The following example shows how to convert a YAML file to a JSON file with PowerShell Core:PowerShellcopy$Inpu

A twisted tale of memory optimization

by Oren Eini

posted on: August 24, 2023

I was looking into reducing the allocation in a particular part of our code, and I ran into what was basically the following code (boiled down to the essentials): This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Scenario { Stream _stream; public void Write(char[] buffer, int len) { var bytes = Encoding.UTF8.GetBytes(new string(buffer,0, len)); _stream.Write(bytes); } } view raw Scenario.orig.cs hosted with ❤ by GitHub As you can see, this does a lot of allocations. The actual method in question was a pretty good size, and all those operations happened in different locations and weren’t as obvious. Take a moment to look at the code, how many allocations can you spot here? The first one, obviously, is the string allocation, but there is another one, inside the call to GetBytes(), let’s fix that first by allocating the buffer once (I’m leaving aside the allocation of the reusable buffer, you can assume it is big enough to cover all our needs): This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Scenario { Stream _stream; byte[] _reusableBuffer; public void Write(char[] buffer, int len) { var bytes = Encoding.UTF8.GetBytes(new string(buffer,0, len), _reusableBuffer); _stream.Write(_reusableBuffer[..bytes]); } } view raw Scenario.step-1.cs hosted with ❤ by GitHub For that matter, we can also easily fix the second problem, by avoiding the string allocation: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Scenario { Stream _stream; byte[] _reusableBuffer; public void Write(char[] buffer, int len) { var bytes = Encoding.UTF8.GetBytes(buffer[..len], _reusableBuffer); _stream.Write(_reusableBuffer[..bytes]); } } view raw Scenario.step2.cs hosted with ❤ by GitHub That is a few minutes of work, and we are good to go. This method is called a lot, so we can expect a huge reduction in the amount of memory that we allocated. Except… that didn’t happen. In fact, the amount of memory that we allocate remained pretty much the same. Digging into the details, we allocate roughly the same number of byte arrays (how!) and instead of allocating a lot of strings, we now allocate a lot of character arrays. I broke the code apart into multiple lines, which made things a lot clearer. (In fact, I threw that into SharpLab, to be honest). Take a look: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Scenario { Stream _stream; byte[] _reusableBuffer; public void Write(char[] buffer, int len) { char[] charBuffer = buffer[..len]; var bytes = Encoding.UTF8.GetBytes(charBuffer, _reusableBuffer); byte[] bytesBuffer = _reusableBuffer[..bytes]; _stream.Write(bytesBuffer); } } view raw Scenario.step-3.cs hosted with ❤ by GitHub This code: buffer[..len] is actually translated to: char[] charBuffer= RuntimeHelpers.GetSubArray(buffer, Range.EndAt(len)); That will, of course, allocate. I had to change the code to be very explicit about the types that I wanted to use: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Scenario { Stream _stream; byte[] _reusableBuffer; public void Write(char[] buffer, int len) { Span<char> bufferSpan = buffer; Span<char> charBuffer = bufferSpan[..len]; Span<byte> reusableBufferSpan = _reusableBuffer; var bytes = Encoding.UTF8.GetBytes(charBuffer, reusableBufferSpan); Span<byte> bytesBuffer = reusableBufferSpan[..bytes]; _stream.Write(bytesBuffer); } } view raw Scenario.step-4.cs hosted with ❤ by GitHub This will not allocate, but if you note the changes in the code, you can see that the use of var in this case really tripped me up. Because of the number of overloads and automatic coercion of types that didn’t happen. For that matter, note that any slicing on arrays will generate a new array, including this code: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters char[] buffer = ...; Span<char> span = buffer[0..10]; // hidden allocation here view raw Surprise.cs hosted with ❤ by GitHub This makes perfect sense when you realize what is going on and can still be a big surprise, I looked at the code a lot before I figured out what was going on, and that was with a profiler output that pinpointed the fault.

Mastering Unit Tests in .NET: Best Practices and Naming Conventions

by Ardalis

posted on: August 24, 2023

Unit testing is a crucial part of modern software development. It ensures that code is working as intended and can be a lifesaver when…Keep Reading →

Last chance to win a ticket to APIDays London with my new book!

by Andrew Lock

posted on: August 22, 2023

In this post I describe how you can win a ticket to APIDays London by purchasing my book, but you must enter before 23rd August!…

How to stay up to date with .NET

by Gérald Barré

posted on: August 21, 2023

This post is a collection of resources that I use to stay up to date with .NET. This list is not exhaustive, but I think it is a good starting point when you don't know where to start. I hope that you find it useful!TipYou can subscribe to most blogs using tools such as Feedly. This allows you to r

Mastering the Art of Managing Up: A Developer's Guide to Career Advancement

by Ardalis

posted on: August 16, 2023

Mastering the Art of Managing Up: A Developer's Guide to Career Advancement Learn the essential skills behind "Managing Up" tailored for the…Keep Reading →

Unhandled Exception Episode 55: RavenDB and Database Internals - with Oren Eini

by Oren Eini

posted on: August 15, 2023

You can listen to me talk with Dan in the Unhandled Exception podcast, where we dug deep into the internals of database engines. As usual, I would love your feedback.

Keyed service dependency injection container support

by Andrew Lock

posted on: August 15, 2023

Exploring the .NET 8 preview - Part 6