【问题标题】:A Faster Way To Reverse A String? [duplicate]反转字符串的更快方法? [复制]
【发布时间】:2013-06-05 19:24:22
【问题描述】:

下面是我可以创建的用于反转字符串的最快代码

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);
}

我想解决这个等式中的每个瓶颈,以使其尽可能快。到目前为止,我唯一能找到的是我只部分理解的 Array Bounds 检查。如果您使用.Length,编译器决定不检查边界,但如果您像我在for 循环中那样递减,它仍然会进行边界检查,是否有任何方法可以禁用它?有人可以将其转换为使用指针来避免边界检查吗,我想测试 100k+ 个字符范围内的字符串的速度差异。

根据下面的 cmets 和帖子,这是我迄今为止提出的。

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);
}

上述解决方案比建议的重复问题答案要快得多。这个问题实际上是在解决 5000 * 26 个字符 + 范围内的反转问题。我仍然想用指针来测试一下,看看是否没有瓶颈,尤其是在这么多字符的情况下。

【问题讨论】:

  • 不会成为你的瓶颈。说真的。
  • 使用正确的长度 (text.Length) 初始化 StringBuilder - 这将防止调整缓冲区的大小。
  • 我的问题是乞求探索 c# 中的指针替代方案并批评我目前的想法。
  • new StringBuilder(text.Length) 替换new StringBuilder() 应该可以消除所有不必要的内存[重新]分配。
  • This answer 在重复的问题中提供了带有指针的解决方案。不要停留在接受的答案上,看看所有的答案。虽然对于大字符串,您可能希望删除 stackalloc

标签: c# pointers


【解决方案1】:
var arr = x.ToCharArray();
Array.Reverse(arr);
return new string(arr);

但是请注意,这将反转任何 unicode 修饰符(重音等)。

基准测试:

Array.Reverse: 179ms
StringBuilder: 475ms

与:

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);
}

如果我们尝试一个长度为 500 的字符串并循环 500000 次:

Array.Reverse: 480ms
StringBuilder: 1176ms

我也尝试在其中添加unsafe,即

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

这没有任何区别。

我也加入了 JeffRSon 的回答;我明白了:

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

(对于 500 长度 x 5000 次迭代测试)

【讨论】:

  • 这肯定更快吗?好奇多少钱...
  • @Mitch 好吧,我看不出它可能会更慢; StringBuilder 需要至少进行尽可能多的分配,缺点是 a:可能过大,b:可能需要调整大小;循环只是一个基本循环。我想这取决于StringBuilder 是否乐意给你它的私人string(如果它是一个精确的大小匹配)。那是给OP测试的,我太忙了。
  • 对不起,我认为问题是“一种更快的反转字符串的方法?”我们通常不告诉人们进行基准测试吗? ;)
  • 我在我的机器上对它进行了几次基准测试,在 100k+ 字符时,这在我的机器上肯定会慢一些。对于任何正常操作,我确信这很好。正如米奇指出的那样,这不是一种更快的反转字符串的方法。
  • @CodeCamper 啊,它也可能是.NET Framework版本特定的; StringBuilder 这些年来发生了很多变化。我的目标是 .NET 4.5;你?我已经在 x86 和 x64 上进行了测试——这也很重要。对我来说,Array.Reverse 方法对于小型(500 等)字符串仍然是最快的。对于非常大的字符串(100000),指针代码稍微快(不足以跳舞)
【解决方案2】:

这是一个基于指针的解决方案:

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);
        }

摆脱数组索引 (sarr[--idx]) 可能会更快:

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);
        }

【讨论】:

  • 不错的方法;我对它进行了基准测试,它仍然比平坦的Array.Reverse 稍慢 - 我不声称知道为什么;但很好
  • 编辑:对此进行澄清 - 它似乎取决于字符串长度;对于小字符串,Array.Reverse 似乎稍微更快;对于大字符串,指针代码似乎 稍微 更快 - 没有足够的差异,我会说两者都是明显的赢家
  • 我不喜欢基于索引的数组访问,因为这涉及到每次都添加指针 - 现在它只是指针......
【解决方案3】:

在创建StringBuilder 时设置容量,这样它就不必在循环期间增长并分配更多内存。将参数分配给局部变量是不必要的步骤,因为参数已经是局部变量。

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]);
  }
}

这只是删除任何不必要工作的基本步骤。如果您确实对代码有性能问题,则需要分析生成的代码的作用,并可能根据当前框架和硬件创建不同的版本来执行不同的操作。

【讨论】:

    猜你喜欢
    • 2019-03-14
    • 2014-04-02
    • 2022-11-16
    • 2017-12-09
    • 1970-01-01
    • 1970-01-01
    • 2017-12-21
    • 2021-06-25
    相关资源
    最近更新 更多