代码之家  ›  专栏  ›  技术社区  ›  Raudel Ravelo

编译器使用经典交换对我的字符串反转方法进行了什么优化?

  •  4
  • Raudel Ravelo  · 技术社区  · 7 年前

    问题背景

    我读过 this 问题是关于如何尽可能快地反转字符串。我发现其中一个答案是比较不同的方法。在其中一种情况下,它们只是运行一个循环,从位置交换元素 i 一个在位置上 string.Length-1-i 但他们通过异或使用已知的棘手交换。我想知道,与通过时态变量使用经典交换的相同方法相比,通过XOR使用交换反转字符串的速度有多快。令人惊讶的是,与XOR相比,我几乎提高了50%。

    问题是

    编译器是否在幕后发挥了神奇的作用,为什么我会得到这个结果?

    使用基准测试修改的代码

    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();
            }
        }
    }
    
    3 回复  |  直到 7 年前
        1
  •  4
  •   Rey Cruz    7 年前

    原因很简单,让我们检查一下每个函数在IL代码中有多少个操作。 但首先,让我们看看这两个函数在时间上的真正差异。您说过XOR函数几乎比其他函数慢50%,当我在调试模式下运行代码时,我得到了结果,但您必须在发布模式下运行代码,以完全允许优化器完成其工作:)。在释放模式下,XOR功能几乎慢3倍。

    这些图片包含for循环内部件的IL代码,这是唯一一个发生变化的部件。

    第一幅图是带有temp变量的函数

    Classic code with the temp variable

    第二幅图是带有XOR的函数

    XOR function

    正如您所看到的,指令数量的差异是巨大的,14对34。3倍的时间差来自以下操作 转换u2 那有点贵。

        2
  •  2
  •   Zohar Peled    7 年前

    我同意哈罗德的观点。异或交换并不比使用临时变量快。

    我认为异或交换的神话可以追溯到为新变量分配内存是一项耗时的工作的时代。

    起初,我认为这可能与类型有关,并且可能在中使用XOR交换 int 数组将提供比on更好的结果 char 数组,因此我在您的基准测试的基础上构建了 内景 仅针对文本的数组-对于 内景 XOR交换只比使用临时变量慢。

    更新:

    正如不信教者Damien\u在对你的问题的评论中所写的 answer R.Martinho Fernandes在您链接的问题中给出的答案实际上是第一页中唯一正确反转字符串的答案,即使使用英语以外的语言也是如此。
    事实上,根据这个答案 one of it's comments 我编写了一个扩展方法来正确反转字符串。

    在我的母语(希伯来语)中,我们有各种各样的点和符号来指定元音,还有数组(或 IEnumerable )是做错了,就像法国的例子一样。

    它比常规的基于交换的实现要慢得多(大约是基于XOR的交换的10倍),但我认为这不会是一个问题,因为反转一个字符串不是经常做的事情,在一个紧密的循环中反转许多字符串甚至更少。
    根据您的基准测试方法进行测试,使用此方法反转长度为10000的10000字符串大约需要13.5秒。
    如果有人编写的程序需要对如此长的字符串进行如此多的反转,我会非常惊讶。

    下面是我的国际安全字符串反向实现,我希望有人能从中受益:

    public static string Reverse(this string source)
    {
        if (string.IsNullOrEmpty(source))
        {
            return source;
        }
        var info = new StringInfo(source);
        var sb = new StringBuilder();
    
        for (int i = info.LengthInTextElements - 1; i > -1; i--)
        {
            sb.Append(info.SubstringByTextElements(i, 1));
        }
        return sb.ToString();
    }
    
        3
  •  0
  •   programtreasures    7 年前

    您可以使用测试以下代码 Array.Reverse ,它将比您提到的其他方法更有效, 大堆颠倒 本机编码 维护和理解非常简单。

        public static string ReverseArray(string text)
        {
            if (text == null) return null;
    
            char[] array = text.ToCharArray();
            Array.Reverse(array);
            return new String(array);
        }
    

    下面是要演示的完整代码,

    委托字符串StringDelegate(字符串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 ReverseArray(string text)
        {
            if (text == null) return null;
    
            // this was posted by petebob as well 
            char[] array = text.ToCharArray();
            Array.Reverse(array);
            return new String(array);
        }
    
        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("Array (Length: {0})", l), ReverseArray, iterations, text);
                Benchmark(String.Format("Xor (Length: {0})", l), ReverseXor, iterations, text);
                Console.WriteLine();
            }
            Console.Read();
        }
    

    注意:我已经更正了您反转字符串的代码,它没有正确地将字符串反转为您的帖子