Question Background
I read this question that is about how to reverse a string as fast as possible. I found that one of the answers was comparing different methods. In one of them, they just run a loop swapping elements from position i
with the one at position string.Length-1-i
but they use the known tricky swap via XOR. I was wondering how faster is reversing the string using the swap via XOR in comparison with the same method using the classic swap via a temporal variable. Surprisingly I'm getting almost a 50% improvement over the XOR one.
The Question
Is the compiler doing something magic behind the scenes, why I'm I getting this result?
The modified code with the Benchmarks
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ContestLibrary
{
public class Problem
{
delegate string StringDelegate(string s);
static void Benchmark(string description, StringDelegate d, int times, string text)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int j = 0; j < times; j++)
{
d(text);
}
sw.Stop();
Console.WriteLine("{0} Ticks {1} : called {2} times.", sw.ElapsedTicks, description, times);
}
public static string ReverseXor(string s)
{
char[] charArray = s.ToCharArray();
int len = s.Length - 1;
for (int i = 0; i < len; i++, len--)
{
charArray[i] ^= charArray[len];
charArray[len] ^= charArray[i];
charArray[i] ^= charArray[len];
}
return new string(charArray);
}
public static string ReverseClassic(string s)
{
char[] charArray = s.ToCharArray();
int len = s.Length-1;
for (int i = 0; i < len; i++, len--)
{
char temp = charArray[len];
charArray[len] = charArray[i];
charArray[i] = temp;
}
return new string(charArray);
}
public static string StringOfLength(int length)
{
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
sb.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
}
return sb.ToString();
}
static void Main(string[] args)
{
int[] lengths = new int[] {1,10,100,1000, 10000, 100000};
foreach (int l in lengths)
{
int iterations = 10000;
string text = StringOfLength(l);
Benchmark(String.Format("Classic (Length: {0})", l), ReverseClassic, iterations, text);
Benchmark(String.Format("Xor (Length: {0})", l), ReverseXor, iterations, text);
Console.WriteLine();
}
Console.Read();
}
}
}
The reason is very simple, lets check how many operations in IL code each function has. But first, lets see the real difference in time of both functions. You said that the XOR function is almost 50% slower than the other, when I run the code in Debug mode I have that results, but you must run the code in Release mode to fully allow the optimizer do its job :). In release mode the XOR functions is almost 3x slower.
The pictures have the IL code of the part inside the for loop, that is the only piece that changes.
The first picture is the function with the temp variable
The second picture is the function with the XOR
As you can see the difference in the number of instructions is huge, 14 against 34. The 3x difference in time come from some operations like conv.u2 that are a little expensive.