Simple string concatenation for small amounts of strings in .NET 8
Date Added (UTC):
05 Apr 2024 @ 04:17
Date Updated (UTC):05 Apr 2024 @ 04:17
.NET Version(s): Tag(s):
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using System.Text;
namespace Benchmarks
{
[MemoryDiagnoser]
[Config(typeof(Config))]
[SimpleJob(RuntimeMoniker.Net80)]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
public class StringConcatSimple
{
private class Config : ManualConfig
{
public Config()
{
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
}
}
private string
title = "Mr.", firstName = "David", middleName = "Patrick", lastName = "Callan";
[Benchmark(Baseline = true)]
public string StringBuilder()
{
var stringBuilder =
new StringBuilder();
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
[Benchmark]
public string StringBuilderExact24()
{
var stringBuilder =
new StringBuilder(24);
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
[Benchmark]
public string StringBuilderEstimate100()
{
var stringBuilder =
new StringBuilder(100);
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
[Benchmark]
public string StringPlus()
{
return title + ' ' + firstName + ' ' +
middleName + ' ' + lastName;
}
[Benchmark]
public string StringFormat()
{
return string.Format("{0} {1} {2} {3}",
title, firstName, middleName, lastName);
}
[Benchmark]
public string StringInterpolation()
{
return
$"{title} {firstName} {middleName} {lastName}";
}
[Benchmark]
public string StringJoin()
{
return string.Join(" ", title, firstName,
middleName, lastName);
}
[Benchmark]
public string StringConcat()
{
return string.
Concat(new string[] { title, " ", firstName, " ", middleName, " ", lastName });
}
}
}
Powered by SharpLab
// .NET 8
public string StringBuilder()
{
return new StringBuilder().Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
// .NET 8
public string StringBuilderExact24()
{
return new StringBuilder(24).Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
// .NET 8
public string StringBuilderEstimate100()
{
return new StringBuilder(100).Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
// .NET 8
public string StringPlus()
{
string[] array = new string[7];
array[0] = title;
array[1] = " ";
array[2] = firstName;
array[3] = " ";
array[4] = middleName;
array[5] = " ";
array[6] = lastName;
return string.Concat(array);
}
// .NET 8
public string StringFormat()
{
object[] array = new object[4];
array[0] = title;
array[1] = firstName;
array[2] = middleName;
array[3] = lastName;
return string.Format("{0} {1} {2} {3}", array);
}
// .NET 8
public string StringInterpolation()
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(3, 4);
defaultInterpolatedStringHandler.AppendFormatted(title);
defaultInterpolatedStringHandler.AppendLiteral(" ");
defaultInterpolatedStringHandler.AppendFormatted(firstName);
defaultInterpolatedStringHandler.AppendLiteral(" ");
defaultInterpolatedStringHandler.AppendFormatted(middleName);
defaultInterpolatedStringHandler.AppendLiteral(" ");
defaultInterpolatedStringHandler.AppendFormatted(lastName);
return defaultInterpolatedStringHandler.ToStringAndClear();
}
// .NET 8
public string StringJoin()
{
string[] array = new string[4];
array[0] = title;
array[1] = firstName;
array[2] = middleName;
array[3] = lastName;
return string.Join(" ", array);
}
// .NET 8
public string StringConcat()
{
string[] array = new string[7];
array[0] = title;
array[1] = " ";
array[2] = firstName;
array[3] = " ";
array[4] = middleName;
array[5] = " ";
array[6] = lastName;
return string.Concat(array);
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance string StringBuilder () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 27 00 00 00 01 5f 01 00 54 02 08 42 61 73
65 6c 69 6e 65 01
)
// Method begins at RVA 0x2050
// Code size 76 (0x4c)
.maxstack 2
// sequence point: (line 42, col 13) to (line 43, col 37) in _
IL_0000: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor()
// sequence point: (line 45, col 13) to (line 49, col 46) in _
IL_0005: ldarg.0
IL_0006: ldfld string Benchmarks.StringConcatSimple::title
IL_000b: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0010: ldc.i4.s 32
IL_0012: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0017: ldarg.0
IL_0018: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0022: ldc.i4.s 32
IL_0024: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0029: ldarg.0
IL_002a: ldfld string Benchmarks.StringConcatSimple::middleName
IL_002f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0034: ldc.i4.s 32
IL_0036: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003b: ldarg.0
IL_003c: ldfld string Benchmarks.StringConcatSimple::lastName
IL_0041: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0046: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004b: ret
}
// .NET 8
.method public hidebysig
instance string StringBuilderExact24 () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 34 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20a8
// Code size 78 (0x4e)
.maxstack 2
// sequence point: (line 55, col 13) to (line 56, col 39) in _
IL_0000: ldc.i4.s 24
IL_0002: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor(int32)
// sequence point: (line 58, col 13) to (line 62, col 46) in _
IL_0007: ldarg.0
IL_0008: ldfld string Benchmarks.StringConcatSimple::title
IL_000d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0012: ldc.i4.s 32
IL_0014: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0024: ldc.i4.s 32
IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_002b: ldarg.0
IL_002c: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0031: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0036: ldc.i4.s 32
IL_0038: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003d: ldarg.0
IL_003e: ldfld string Benchmarks.StringConcatSimple::lastName
IL_0043: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0048: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004d: ret
}
// .NET 8
.method public hidebysig
instance string StringBuilderEstimate100 () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 41 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2104
// Code size 78 (0x4e)
.maxstack 2
// sequence point: (line 68, col 13) to (line 69, col 40) in _
IL_0000: ldc.i4.s 100
IL_0002: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor(int32)
// sequence point: (line 71, col 13) to (line 75, col 46) in _
IL_0007: ldarg.0
IL_0008: ldfld string Benchmarks.StringConcatSimple::title
IL_000d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0012: ldc.i4.s 32
IL_0014: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0024: ldc.i4.s 32
IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_002b: ldarg.0
IL_002c: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0031: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0036: ldc.i4.s 32
IL_0038: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003d: ldarg.0
IL_003e: ldfld string Benchmarks.StringConcatSimple::lastName
IL_0043: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0048: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004d: ret
}
// .NET 8
.method public hidebysig
instance string StringPlus () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 4e 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2160
// Code size 72 (0x48)
.maxstack 4
// sequence point: (line 81, col 13) to (line 82, col 45) in _
IL_0000: ldc.i4.7
IL_0001: newarr [System.Runtime]System.String
IL_0006: dup
IL_0007: ldc.i4.0
IL_0008: ldarg.0
IL_0009: ldfld string Benchmarks.StringConcatSimple::title
IL_000e: stelem.ref
IL_000f: dup
IL_0010: ldc.i4.1
IL_0011: ldstr " "
IL_0016: stelem.ref
IL_0017: dup
IL_0018: ldc.i4.2
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001f: stelem.ref
IL_0020: dup
IL_0021: ldc.i4.3
IL_0022: ldstr " "
IL_0027: stelem.ref
IL_0028: dup
IL_0029: ldc.i4.4
IL_002a: ldarg.0
IL_002b: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0030: stelem.ref
IL_0031: dup
IL_0032: ldc.i4.5
IL_0033: ldstr " "
IL_0038: stelem.ref
IL_0039: dup
IL_003a: ldc.i4.6
IL_003b: ldarg.0
IL_003c: ldfld string Benchmarks.StringConcatSimple::lastName
IL_0041: stelem.ref
IL_0042: call string [System.Runtime]System.String::Concat(string[])
IL_0047: ret
}
// .NET 8
.method public hidebysig
instance string StringFormat () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 55 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x21b4
// Code size 53 (0x35)
.maxstack 8
// sequence point: (line 88, col 13) to (line 89, col 57) in _
IL_0000: ldstr "{0}
// .NET 8
.method public hidebysig
instance string StringInterpolation () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 5c 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x21ec
// Code size 105 (0x69)
.maxstack 3
.locals init (
[0] valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler
)
// sequence point: (line 95, col 13) to (line 96, col 60) in _
IL_0000: ldloca.s 0
IL_0002: ldc.i4.3
IL_0003: ldc.i4.4
IL_0004: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, int32)
IL_0009: ldloca.s 0
IL_000b: ldarg.0
IL_000c: ldfld string Benchmarks.StringConcatSimple::title
IL_0011: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(string)
IL_0016: ldloca.s 0
IL_0018: ldstr " "
IL_001d: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string)
IL_0022: ldloca.s 0
IL_0024: ldarg.0
IL_0025: ldfld string Benchmarks.StringConcatSimple::firstName
IL_002a: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(string)
IL_002f: ldloca.s 0
IL_0031: ldstr " "
IL_0036: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string)
IL_003b: ldloca.s 0
IL_003d: ldarg.0
IL_003e: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0043: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(string)
IL_0048: ldloca.s 0
IL_004a: ldstr " "
IL_004f: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string)
IL_0054: ldloca.s 0
IL_0056: ldarg.0
IL_0057: ldfld string Benchmarks.StringConcatSimple::lastName
IL_005c: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(string)
IL_0061: ldloca.s 0
IL_0063: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear()
IL_0068: ret
}
// .NET 8
.method public hidebysig
instance string StringJoin () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 63 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2261
// Code size 53 (0x35)
.maxstack 8
// sequence point: (line 102, col 13) to (line 103, col 39) in _
IL_0000: ldstr " "
IL_0005: ldc.i4.4
IL_0006: newarr [System.Runtime]System.String
IL_000b: dup
IL_000c: ldc.i4.0
IL_000d: ldarg.0
IL_000e: ldfld string Benchmarks.StringConcatSimple::title
IL_0013: stelem.ref
IL_0014: dup
IL_0015: ldc.i4.1
IL_0016: ldarg.0
IL_0017: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001c: stelem.ref
IL_001d: dup
IL_001e: ldc.i4.2
IL_001f: ldarg.0
IL_0020: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0025: stelem.ref
IL_0026: dup
IL_0027: ldc.i4.3
IL_0028: ldarg.0
IL_0029: ldfld string Benchmarks.StringConcatSimple::lastName
IL_002e: stelem.ref
IL_002f: call string [System.Runtime]System.String::Join(string, string[])
IL_0034: ret
}
// .NET 8
.method public hidebysig
instance string StringConcat () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 6a 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2298
// Code size 72 (0x48)
.maxstack 4
// sequence point: (line 109, col 13) to (line 110, col 96) in _
IL_0000: ldc.i4.7
IL_0001: newarr [System.Runtime]System.String
IL_0006: dup
IL_0007: ldc.i4.0
IL_0008: ldarg.0
IL_0009: ldfld string Benchmarks.StringConcatSimple::title
IL_000e: stelem.ref
IL_000f: dup
IL_0010: ldc.i4.1
IL_0011: ldstr " "
IL_0016: stelem.ref
IL_0017: dup
IL_0018: ldc.i4.2
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringConcatSimple::firstName
IL_001f: stelem.ref
IL_0020: dup
IL_0021: ldc.i4.3
IL_0022: ldstr " "
IL_0027: stelem.ref
IL_0028: dup
IL_0029: ldc.i4.4
IL_002a: ldarg.0
IL_002b: ldfld string Benchmarks.StringConcatSimple::middleName
IL_0030: stelem.ref
IL_0031: dup
IL_0032: ldc.i4.5
IL_0033: ldstr " "
IL_0038: stelem.ref
IL_0039: dup
IL_003a: ldc.i4.6
IL_003b: ldarg.0
IL_003c: ldfld string Benchmarks.StringConcatSimple::lastName
IL_0041: stelem.ref
IL_0042: call string [System.Runtime]System.String::Concat(string[])
IL_0047: ret
}
Powered by SharpLab
|
|
|
|
|
|
|
|
Benchmark Description:
Benchmark comparing common approaches for string concatenation of only a couple of strings including interpolation, string.Format, StringBuilder etc.
We can see StringBuilder is, at least for this benchmark run, the slowest except for string.Format, Keep this in mind if you ever hear the "_always use StringBuilder_" line on social media or blogs.
String.Join is the fastest, while interpolation is super efficient as far as memory is concerned.
The provided benchmark code is designed to measure and compare the performance of different methods for concatenating strings in C#. It uses the BenchmarkDotNet library, a powerful tool for benchmarking .NET applications. The benchmarks are configured to run on the .NET 8.0 runtime, indicating that they are intended for applications targeting the latest version of the .NET platform at the time of writing. The configuration hides certain columns in the output to focus on the most relevant metrics, and it sets a custom summary style to display ratio information as percentages.
Below is an overview of each benchmark method, including the rationale behind it and the specific performance aspect it is designed to test:
### 1. `StringBuilder()`
- **Purpose**: Measures the performance of string concatenation using the `StringBuilder` class without pre-setting the capacity.
- **Importance**: This method tests how well `StringBuilder` performs when it needs to dynamically resize its internal buffer to accommodate the concatenated strings. It's a common scenario when the final size of the concatenated string is unknown.
- **Expected Insights**: This benchmark will show how efficient `StringBuilder` is in handling string concatenations without initial capacity optimization. It serves as a baseline for comparing other methods.
### 2. `StringBuilderExact24()`
- **Purpose**: Measures the performance of string concatenation using the `StringBuilder` class with the exact initial capacity needed for the final string.
- **Importance**: Pre-setting the `StringBuilder` capacity to the exact size needed can significantly improve performance by eliminating the need for internal buffer resizing. This method tests the best-case scenario for `StringBuilder`.
- **Expected Insights**: This benchmark is expected to demonstrate the performance benefits of optimizing `StringBuilder`'s initial capacity. It should be faster than the baseline `StringBuilder` method.
### 3. `StringBuilderEstimate100()`
- **Purpose**: Similar to `StringBuilderExact24()`, but with an overestimated initial capacity.
- **Importance**: This method tests the impact of overestimating the `StringBuilder`'s initial capacity. It's relevant for scenarios where the exact size isn't known, but an estimate is possible.
- **Expected Insights**: The results will show how performance is affected by overestimating the capacity. It's likely to be faster than the baseline but may not offer significant advantages over `StringBuilderExact24()`.
### 4. `StringPlus()`
- **Purpose**: Measures the performance of string concatenation using the `+` operator.
- **Importance**: The `+` operator is the simplest way to concatenate strings but can be inefficient for multiple concatenations due to the creation of intermediate strings.
- **Expected Insights**: This benchmark will likely show that using the `+` operator is less efficient than using `StringBuilder`, especially as the number of strings increases.
### 5. `StringFormat()`
- **Purpose**: Measures the performance of string concatenation using `string.Format()`.
- **Importance**: `string.Format()` is useful for creating formatted strings. This benchmark tests its performance for concatenation purposes.
- **Expected Insights**: The method might be slower than `StringBuilder` due to parsing the format string and the overhead of method invocation.
### 6. `StringInterpolation()`
- **Purpose**: Measures the performance of string concatenation using string interpolation.
- **Importance**: String interpolation is a more readable and concise way to concatenate strings, introduced in C# 6. This benchmark assesses its performance.
- **Expected Insights**: This method's performance could be similar to `StringFormat()`, as string interpolation is compiled to a `string.Format()` call under the hood.
### 7. `StringJoin()`
- **Purpose**: Measures the performance of concatenating strings using `string.Join()`.
- **Importance**: `string.Join()` is designed for concatenating an array of strings with a separator and can be more efficient than using the `+` operator in some cases.
- **Expected Insights**: This benchmark will show how `string.Join()` performs compared to other methods, potentially offering a balance between readability and performance.
### 8. `StringConcat()`
- **Purpose**: Measures the performance of concatenating strings using `string.Concat()`.
- **Importance**: `string.Concat()` is a versatile method that can concatenate an array of strings. It's intended to be efficient for concatenating multiple strings.
- **Expected Insights**: The performance of `string.Concat()` should be competitive with other efficient methods like `StringBuilder`, especially for a known number of strings.
By running these benchmarks, developers can gain insights into the most efficient methods for string concatenation in different scenarios, helping them write more performant C# code.