String vs StringBuilder .NET (C#)
Tags: dotnet
Published: 2023-08-06 06:19:14

Hi, everyone!

Do you have any use cases to manipulate large strings? Have you ever wondered about optimizing your code? Let's explore the difference between the native string and the string builder. When should we use string builder? When could we use string manipulation directly? My premise is we should use the string builder when we have large string manipulation, and we can use string manipulation directly if we combine two or three strings.

## Speed/Performance and Memory

I ran the Benchmark using .NET 8 and BenchmarkDotNet. The result may vary depending on your machine. My machine specs:

1. Intel Core i7-9750H CPU @ 2.60GHz, 6 Core(s), 12 Logical Processor(s)
2. RAM 32 GB, 2667 MHz

### The Result

This is the raw result after running my Benchmark. I will explain case by case.

|                   Method |      N |             Mean |          Error |         StdDev |           Median | Rank |          Gen0 |          Gen1 |          Gen2 |       Allocated |
|------------------------- |------- |-----------------:|---------------:|---------------:|-----------------:|-----:|--------------:|--------------:|--------------:|----------------:|
|  AppendWithStringBuilder |   1000 |         10.45 us |       0.207 us |       0.548 us |         10.20 us |    1 |        8.7585 |        0.9613 |             - |        53.91 KB |
|  AppendWithStringBuilder |  10000 |        213.85 us |       4.248 us |       7.768 us |        212.04 us |    2 |       66.6504 |       66.6504 |       66.6504 |       435.63 KB |
|         AppendWithString |   1000 |        759.31 us |      15.032 us |      20.068 us |        756.16 us |    3 |     1755.8594 |       93.7500 |             - |      10777.3 KB |
|        PrependWithString |   1000 |        761.74 us |      15.045 us |      25.137 us |        761.18 us |    3 |     1755.8594 |       93.7500 |             - |      10777.3 KB |
|  AppendWithStringBuilder | 100000 |      1,491.00 us |       8.566 us |       7.153 us |      1,493.93 us |    4 |      707.0313 |      652.3438 |      353.5156 |      4316.28 KB |
| PrependWithStringBuilder |   1000 |      3,652.40 us |      57.881 us |      51.310 us |      3,639.86 us |    5 |       19.5313 |        3.9063 |             - |       123.07 KB |
|        PrependWithString |  10000 |    103,185.42 us |   1,756.825 us |   1,643.335 us |    102,605.36 us |    6 |   316200.0000 |   294200.0000 |   290200.0000 |   1074665.57 KB |
|         AppendWithString |  10000 |    103,766.99 us |   2,047.758 us |   2,102.896 us |    103,956.12 us |    6 |   316200.0000 |   294200.0000 |   290200.0000 |   1074665.57 KB |
| PrependWithStringBuilder |  10000 |    443,622.50 us |   8,848.754 us |   9,087.019 us |    443,266.50 us |    7 |             - |             - |             - |      1230.88 KB |
|         AppendWithString | 100000 | 31,694,035.38 us | 479,131.423 us | 424,737.527 us | 31,537,399.50 us |    8 | 18852000.0000 | 18809000.0000 | 18805000.0000 | 107431164.26 KB |
|        PrependWithString | 100000 | 32,527,484.53 us | 133,803.969 us | 111,732.374 us | 32,527,375.30 us |    9 | 19962000.0000 | 19918000.0000 | 19914000.0000 | 107431463.44 KB |
| PrependWithStringBuilder | 100000 | 58,193,475.60 us | 712,873.020 us | 792,356.472 us | 58,134,526.30 us |   10 |     1000.0000 |             - |             - |      12305.1 KB |

## Append Case

|                   Method |      N |             Mean |          Error |         StdDev |           Median | Rank |          Gen0 |          Gen1 |          Gen2 |       Allocated |
|------------------------- |------- |-----------------:|---------------:|---------------:|-----------------:|-----:|--------------:|--------------:|--------------:|----------------:|
|  AppendWithStringBuilder |   1000 |         10.45 us |       0.207 us |       0.548 us |         10.20 us |    1 |        8.7585 |        0.9613 |             - |        53.91 KB |
|         AppendWithString |   1000 |        759.31 us |      15.032 us |      20.068 us |        756.16 us |    3 |     1755.8594 |       93.7500 |             - |      10777.3 KB |
|  AppendWithStringBuilder |  10000 |        213.85 us |       4.248 us |       7.768 us |        212.04 us |    2 |       66.6504 |       66.6504 |       66.6504 |       435.63 KB |
|         AppendWithString |  10000 |    103,766.99 us |   2,047.758 us |   2,102.896 us |    103,956.12 us |    6 |   316200.0000 |   294200.0000 |   290200.0000 |   1074665.57 KB |
|  AppendWithStringBuilder | 100000 |      1,491.00 us |       8.566 us |       7.153 us |      1,493.93 us |    4 |      707.0313 |      652.3438 |      353.5156 |      4316.28 KB |
|         AppendWithString | 100000 | 31,694,035.38 us | 479,131.423 us | 424,737.527 us | 31,537,399.50 us |    8 | 18852000.0000 | 18809000.0000 | 18805000.0000 | 107431164.26 KB |

As you can see, the String Builder always wins. It's not only in terms of performance but also allocated memory. The ratio of performance and memory is high. You know, it's a big deal!

If you wonder why the string concat uses large memory when looping, it will copy the string N times. The string is immutable, so they need to copy the content.

### Append String Builder

![Append SB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c99kn6c2cz614pj4ebop.png)

### Append String

![Append String](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nfboc09wdgqaa9b01kh.png)

## Prepend Case

|                   Method |      N |             Mean |          Error |         StdDev |           Median | Rank |          Gen0 |          Gen1 |          Gen2 |       Allocated |
|------------------------- |------- |-----------------:|---------------:|---------------:|-----------------:|-----:|--------------:|--------------:|--------------:|----------------:|
|        PrependWithString |   1000 |        761.74 us |      15.045 us |      25.137 us |        761.18 us |    3 |     1755.8594 |       93.7500 |             - |      10777.3 KB |
| PrependWithStringBuilder |   1000 |      3,652.40 us |      57.881 us |      51.310 us |      3,639.86 us |    5 |       19.5313 |        3.9063 |             - |       123.07 KB |
|        PrependWithString |  10000 |    103,185.42 us |   1,756.825 us |   1,643.335 us |    102,605.36 us |    6 |   316200.0000 |   294200.0000 |   290200.0000 |   1074665.57 KB |
| PrependWithStringBuilder |  10000 |    443,622.50 us |   8,848.754 us |   9,087.019 us |    443,266.50 us |    7 |             - |             - |             - |      1230.88 KB |
|        PrependWithString | 100000 | 32,527,484.53 us | 133,803.969 us | 111,732.374 us | 32,527,375.30 us |    9 | 19962000.0000 | 19918000.0000 | 19914000.0000 | 107431463.44 KB |
| PrependWithStringBuilder | 100000 | 58,193,475.60 us | 712,873.020 us | 792,356.472 us | 58,134,526.30 us |   10 |     1000.0000 |             - |             - |      12305.1 KB |

As you can see, the String Builder starts struggling after dealing with prepend. I will choose String Builder if I have memory concerns. Of course, there is a tread-off. I rarely do prepend the string. If needed to do so, I might choose a different approach.

If you need to learn more about the difference between String and String Builder, please visit this [documentation](https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?view=net-7.0#the-string-and-stringbuilder-types).

### Prepend String Builder

![Prepend String Builder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bavsdh855j2s37w0k2ux.png)


### Prepend String

![Prepend String](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb0qssagegnnorxzfzj4.png)


## Benchmark Code

{% github bervProject/StringBuilderPerformance %}

## Thank you

Do you have any feedback? Feel free to drop your comment. Thank you for reading. :)

![GIF Thanks](https://media.giphy.com/media/kfvEdnjjcAoea0naii/giphy.gif)