3

Below is the fastest code I could create for reversing a String

public static void ReverseFast(string x)
{
    string text = x;
    StringBuilder reverse = new StringBuilder();

    for (int i = text.Length - 1; i >= 0; i--)
    {
        reverse.Append(text[i]);
    }
      Console.WriteLine(reverse);
}

I want to address every bottleneck in this equation to make it as fast as possible. The only one I can find so far is the Array Bounds check which I only partially understand. Is there anyway to disable this as I understand it if you use .Length the compiler decides not to check the bounds but if you are decrementing as I am in the for loop it still does the boundary check? Can someone convert this to use pointers for me which would avoid the boundary check, I would like to test the speed difference for strings in the range of 100k+ characters.

Based on the comments and posts below this is what I have come up with so far.

public static void ReverseFast(string x)
{
    StringBuilder reverse = new StringBuilder(x.Length);
    for (int i = x.Length - 1; i >= 0; i--)
    {
        reverse.Append(x[i]);
    }
    Console.WriteLine(reverse);
}

This above solution is way faster than the suggested duplicate question answer. This question is really addressing reversal in the range of 5000 * 26 characters +. I would still like to test this out using pointers to really see if there is no bottleneck especially with such large amount of characters.

GSerg
  • 76,472
  • 17
  • 159
  • 346
CodeCamper
  • 6,609
  • 6
  • 44
  • 94
  • 4
    That is **not** going to be your bottleneck. Seriously. – It'sNotALie. Jun 10 '13 at 07:28
  • Initialize the StringBuilder with the correct length (text.Length) - this will prevent the buffer from being resized. – JeffRSon Jun 10 '13 at 07:33
  • My question is sort of begging to explore the pointer alternatives in c# and to critique my current idea. – CodeCamper Jun 10 '13 at 07:33
  • Replacing `new StringBuilder()` with `new StringBuilder(text.Length)` should get rid of all the unnecessary memory [re]allocations. – GSerg Jun 10 '13 at 07:33
  • [This answer](http://stackoverflow.com/a/3047997/11683) in the duplicated question provides a solution with pointers. Don't stop at the accepted answer there, look through all of them. Although for big strings you might want to remove the `stackalloc`. – GSerg Jun 10 '13 at 07:45

3 Answers3

14
var arr = x.ToCharArray();
Array.Reverse(arr);
return new string(arr);

Note, however, that this will reverse any unicode modifier characters (accents, etc).

Benchmark:

Array.Reverse: 179ms
StringBuilder: 475ms

With:

static void Main()
{
    string text = new string('x', 100000);
    GC.Collect();
    GC.WaitForPendingFinalizers();
    var watch = Stopwatch.StartNew();
    const int LOOP = 1000;
    for (int i = 0; i < LOOP; i++)
    {
        var arr = text.ToCharArray();
        Array.Reverse(arr);
        string y = new string(arr);
    }
    watch.Stop();
    Console.WriteLine("Array.Reverse: {0}ms", watch.ElapsedMilliseconds);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        var reverse = new StringBuilder(text.Length);
        for (int j = text.Length - 1; j >= 0; j--)
        {
            reverse.Append(text[j]);
        }
        string y = reverse.ToString();
    }
    watch.Stop();
    Console.WriteLine("StringBuilder: {0}ms", watch.ElapsedMilliseconds);
}

If we try a string of length 500 and loop 500000 times:

Array.Reverse: 480ms
StringBuilder: 1176ms

I also tried adding unsafe into that, i.e.

fixed (char* c = text)
{
    for (int j = text.Length - 1; j >= 0; j--)
    {
        reverse.Append(c[j]);
    }
}

this made no difference whatsoever.

And I added in JeffRSon's answer too; I get:

Array.Reverse: 459ms
StringBuilder: 1092ms
Pointer: 513ms

(for the 500 length x 5000 iterations test)

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • is that definitely faster? curious as to how much... – Mitch Wheat Jun 10 '13 at 07:29
  • 1
    @Mitch well, I can't see any way it could be slower; the `StringBuilder` needs to do *at least* as much allocation, with the disadvantage of a: probably being oversized, and b: possibly needing a resize; the loop is just a basic loop. I guess it depends on whether `StringBuilder` is happy to give you its private `string` (if it is an exact size match). That's for the OP to test, I'm too busy. – Marc Gravell Jun 10 '13 at 07:32
  • 3
    Sorry I thought the question was "A Faster Way To Reverse A String?" Don't we usually tell people to benchmark? ;) – Mitch Wheat Jun 10 '13 at 07:33
  • 1
    I benchmarked it on my machine a few times and at 100k+ characters this is definitely slower on my machine. For any normal operation I am sure this is fine. Just not a faster way to reverse a string as Mitch pointed out. – CodeCamper Jun 10 '13 at 07:42
  • @CodeCamper 100k benchmark added; much faster – Marc Gravell Jun 10 '13 at 08:04
  • @MitchWheat happier now? – Marc Gravell Jun 10 '13 at 08:09
  • Strange on my machine I got Array.Reverse: 543ms StringBuilder: 107ms Array.Reverse: 247ms StringBuilder: 111ms Array.Reverse: 163ms StringBuilder: 130ms – CodeCamper Jun 10 '13 at 08:11
  • 1
    @CodeCamper ah, it could also be .NET Framework version specific; `StringBuilder` has had lots of changes over the years. I'm targeting .NET 4.5; you? I've tested on both x86 and x64 - that can be important too. For me the `Array.Reverse` approach was still fastest on both for small (500 etc) strings. For very large strings (100000) the pointer code was *slightly* faster (not enough to dance about) – Marc Gravell Jun 10 '13 at 08:15
4

Here's a pointer based solution:

unsafe String Reverse(String s)
        {
            char[] sarr = new char[s.Length];
            int idx = s.Length;
            fixed (char* c = s)
            {
                char* c1 = c;
                while (idx != 0)
                {
                    sarr[--idx] = *c1++;
                }
            }

            return new String(sarr);
        }

Getting rid of the array index (sarr[--idx]) the following might be faster:

unsafe String Reverse(String s)
        {
            char[] sarr = new char[s.Length];
            fixed (char* c = s)
            fixed (char* d = sarr)
            {
                char* c1 = c;
                char* d1 = d + s.Length;
                while (d1 > d)
                {
                    *--d1 = *c1++;
                }
            }

            return new String(sarr);
        }
JeffRSon
  • 10,404
  • 4
  • 26
  • 51
  • Nice approach; I benchmarked it, and it is still slightly slower than a flat `Array.Reverse` - I don't claim to know why; but very nice – Marc Gravell Jun 10 '13 at 08:13
  • Edit: clarification for that - it seems to depend on the string length; for small strings, the `Array.Reverse` seems *slightly* faster; for large strings the pointer code seems *slightly* faster - not enough difference that I'd say either is a clear winner – Marc Gravell Jun 10 '13 at 08:17
  • I didn't like the index based array access because that involves adding pointers everytime - now it's pointer only... – JeffRSon Jun 10 '13 at 08:36
2

Set the capacity when you create the StringBuilder, that way it doesn't have to grow during the loop and allocate more memory. Assigning the parameter to a local variable is an unnecessary step, as the parameter is already a local variable.

public static void ReverseFast(string text) {
  StringBuilder reverse = new StringBuilder(text.Length);
  for (int i = text.Length - 1; i >= 0; i--) {
    reverse.Append(text[i]);
  }
}

That is just the basic steps to remove any unneccesary work. If you really have a performance problem with the code, you would need to analyse what the generated code does, and possibly create different versions that do different things depending on the current framework and hardware.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005