【问题标题】:How can C#'s string.IndexOf perform so fast, 10 times faster than ordinary for loop find?C#的string.IndexOf怎么会执行得这么快,比普通的for循环find快10倍?
【发布时间】:2012-05-18 03:54:06
【问题描述】:

我有一个很长的字符串(60MB),我需要在其中找到有多少对“”。


我首先尝试了自己的方式:

        char pre = '!';
        int match1 = 0;
        for (int j = 0; j < html.Length; j++)
        {
            char c = html[j];
            if (pre == '<' && c == '>') //find a match
            {
                pre = '!';
                match1++;
            }
            else if (pre == '!' && c == '<')
                pre = '<';
        }

上面的代码在我的字符串上运行了大约 1000 毫秒


然后我尝试使用string.IndexOf

        int match2 = 0;
        int index = -1;
        do
        {
            index = html.IndexOf('<', index + 1);
            if (index != -1) // find a match
            {
                index = html.IndexOf('>', index + 1);
                if (index != -1)
                   match2++;
            }
        } while (index != -1);

上面的代码只运行了大约 150 毫秒


我想知道是什么魔力让string.IndexOf 运行这么快

谁能启发我?


编辑

好的,根据@BrokenGlass 的回答。我修改了我的代码,他们不检查配对,而是检查字符串中有多少个“


        for (int j = 0; j < html.Length; j++)
        {
            char c = html[j];
            if (c == '>')
            {
                match1++;
            }
        }

上面的代码运行了大约 760 毫秒


使用IndexOf

        int index = -1;
        do
        {
            index = html.IndexOf('<', index + 1);
            if (index != -1)
            {
                match2++;
            }
        } while (index != -1);

上面的代码运行了大约 132 毫秒仍然非常非常快。


编辑 2

阅读@Jeffrey Sax 的评论后,我意识到我是在 VS 中以 Debug 模式运行的。

然后我在发布模式下构建并运行,好的,IndexOf 仍然更快,但不再那么快了。

结果如下:

对于配对计数:207ms VS 144ms

对于正常的一个字符数:141ms VS 111ms

我自己的代码的性能确实得到了改善。


经验教训:当您进行基准测试时,请在发布模式下进行!

【问题讨论】:

  • 您是否在测试时启用了优化?
  • 你看过string.IndexOf在幕后做了什么吗?
  • @MartinLiversage 如何启用优化?
  • @zimdanen 不,这就是我真正要问的
  • 尽量不要使用html.length,尝试int len =html.length;,然后在for循环中使用len

标签: c# string search indexof


【解决方案1】:

使用 switch 语句而不是 if 测试也可以加快速度。这段代码偶尔会胜过我机器上的 indexof 代码。

        int count = 0;
        bool open = false;
        for (int j = 0; j < testStr.Length; j++)
        {  
            switch (testStr[j])
            {
                case '<':
                    open = true;
                    break;
                case '>':
                    if (open)
                       count++;

                    open = false;
                    break;         
            }
        }

【讨论】:

  • 如果你有很多比较,建立一个哈希表会加快速度
【解决方案2】:

直接比较您的托管实现和String.IndexOf 方法有点谬误。 IndexOf 的实现主要是本机代码,因此具有与托管实现不同的一组性能特征。特别是本机代码避免了类型安全检查及其相应的开销,这些开销由 CLR 在托管算法中注入。

一个例子是数组索引的使用

char c = html[j];

在托管代码中,此语句将验证 j 是数组 html 的有效索引,然后返回该值。本机代码等效项仅返回内存偏移量,无需额外检查。这种检查的缺乏给原生代码带来了固有的优势,因为它可以避免在循环的每次迭代中添加额外的分支指令。

请注意,这种优势不是绝对的。 JIT 可以很好地检查此循环并确定 j 永远不会是无效索引并忽略生成的本机代码中的检查(在某些情况下它确实会这样做 IIRC)。

【讨论】:

    【解决方案3】:

    我唯一想到的是IndexOf iniside 字符串类的实际实现,它调用

    callvirt    System.String.IndexOf
    

    如果我们使用反射器的力量(尽可能多地)最终进入

    CompareInfo.IndexOf(..)
    

    调用,而是使用超快的windows原生函数FindNLSString

    【讨论】:

      【解决方案4】:

      我希望string.IndexOf 与您的第一个代码示例相比至少运行速度快两倍(除了可能在那里进行的任何其他优化),因为您检查了 both 中的开始和结束字符你的每一次迭代。另一方面,您使用string.IndexOf 的实现将仅在成功找到开始字符后检查结束字符。这大大减少了每次迭代的操作次数(少了一个比较)。

      【讨论】:

      • 其实IndexOf也检查了两次。
      • @Jack:不,它没有:您将找到的起始字符&lt; 的索引传递给string.IndexOf - 所以您只在该索引之后寻找结束字符的第一次出现在您的输入字符串中
      • 你的意思是如果我发现 的搜索将从 10 开始,对吗?我认为在我的第一个实现中也是如此,它也是一次性运行的。
      • 好的,我知道你的意思。你的意思是if 部分。我现在将修改我的代码并再次运行。
      • 对于你的最后一段,我认为IndexOf不能提前退出,因为整个字符串中的最后一个字符串是
      【解决方案5】:

      您是否在 Visual Studio 中运行计时?如果是这样,仅出于这个原因,您的代码运行速度就会明显变慢。

      除此之外,在某种程度上,您还在比较苹果和橙子。这两种算法的工作方式不同。

      IndexOf 版本交替查找左括号only 和右括号only。您的代码遍历整个字符串并保留一个状态标志,指示它是在寻找左括号还是右括号。这需要更多的工作,预计会更慢。

      这里有一些代码,其比较方式与您的 IndexOf 方法相同。

      int match3 = 0;
      for (int j = 0; j < html.Length; j++) {
          if (html[j] == '<') {
              for (; j < html.Length; j++)
                  if (html[j] == '>')
                      match3++;
          }
      }
      

      在我的测试中,这实际上比 IndexOf 方法快了大约 3 倍。原因?字符串实际上并不像单个字符的序列那么简单。有标记、重音等等。String.IndexOf 可以妥善处理所有这些额外的复杂性,但这是有代价的。

      【讨论】:

      • 感谢您,因为您提到了Are you running your timings from within Visual Studio?
      猜你喜欢
      • 2017-11-29
      • 2021-10-01
      • 1970-01-01
      • 2012-09-19
      • 1970-01-01
      • 2022-06-12
      • 1970-01-01
      • 2021-11-25
      • 2021-09-13
      相关资源
      最近更新 更多