【问题标题】:Inlining a method to increase performance but it executes slower in C#内联方法以提高性能,但在 C# 中执行速度较慢
【发布时间】:2020-07-03 09:28:12
【问题描述】:

我正在尝试删除一种方法来优化我的代码。似乎我没有获得任何性能,而且“优化”的代码更慢!调用方法比在循环中创建变量更快吗?为什么?

为什么下面的代码更快(1.3-1.5 秒)

public void getPureText(string notClearedText)
{
    string peeledText = "";


    foreach (var symbol in notClearedText)
    {
        if(isCyrillic(symbol))
            peeledText += Char.ToLower(symbol);
        else
            peeledText += " ";
    }
}

private bool isCyrillic(int letterCode)
{
    switch (letterCode)
    {
        case 1028: // Є
        case 1108: // є
        case 1030: // І
        case 1110: // і
        case 1031: // Ї
        case 1111: // ї
        case 1168: // Ґ
        case 1169: // ґ
        case 32:  // " "
        case 39:  // '
                  //case 45:  // -
            return true;
        default:
            return
                1040 <= letterCode && letterCode <= 1103 &&  // Cyrillic
                letterCode != 1066 &&  // Ъ
                letterCode != 1067 &&  // Ы
                letterCode != 1098  // ъ
                ||
                65 <= letterCode && letterCode <= 90
                ||
                97 <= letterCode && letterCode <= 122
                ;
    }

}

比“优化”版本(1.5-1.8 秒)?我错过了什么?

public void getPureText(string notClearedText)
{
    string peeledText = "";

    foreach (var symbol in notClearedText)
    {
      int letterCode = symbol;

      switch (letterCode)
      {
        case 1028: // Є
        case 1108: // є
        case 1030: // І
        case 1110: // і
        case 1031: // Ї
        case 1111: // ї
        case 1168: // Ґ
        case 1169: // ґ
        case 32:  // " "
        case 39:  // ' //case 45:  // -
            peeledText += Char.ToLower(symbol);
            break;
        default:
            if (
                1040 <= letterCode && letterCode <= 1103 && // Cyrillic
                letterCode != 1066 && // Ъ
                letterCode != 1067 && // Ы
                letterCode != 1098 // ъ
                ||
                65 <= letterCode && letterCode <= 90
                ||
                97 <= letterCode && letterCode <= 122
            )
                peeledText += Char.ToLower(symbol);
            else
                peeledText += " ";
            
            break;
         }
    }
}

我已经运行了几十个测试使用

void TestPerformance()
{
    Stopwatch sw = new Stopwatch();

    sw.Start();
    _textRepository.getPureText(RawTextExamples.veryLongText);
    sw.Stop();

    unitTestFormGuess.show(sw.Elapsed.ToString());
}

附: 如您所见,我从 getPureText() 中删除了一些代码,使其返回 void,然后再次测量时间:结果相同。有问题……

附言 配置:调试。

编辑

对于 peeledText 将类型 string 替换为 StringBuilder

配置:发布。

字符串大小相同:150 KB。

3 系列测试,每个测试 500 次迭代。

  • 使用方法isCyrillic代码:6.63-6.70毫秒

  • 内联:6.80-6.90 毫秒(仍然较慢 o_0)

  • 内联但使用正则表达式:6.62-6.70 毫秒

  • 使用方法 isCyrillic 但使用 HashSet 而不是 switch:7.89-8.32 毫秒。

【问题讨论】:

  • 不要在 Debug 中执行基准测试,始终使用 Release。不要在附加调试器的情况下执行此操作,也不要自己执行此操作(使用 Benchmarks.Net 之类的东西)。如果你自己做,总是在开始计时之前多次执行该方法,以便正确地 JIT 方法
  • 方法调用几乎没有开销。在方法之间移动代码不会改变性能一点点。这里一个潜在的性能提升是使用StringBuilder 而不是所有那些+= 调用。但这也可能不会更快,因为创建类的开销可能超过堆栈上的分配
  • 您应该对发布版本而不是调试版本进行基准测试。重复测试时,时间相差多少?另外,StringBuilder 有效果吗?我很久没有跟上 .NET 了,但是循环中的 += 一次会非常慢(二次)(如果现在已经优化,可能会有复杂性限制,之后就不可能了——虽然这可能不是这里的区别)。同样,通常两个编译器都能够进行优化,例如内联。
  • 我还要构建字符串,然后在整个字符串上调用ToLower,而不是每个单独的字符,但是你必须再次测试它以查看它是否有任何区别
  • RegEx 也是另一种选择...

标签: c# if-statement optimization inline


【解决方案1】:

如果代码需要 1.6 秒来运行处理一个输入字符串,那么它是一个相当大的字符串。我会 stop using string concatenation (+=) 并开始使用 System.Text.StringBuilder,它可能更快且内存效率更高:

public void getPureText(string notClearedText)
{
    var peeledText = new StringBuilder(notClearedText.Length);

    foreach (var symbol in notClearedText)
    {
        if(isCyrillic(symbol))
            peeledText.Append(Char.ToLower(symbol));
        else
            peeledText.Append(' ');
    }
}

如果您在一个有点紧的循环中调用getPureText,您可以考虑重用buffer,只需清除它并避免每次调用的新成本。

发布模式下再次对其进行基准测试,放弃预热运行并且不附加调试器。如果这仍然不能满足您的性能目标,那么开始微优化内联调用等。抖动是非常聪明的优化代码,因此它可能不会给您带来太多好处。

long Benchmark(string veryLongText, int repetitions)
{
    getPureText(veryLongText); //warmup
    var watch = Stopwatch.StartNew();

    for (var i = 0; i < repetitions; i++)
        getPureText(veryLongText);

    watch.Stop();
    return watch.ElapsedMilliseconds/repetitions;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 2010-12-08
    • 2015-11-20
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    相关资源
    最近更新 更多