Deep Dive on .NET Core String.Create() Performance
By: Casey Mcquillan
Tags: Software Development, Software Engineering, Csharp, Dotnet Core, Programming

Deep Dive on .NET Core String.Create() Performance

When was the last time that an insignificant detail sparked a movement in your brain? As a software engineer, I have a habit of focusing on the one tiny detail I have never seen before in a new block of code. At that point, the gears in my brain begin turning. I love these moments. The ones where the tiniest piece of trivia sends me down a rabbit-hole of discovery.

What did that look like the last time it came to you?

It recently happened to me as I was browsing Twitter. I came upon this exchange between David Fowler and Damian Edwards, discussing the .NET Span<T> API. I have used the Span<T> API before, but I found something new and different in the tweets.

It’s a small thing, The item that began my dive down the rabbit-hole. The String.Create method used above was the piece I had never seen before. String.Create was a hidden gem, and I was determined to uncover its mysteries. I found myself asking the question:

“Why use this method for creating strings over others?”

I began the journey, and it took me to a few interesting places that I would like to share with you. In this article, we will delve into a few topics…

What is String.Create and how is it different from other APIs?

and how is it different from other APIs? What does String.Create do better and how can it make my C# code faster?

do better and how can it make my C# code faster? How much more performance can it achieve?

What pitfalls can and should be avoided?

Definitions

To make the article a little easier, I am going to refer to a few .NET Core APIs in the following ways:

Create — Refers to using String.Create() .

— Refers to using . Concat — Refers to using String.Concat() or the plus ( + ) operator.

— Refers to using or the plus ( ) operator. Format — Refers to using String.Format() , one of its many overloads, or string interpolation with the $"" syntax.

— Refers to using , one of its many overloads, or string interpolation with the syntax. StringBuilder — Refers to constructing a string with the fluent StringBuilder class and API.

How Does It Work?

The .NET Core codebase is open source and developed on GitHub. This provides a great opportunity to dive in and analyze Microsoft’s own practices. They provided the Create API, so seeing how they use it should provide valuable insight. Let’s start with a deep dive into the String object and its relevant APIs.

To generically build a string out of raw character data, you need to use the constructor that requires a pointer to a char array. Using this API directly would require placing individual characters into specific array locations. Below is the code that runs when you allocate a string using this constructor. There are many other ways to create strings, but this is what I consider the most comparable to the Create method.

String Constructor from .NET Core Runtime

To summarize the important steps:

Inputs are validated. An empty or null array returns String.Empty . We allocate memory using FastAllocateString based on the array Length . FastAllocateString is implemented within the .NET Runtime itself and underpins nearly all string allocations. We call Buffer.Memmove , which copies all bytes from the original array into the newly allocated string. Return the resulting string.

To use this constructor, we need to supply it with a char array. After its job is complete, we end up with both a (now unnecessary) char array and a string , each with identical data. If we were to modify the original array, then the string would remain unmodified, because it is a separate and distinct copy of the data. In a high-performance .NET environment, saving object and array allocations can be extremely valuable because it reduces the total amount of work that the .NET Garbage Collector needs to do whenever it runs. Every extra object that is left in memory increases the frequency of collections and hurts total performance.

If you would like to learn more about .NET Garbage Collection and how it can impact performance, there are many fantastic resources online:

To contrast with the constructor and to eliminate this unnecessary array allocation, we take a look at the code that runs for the Create method:

String.Create() from .NET Core Runtime

The steps are similar but contain a critical difference:

Inputs are validated. Invalid action or negative Length will throw an exception and Length of 0 returns Empty; We allocate memory using FastAllocateString based on the length parameter. Convert the newly-allocated string to a Span<char> . Invoke the provided action and pass in the new Span<char> along with the provided state . Return the resulting string .

This methodology avoids the extra allocation by allowing us to pass in a SpanAction , which functions as a set of instructions for how our string should be created instead of requiring a second copy of all the bytes we need put into the string.

An analogy could be drawn from graphic design. You might want to get a professional logo created for your business, and you probably decided that you wanted it created in a vector format like SVG or EPS. You create a document that has details on the theme, colors, and general look of your logo. You now have a couple of options:

Draw the first draft yourself as close to the professional version as possible, send it to a graphic designer, and have them create a finalized version. Send your original detailed document to the graphic designer, who creates a finalized logo from the information you provided.

In the first situation, you end up with two images, but only one that you plan to use. Your original can probably be thrown away without any ceremony, as you now have your professionally designed version. In the second case, you have not produced any extra product and therefore have produced less waste.

The process for the Create method is closer to the situation where you sent your documentation to a designer. By telling them exactly what you wanted from the beginning, you only generate one final artifact in the form that you need.

How is String.Create() Better?

At this point, you might be curious about the Create method, but you don’t necessarily know why it’s better than the methods you have used before. The Create APIs usefulness is situational, but it can be extremely powerful in the proper context.