Dotnet 6 uses string Create improves string creation and splicing performance

Time:2022-5-7

This article tells you how to use string in dotnet 6 or later Create improves the performance of string creation and splicing, and reduces the additional memory required when splicing strings, thus reducing the pressure of memory recovery

This article also followsStephen ToubOne of a series of blogs on performance optimization. This isStephen ToubThe boss is one of the small points in the performance optimization for WPF. It’s just this optimization point, isn’t itStephen ToubThe boss is involved in the design (expected to lead) and development. This optimization point needs to modify the Roslyn kernel, write an analyzer, and support in the dotnet runtime layer. After completing the support from Roslyn to analyzer and runtime in the past, it comes to the support of application framework layer, which isStephen ToubOne of the reasons why the boss will be active in WPF warehouse

Crooked building, you know the relationship between the various layers of dotnet. In dotnet, the roles of each part are:

  • Roslyn: compiler kernel layer
  • Runtime: provides runtime support. In a broad sense, runtime includes execution engine and basic library
  • WPF: application code framework layer

Above WPF is the business code logic

In WPF warehouseStephen ToubThe change code of the boss can be fromRemove some unnecessary StringBuilders by stephentoub · Pull Request #6275 · dotnet/wpfFound. This is the example code of this article

In dotnet 6, a new string is provided Two new overloaded methods of create method. The signatures of these two overloaded methods are as follows

First overloaded method:

public static string Create (IFormatProvider? provider, Span initialBuffer, ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler);

The above three parameters are described as follows:

  • Provider: an object that provides culture specific formatting information.
  • Initialbuffer: initial buffer, temporary space that can be used as part of the formatting operation. The contents of this buffer may be overwritten.
  • Handler: the interpolated string passed by reference.

Second overload method:

public static string Create (IFormatProvider? provider, ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler);

The second overloaded method simply takes the first method’sSpan initialBufferJust kill it

The core of this article is the first overloaded method

Why can these two methods be used only in dotnet 6 or later? Why can’t the lower version be used? As mentioned at the beginning of this article, this is because these two methods need to be changed from Roslyn to dotnet runtime to support. Then why do we need to change so much to support it? Because these two methods don’t look simple, they actually use Roslyn’s black technology. Of course, with Roslyn black technology, you can tell teachers that your knowledge needs to be updated again

Knock on the blackboard and the first knowledge update point is the interpolated string. Interestingly, the knowledge point of interpolation string proposed in c# 6.0 is updated at the time of dotnet 6. Don’t mix up. The c# version and dotnet version here are two different things. As the following interpolated string, guess what this is

$"lindexi is {doubi}"

In dotnet 6 or earlier, you can listen to the teacher and say it’s astring.FormatJust syntax optimization, which is completely equivalent to the following code

string.Format("lindexi is {0}", doubi);

Of course, I didn’t open ide to write such simple code. If the syntax is wrong, please ignore it

But in dotnet 6 or later, these knowledge needs to be updated. See the interpolated string, but not necessarilystring.FormatSyntax optimization, can also beSystem.Runtime.CompilerServices.DefaultInterpolatedStringHandlerType creation

There’s an official blog, uh, againStephen ToubIt’s written by the boss to tell you the source of this defaultinterpolatedstringhandler type and how it works. Please see it for detailsString Interpolation in C# 10 and .NET 6 – .NET Blog

In short, when using interpolated strings, some additional objects will be created before c# 10 and dotnet 6, which will cause the pressure of memory recycling. Well, it’s just causing pressure. Don’t worry. We 996 are not afraid. A little pressure, not much

As shown in the following code, it is the usage of a standard interpolated string

public static string FormatVersion(int major, int minor, int build, int revision) =>
    $"{major}.{minor}.{build}.{revision}";

Before c# 10 and dotnet 6, the constructed code will split the above syntax optimization into the following code

public static string FormatVersion(int major, int minor, int build, int revision)
{
    var array = new object[4];
    array[0] = major;
    array[1] = minor;
    array[2] = build;
    array[3] = revision;
    return string.Format("{0}.{1}.{2}.{3}", array);
}

As you can see, in fact, this will require the creation of an additional object array at the same timestring.FormatThere are many other losses in the method

When c# 10 and dotnet 6 are satisfied at the same time, it will be modified to the following code with equivalent results during construction

public static string FormatVersion(int major, int minor, int build, int revision)
{
    var handler = new DefaultInterpolatedStringHandler(literalLength: 3, formattedCount: 4);
    handler.AppendFormatted(major);
    handler.AppendLiteral(".");
    handler.AppendFormatted(minor);
    handler.AppendLiteral(".");
    handler.AppendFormatted(build);
    handler.AppendLiteral(".");
    handler.AppendFormatted(revision);
    return handler.ToStringAndClear();
}

thisDefaultInterpolatedStringHandlerIs a structure object. According to a completely wrong knowledge, the structure is allocated on the stack. The above code will not require additional memory application except the returned string. Although knowledge is completely wrong, the result is right. Rumor refutation time: the structure can be allocated on the stack or on the heap. For structures created by most local variables, this structure is allocated on the stack. At least, the above code allocates a defaultinterpolatedstringhandler structure object on the stack. Since the memory of the stack is fixed and clear, it can be considered that the memory used on the stack does not belong to the additional memory applied. Moreover, because of the space of the stack, the stack will be recycled automatically after the method is executed, so there is no pressure for memory recycling. This is equivalent to the stack space used in this method will be erased after the execution of this method. Naturally, there is no need to calculate memory recycling. Of course, the protagonist of this article is not stack memory. I expect to blow it for a long time. Let’s go back to the topic of this article. You just need to remember that the above code saves memory and allocates resources

In the above code, the allocated object has only one string. Yes, it is the string of the return value

In other words, in dotnet 6 and later versions, you can build$Interpolate the string and build it into a defaultinterpolatedstringhandler structure object without walkingstring.FormatMethod logic. This is a big advantage. You can interpolate the string without creating additional arrays to store the parameter liststring.FormatMethod to parse the string

But we have another doubt, in useDefaultInterpolatedStringHandlerWhen the tostringandclear method is used, doesn’t the underlying layer need an array for caching? In fact, it is still useful. Otherwise, what else should the protagonist of this article do. In the tostringandclear method, an array is actually needed for caching. Otherwise, the code is still a bit flawed. Array cache is used. Why does this article say that there is no additional memory allocation? Don’t forget the array pool

By default, it is in defaultinterpolatedstringhandler, which will apply forArrayPool.SharedThe array space of an array pool is used as a cache. In most cases, it can be considered a harmless process. However, the array pool is not always free. Moreover, interest is still required for borrowing and settlement

In order to reduce interest and CPU computing time, it is the protagonist of this paper, that isstring.CreateWhen the newly added overloaded method comes out

As mentioned above, calling defaultinterpolatedstringhandler also requires a cache array. If this array also comes from the stack, will it be more economical? you ‘re right. How to transfer the array on the stack to the defaultinterpolatedstringhandler structure needs the protagonist of this article

First apply for a certain array space through stackalloc, and then give the array space to the defaultinterpolatedstringhandler structure to realize that almost all memory allocation logic is allocated on the stack. The garbage will be automatically cleaned up as the method ends

The usage is as follows:

public static string FormatVersion(int major, int minor, int build, int revision) =>
    string.Create(null, stackalloc char[64], $"{major}.{minor}.{build}.{revision}");

The above usage belongs to the advanced usage section. During construction, the interpolated string will be automatically split into the defaultinterpolatedstringhandler structure, prompting the incoming stringstackalloc char[64]Passed in as a buffered array. In this way, in addition to the string of the return value, there is no need to apply for additional space from the heap. Moreover, when the incoming buffer array is sufficient, there is no need to apply for cache array space in the array pool, which reduces the time loss of borrowing and returning, so as to achieve extremely high performance

However, this is a high-level usage, which still needs to be careful. The first is that we use stackalloc to allocate memory space on the stack. Be careful about the size of the allocation. If we blow up the space on the stack, we can only see you again. The default allocation is 512, which can be considered safe. However, the smaller the distribution, the better. Just enough. Don’t hit a few more zeros

The second is that if the incoming cache space is insufficient, you still need to apply for memory space from the array pool. Instead of blowing up your application across the stack. Further, sometimes we can’t predict how large the cache size used by this interpolated string needs to be. If it is really difficult to predict, and the actual business expectation will exceed the estimated size, then using the above method is equivalent to white applying for a section of stack space. It is better not to use it

If the actual required string splicing cache space is larger than the space of the incoming stackalloc. At the bottom of the runtime, the incoming array space will be discarded and the space requested from the array pool will be used instead. Therefore, the estimated fixed size array passed in the stackalloc application is safe in development. If the estimated fixed size is small, there will be no logical problem

For example, the concatenation of interpolated strings requires 5000 char array space as cache space, but the incomingstackallocThe space applied for isstackalloc char[64]That’s obviously not enough. This is no problem. At the bottom, we will borrow enough space from the array pool. It won’t force the allocation of space on your stack to exceed the limit

For strings, another important thing is language and culture. For example, for dates, the string representation of dates in American and Chinese cultures is different. Naturally, when formatting the output string, it is best to bring the date. The above example is just for simplicity, passing iformatprovider into null value. In fact, you can pass in formatting methods that meet your expectations, such as formatting that ignores language and culture

public static string FormatVersion(int major, int minor, int build, int revision) =>
    string.Create(CultureInfo.InvariantCulture, stackalloc char[64], $"{major}.{minor}.{build}.{revision}");

AboveCultureInfo.InvariantCultureThe subsequent interpolated strings will be formatted accordingly, which can solve many language and cultural problems

For our application code, if you need to show it to users, it’s best to show it according to the local language and culture. For the computational logic in our application layer, it’s best to do something that has nothing to do with language and culture. In this way, we can keep the logic in line with expectations. After all, there are still many strange language formats. The use of language and culture is irrelevant, which can keep the computing logic in our application in line with expectations

Under dotnet 6, if usedstring.CreateThe performance of these two new overloaded methods is better than that of splicing stringsStringBuilderHigher

For example, the following code uses StringBuilder to splice and create strings

StringBuilder stringBuilder = new StringBuilder(64);
stringBuilder.Append(cr.TopLeft.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.TopRight.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.BottomRight.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.BottomLeft.ToString(cultureInfo));
return sb.ToString();

The above code needs to allocate one more StringBuilder object on the stack, and it also needs to apply for at least one 64 length array for this object. After optimization, thestring.CreateFor example, the following code does not need to apply for any space except the string of the return value

return string.Create(cultureInfo, stackalloc char[128], $"{cr.TopLeft}{listSeparator}{cr.TopRight}{listSeparator}{cr.BottomRight}{listSeparator}{cr.BottomLeft}");

In fact, not all places where string splicing is used, using StringBuilder can improve performance. If string splicing is simply the addition of two strings, then most of the time, the performance of adding two strings is better than that of splicing with StringBuilder

This is the performance optimization point discussed in this paper. The string interpolation optimization method combined with c# 10 and dotnet 6 is adopted

Recommended Today

Set the serialization of Jackson data returned by springboot

Localdatetime formatUsing the following configuration is equivalent to global configuration, so there is no need to add @ jsonformat (pattern = dateut. Yyyy_mm_dd_hh_mm_ss) to the fieldIf individual fields need different configurations, you can use @ jsonformat (pattern = dateut. Yyyy_mm_dd), @ jsonformat will take precedence, and @ jsonformat annotation can also be used for deserialization […]