C#: Why does .ToString() append text faster to an int converted to string?

c#

Solution

The first two answers to your question are not quite correct. The `sb.Append(i + ",");` statement does not call `i.ToString()`, what it actually does is

StringBuilder.Append(string.Concat((object)i, (object)","));

Internally in the `string.Concat` function, it calls `ToString()` on the two `object`s passed in. The key performance concern in this statement is `(object)i`. This is boxing - wrapping a value type inside a reference. This is a (relatively) sizable performance hit, as it takes extra cycles and memory allocation to box something, and then there's extra garbage collection required.

You can see this happening in the IL of the (Release) compiled code:

IL_000c:  box        [mscorlib]System.Int32
IL_0011:  ldstr      ","
IL_0016:  call       string [mscorlib]System.String::Concat(object,
                                                            object)
IL_001b:  callvirt   instance class [mscorlib]System.Text.StringBuilder 
                     [mscorlib]System.Text.StringBuilder::Append(string)

See that the first line is a `box` call, followed by a `Concat` call, ending with finally calling `Append`.

If you call `i.ToString()` instead, shown below, you forego the boxing, and also the `string.Concat()` call.

for (int i = 0; i < 50; i++)
{
    sb.Append(i.ToString());
    sb.Append(",");
}

This call yields the following IL:

IL_000b:  ldloca.s   i
IL_000d:  call       instance string [mscorlib]System.Int32::ToString()
IL_0012:  callvirt   instance class [mscorlib]System.Text.StringBuilder
                     [mscorlib]System.Text.StringBuilder::Append(string)
IL_0017:  pop
IL_0018:  ldloc.0
IL_0019:  ldstr      ","
IL_001e:  callvirt   instance class [mscorlib]System.Text.StringBuilder
                     [mscorlib]System.Text.StringBuilder::Append(string)

Note that there is no boxing, and no `String.Concat`, therefore there is less resources created that need to be collected, and less cycles wasted on boxing, at the cost of adding one `Append()` call, which is relatively much cheaper.

This is why the second set of code is better performance.

You can extend this idea to many other things - anywhere that's operating on strings that you're passing a value type into a function that isn't explicitly taking that type as an argument (calls that take an `object` as an argument, like `string.Format()` for example), it's a good idea to call `<valuetype>.ToString()` when passing in a value type argument.

In response to Theodoros' question in the comment:

The compiler team certainly could have decided to do such an optimization, but my guess is that they decided that the cost (in terms of additional complexity, time, additional testing, etc.) made the value of such a change not worth the investment.

Basically, they would have had to put in a special case branching for functions that ostensibly operate on `string`s, but offer an overload with `object` in it (basically, `if (boxing occurs && overload has string)`). Inside that branch the compiler would have to also check to verify that the `object` function overload does the same things as the `string` overload with the exception of calling `ToString()` on the arguments - it needs to do this because a user could create function overloads in which one function takes a `string` and another takes an `object`, but the two overloads perform different work on the arguments.

This seems to me like a lot of complexity and analysis for making a minor optimization to a few string manipulation functions. Additionally, this would be mucking around with the core compiler function resolution code, which already has some very exact rules that people misunderstand all the time (take a look at a number of Eric Lippert's answers - quite a few revolve around function resolution issues). Making it more complicated with "it works like this, except when you have that situation" type rules is certainly something to be avoided if the return is minimal.

The less expensive and less complex solution is to use the base function resolution rules, and let the compiler resolve you passing in a value type (like an `int`) into a function, and having it figure out that the only function signature that fits it is one that takes `object`, and do a box. Then rely on users to do the optimization of `ToString()` when they profile their code and determine it is necessary (or just know about this behavior and do it all the time anyway when they encounter the situation, which I do).

A more likely alternative they could have done is have a number of `string.Concat` overloads that take `int`s, `double`s, etc. (like `string.Concat(int, int)`) and just call `ToString` on the arguments internally where they would not be boxed. This has the advantage that the optimization is in the class library instead of the compiler, but then you inevitably run into situations where you want to mix types in the concatenation, like the original question here where you have `string.Concat(int, string)`. The permutations would explode, which is the likely reason they did not do so. They also could have determined the most commonly used situations where such overloads would be used and do the top 5, but I'm guessing they decided that would just open them up to people asking "well, you did `(int, string)`, why don't you do `(string, int)`?".

Problem

This is from C# in a nutshell book ``` StringBuilder sb = new StringBuilder(); for(int i = 0; i < 50; i++) sb.Append (i + ","); //Outputs 0,1,2,3.............49, ``` However , it then says "the expression i + "," means that we are still repeatedly concatenating strings, howver this only incurs a small performance cost as strings are small" Then it says that changing it to the lines below makes it faster ``` for(int i = 0; i < 50; i++) { sb.Append(i.ToString()); sb.Append(","); } ``` But why is that faster? Now we have an extra step where `i` is being converted to a string? What is actually going under the hood here?There isn't any more explanation in the rest of the chapter.

Original source

Related problems