1

Why the speed rank is: AddStringInMutipleStatement > AddStringInOneStatement > AddStringInMutipleStatementEx ?

Is it related the temp string object created on runtime? What the detail is?

namespace Test
{
    class Program
    {
        static string AddStringInOneStatement(int a, string b, int c, string d)
        {
            string str;
            str = "a = " + a.ToString() + "b = " + b + "c = " + c.ToString() + "d = " + d; 
            return str;
        }

        static string AddStringInMutipleStatement(int a, string b, int c, string d)
        {
            string str = "";
            str += "a = " + a.ToString();
            str += "b = " + b;
            str += "c = " + c.ToString();
            str += "d = " + d;
            return str;
        }

        static string AddStringInMutipleStatementEx(int a, string b, int c, string d)
        {
            string str = "";
            str += "a = ";
            str += a.ToString();
            str += "b = ";
            str += b;
            str += "c = ";
            str += c.ToString();
            str += "d = ";
            str += d;
            return str;
        }

        static void Main(string[] args)
        {
            uint times = 10000000;
            Stopwatch timer = new Stopwatch();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInOneStatement(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("First: " + timer.ElapsedMilliseconds); // 4341 ms
            timer.Reset();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInMutipleStatement(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("Second: " + timer.ElapsedMilliseconds); // 3427 ms
            timer.Reset();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInMutipleStatementEx(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("Second: " + timer.ElapsedMilliseconds); // 5701 ms
        }
    }
}
7
  • 6
    Make your title better.. meta.stackexchange.com/questions/10647/… Commented Apr 9, 2014 at 7:44
  • 2
    I would recommend using String.Format() here. Commented Apr 9, 2014 at 7:45
  • 2
    How are you benchmarking the speeds? Commented Apr 9, 2014 at 7:45
  • 1
    +1, it's a legitimate question. I can reproduce your observation (release build): 3078ms, 2530ms, 3297ms. Commented Apr 9, 2014 at 7:46
  • you should also be using StringBuilder here for concatenation like this and helps with memory usage and perhaps perf too. Commented Apr 9, 2014 at 7:52

3 Answers 3

5

Firstly, a few points on your benchmarking:

  • I would suggest adding a call to each method before the timing starts, so that you're not measuring JIT compilation time
  • I would suggest adding a call to GC.Collect() after each test, so that garbage created by one test doesn't affect another
  • You may get significantly different results on x64 vs x86

There are various costs here:

  • Method call overhead
  • String creation
  • Array creation (hidden)
  • Integer to string conversion

In the first method, the compiler is actually transforming your code into:

string[] bits = new string[] { "a = ", a.ToString(),
                               "b = ", b,
                               "c = ", c.ToString(),
                               "d = ", d };
return string.Concat(bits);

The second and third methods create several intermediate strings; it looks like they create roughly the same number of intermediates (because each line in the second method creates two) but the third method requires more copying - the intermediate strings all contain "the whole of the string so far" whereas half of the intermediate strings in the second method are just short ("b = " + b etc). That may be responsible for the difference.

I suspect in this case the cost of the array creation (and population) in the first method outweighs the cost of the intermediate strings in the second method. Indeed, changing the first method to just create and population the array, that appears to take up more than half of the running time (on my machine). That creation/population includes the int.ToString() calls as well, mind you... which seem to be a large part of the expense too.

I've removed the int.ToString() part in a local copy of your benchmark, and the result still holds - but is even clearer as there's less constant overhead.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks Jon! I'd like to ask more about: str += "a = " + a.ToString(); Does it create only 1 intermediate string? I thought it's like: string temp1 = "a = " + a.ToString(); string temp2 = str + temp1; str = temp2; Am I wrong?
In general running WaitForPendingFinalizers is also not a bad idea after calling Collect though in this case of course it would not make a difference.
3

Probably because of a compiler optimisation, inspect the IL, infact I think I remember Eric Lippert himself writing such an optimisation for MS's C# compiler.

Yup here it is: http://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/

Comments

1

They are compiled differently.

Take a look:

.method private hidebysig static string AddStringInOneStatement(int32 a, string b, int32 c, string d) cil managed
{
    .maxstack 3
    .locals init (
        [0] string str,
        [1] string CS$1$0000,
        [2] string[] CS$0$0001)
    L_0000: nop 
    L_0001: ldc.i4.8 
    L_0002: newarr string
    L_0007: stloc.2 
    L_0008: ldloc.2 
    L_0009: ldc.i4.0 
    L_000a: ldstr "a = "
    L_000f: stelem.ref 
    L_0010: ldloc.2 
    L_0011: ldc.i4.1 
    L_0012: ldarga.s a
    L_0014: call instance string [mscorlib]System.Int32::ToString()
    L_0019: stelem.ref 
    L_001a: ldloc.2 
    L_001b: ldc.i4.2 
    L_001c: ldstr "b = "
    L_0021: stelem.ref 
    L_0022: ldloc.2 
    L_0023: ldc.i4.3 
    L_0024: ldarg.1 
    L_0025: stelem.ref 
    L_0026: ldloc.2 
    L_0027: ldc.i4.4 
    L_0028: ldstr "c = "
    L_002d: stelem.ref 
    L_002e: ldloc.2 
    L_002f: ldc.i4.5 
    L_0030: ldarga.s c
    L_0032: call instance string [mscorlib]System.Int32::ToString()
    L_0037: stelem.ref 
    L_0038: ldloc.2 
    L_0039: ldc.i4.6 
    L_003a: ldstr "d = "
    L_003f: stelem.ref 
    L_0040: ldloc.2 
    L_0041: ldc.i4.7 
    L_0042: ldarg.3 
    L_0043: stelem.ref 
    L_0044: ldloc.2 
    L_0045: call string [mscorlib]System.String::Concat(string[])
    L_004a: stloc.0 
    L_004b: ldloc.0 
    L_004c: stloc.1 
    L_004d: br.s L_004f
    L_004f: ldloc.1 
    L_0050: ret 
}

...

.method private hidebysig static string AddStringInMutipleStatement(int32 a, string b, int32 c, string d) cil managed
{
    .maxstack 3
    .locals init (
        [0] string str,
        [1] string CS$1$0000)
    L_0000: nop 
    L_0001: ldstr ""
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr "a = "
    L_000d: ldarga.s a
    L_000f: call instance string [mscorlib]System.Int32::ToString()
    L_0014: call string [mscorlib]System.String::Concat(string, string, string)
    L_0019: stloc.0 
    L_001a: ldloc.0 
    L_001b: ldstr "b = "
    L_0020: ldarg.1 
    L_0021: call string [mscorlib]System.String::Concat(string, string, string)
    L_0026: stloc.0 
    L_0027: ldloc.0 
    L_0028: ldstr "c = "
    L_002d: ldarga.s c
    L_002f: call instance string [mscorlib]System.Int32::ToString()
    L_0034: call string [mscorlib]System.String::Concat(string, string, string)
    L_0039: stloc.0 
    L_003a: ldloc.0 
    L_003b: ldstr "d = "
    L_0040: ldarg.3 
    L_0041: call string [mscorlib]System.String::Concat(string, string, string)
    L_0046: stloc.0 
    L_0047: ldloc.0 
    L_0048: stloc.1 
    L_0049: br.s L_004b
    L_004b: ldloc.1 
    L_004c: ret 
}

...

.method private hidebysig static string AddStringInMutipleStatementEx(int32 a, string b, int32 c, string d) cil managed
{
    .maxstack 2
    .locals init (
        [0] string str,
        [1] string CS$1$0000)
    L_0000: nop 
    L_0001: ldstr ""
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr "a = "
    L_000d: call string [mscorlib]System.String::Concat(string, string)
    L_0012: stloc.0 
    L_0013: ldloc.0 
    L_0014: ldarga.s a
    L_0016: call instance string [mscorlib]System.Int32::ToString()
    L_001b: call string [mscorlib]System.String::Concat(string, string)
    L_0020: stloc.0 
    L_0021: ldloc.0 
    L_0022: ldstr "b = "
    L_0027: call string [mscorlib]System.String::Concat(string, string)
    L_002c: stloc.0 
    L_002d: ldloc.0 
    L_002e: ldarg.1 
    L_002f: call string [mscorlib]System.String::Concat(string, string)
    L_0034: stloc.0 
    L_0035: ldloc.0 
    L_0036: ldstr "c = "
    L_003b: call string [mscorlib]System.String::Concat(string, string)
    L_0040: stloc.0 
    L_0041: ldloc.0 
    L_0042: ldarga.s c
    L_0044: call instance string [mscorlib]System.Int32::ToString()
    L_0049: call string [mscorlib]System.String::Concat(string, string)
    L_004e: stloc.0 
    L_004f: ldloc.0 
    L_0050: ldstr "d = "
    L_0055: call string [mscorlib]System.String::Concat(string, string)
    L_005a: stloc.0 
    L_005b: ldloc.0 
    L_005c: ldarg.3 
    L_005d: call string [mscorlib]System.String::Concat(string, string)
    L_0062: stloc.0 
    L_0063: ldloc.0 
    L_0064: stloc.1 
    L_0065: br.s L_0067
    L_0067: ldloc.1 
    L_0068: ret 
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.