【问题标题】:Dynamic compilation for performance动态编译性能
【发布时间】:2010-11-24 11:27:06
【问题描述】:

我知道如何通过动态代码生成来提高性能,但我不确定哪种方法是解决此问题的最佳方法。

假设我有一堂课


class Calculator
{
  int Value1;
  int Value2;
  //.......... 
  int ValueN;

  void DoCalc()
  {
    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    //....
    //....
    //....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }
}

DoCalc 方法处于最低级别,在计算过程中被多次调用。另一个重要方面是 ValueN 仅在开始时设置,在计算过程中不会更改。 DoCalc 方法中的许多 if 是不必要的,因为许多 ValueN 为 0。所以我希望动态代码生成有助于提高性能。

例如,如果我创建一个方法


  void DoCalc_Specific()
  {
    const Value1 = 0;
    const Value2 = 0;
    const ValueN = 1;

    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    ....
    ....
    ....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }

并在打开优化的情况下编译它 C# 编译器足够聪明,只保留必要的东西。所以我想在运行时根据 ValueN 的值创建这样的方法,并在计算过程中使用生成的方法。

我想我可以为此使用表达式树,但表达式树仅适用于简单的 lambda 函数,因此我不能在函数体内使用 if、while 等。所以在这种情况下,我需要以适当的方式更改此方法。

另一种可能性是将必要的代码创建为字符串并动态编译。但是如果我能采用现有的方法并相应地修改它,那对我来说会更好。

还有 Reflection.Emit,但我不想坚持使用它,因为它很难维护。

顺便说一句。我不仅限于 C#。因此,我愿意接受最适合此类问题的编程语言的建议。出于几个原因,LISP 除外。

一个重要的澄清。 DoValue1RelatedStuff() 不是我算法中的方法调用。这只是一些基于公式的计算,而且速度非常快。我应该这样写的


if (Value1 > 0)
{
  // Do Value1 Related Stuff
}

我已经运行了一些性能测试,我可以看到在禁用两个 if 时,优化的方法比冗余 if 快大约 2 倍。

这是我用于测试的代码:


    public class Program
    {
        static void Main(string[] args)
        {
            int x = 0, y = 2;

            var if_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                WithIf(x, y);
            }
            var if_et = DateTime.Now.Ticks - if_st;
            Console.WriteLine(if_et.ToString());

            var noif_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                Without(x, y);
            }
            var noif_et = DateTime.Now.Ticks - noif_st;
            Console.WriteLine(noif_et.ToString());

            Console.ReadLine();

        }

        static double WithIf(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i  < 100; i++)
            {
                if (x > 0)
                {
                    result += x * 0.01;
                }
                if (y > 0)
                {
                    result += y * 0.01;
                }
            }
            return result;
        }

        static double Without(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i < 100; i++)
            {
                result += y * 0.01;
            }
            return result;
        }
    }

【问题讨论】:

  • 如果你想使用System.Linq.Expressions,你总是可以有条件地构建你的表达式树。它可能看起来与您最初编写的内容不完全相同,但它几乎肯定会工作,并且几乎肯定会比 System.Reflection.Emit 更易于维护,而且它也很有可能表现得更好。
  • 表达式树的解决方案似乎是最有希望的。但总的来说,我希望有一个能够以这种方式优化任何方法的工具。

标签: c# performance code-generation dynamic-compilation


【解决方案1】:

我很惊讶发现评估 if 语句的开销值得动态发出代码的努力。

现代 CPU 支持 branch predictionbranch predication,这使得小段代码中的分支开销接近于零。

您是否尝试过对代码的两个手动编码版本进行基准测试,一个包含所有 if 语句但大多数情况下提供零值,另一个删除所有相同的 if 分支?

【讨论】:

  • 我同意,N 必须是数百甚至数千,并且在实践中需要省略大多数情况,因为它是运行时间的一个非常重要的部分。跨度>
  • 几年来我在 ASM/IL 级别上做的不多,但我相信当前的 Intel 处理器会在 IP(指令指针)到达之前预先确定正确的分支。有谁知道详情吗?
  • 我不是最先进的分支预测专家,在 C# 代码和执行分支的处理器之间发生了很多事情,但我很确定现代处理器会预测分支错误在条件不变的情况下最多一次(除了条件的数量很大,分支预测缓存不能存储所有预测)。
【解决方案2】:

我通常不会考虑这样的优化。 DoValueXRelatedStuff() 做了多少工作?超过 10 到 50 个处理器周期?是的?这意味着您将构建一个相当复杂的系统来节省不到 10% 的执行时间(这对我来说似乎很乐观)。这很容易降至不到 1%。

是否没有其他优化的空间?更好的算法?你真的需要消除只需要一个处理器周期的单个分支(如果分支预测是正确的)?是的?难道你不应该考虑用汇编程序或其他更特定于机器的东西而不是使用 .NET 来编写代码吗?

您能否给出N 的顺序、典型方法的复杂度以及通常计算结果为真的表达式的比率?

【讨论】:

  • DoValueXRelatedStuff() 实际上不是我算法中的方法调用。这只是一些表达式计算,所以它很快。基本上它只是几个加法、乘法和可能的除法。
  • N的顺序是什么?以及比率表达式真假?
  • 现在 N 是 8。在典型情况下,真假比是 3/5。但我需要在下一个版本中将 N 更改为 20 左右。
  • 即使在 20 的情况下,我也倾向于相信您不会获得那么快的速度。你能像 Eric J. 建议的那样描述它吗?我真的很好奇结果。或者更好的是,您能否提供一个更完整的代码片段,其中包含两个或三个完整的 DoValueXRelatedStuff() 片段。我也想做一些分析。
  • 我按照 Eric J. 的建议对其进行了测试。我已经为此测试添加了代码。不幸的是,我无法发布完整的代码,因为它是用 Pascal 编写的,并且适用于一些特定的数据结构。
【解决方案3】:

如果您真的很喜欢代码优化 - 在您做任何事情之前 - 运行分析器!它将向您展示瓶颈在哪里以及哪些领域值得优化。

另外 - 如果语言选择不受限制(LISP 除外),那么在性能方面没有什么能胜过汇编程序;)

我记得通过使用汇编程序重写一些内部函数(比如你拥有的那个)来实现一些性能魔法。

【讨论】:

  • 用汇编器写这个是不切实际的。首先,代码相当复杂,用 asm 重写会花费太多时间。其次,我不能用汇编程序进行这种动态编译。
【解决方案4】:

在你做任何事情之前,你真的有问题吗?

即它运行的时间是否足以打扰您?

如果是这样,找出真正需要时间的东西,不是你猜的This 是我用来查看时间流逝的快速、肮脏和高效的方法。

现在,您正在谈论解释与编译。解释代码通常比编译代码慢 1-2 个数量级。原因是解释器不断地弄清楚下一步该做什么,然后忘记,而编译的代码只知道

如果你处于这种情况,那么付出翻译的代价来获得编译代码的速度可能是有意义的。

【讨论】:

    猜你喜欢
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-01
    • 1970-01-01
    • 2017-02-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多