循环中方法调用的开销是多少?

Dev*_*dse 5 c# compiler-construction performance

我一直在研究C#迷宫发生器一段时间,它可以生成类似128000x128000像素的迷宫.所有内存使用已经优化,因此我目前正在考虑加速这一代.

我发现一个问题(更多的是关注点)是以下(只是一些示例代码来说明问题):

当pixelChanged为null时,此代码在我的机器上运行大约1.4秒:

public void Go()
{
    for (int i = 0; i < bitjes.Length; i++)
    {
        BitArray curArray = bitjes[i];
        for (int y = 0; y < curArray.Length; y++)
        {
            curArray[y] = !curArray[y];
            GoDrawPixel(i, y, false);
        }
    }
}

public void GoDrawPixel(int i, int y, Boolean enabled)
{
    if (pixelChanged != null)
    {
        pixelChanged.Invoke(new PixelChangedEventArgs(i, y, enabled));
    }
}
Run Code Online (Sandbox Code Playgroud)

以下代码实际运行速度快0.4秒

public void Go()
{
    for (int i = 0; i < bitjes.Length; i++)
    {
        BitArray curArray = bitjes[i];
        for (int y = 0; y < curArray.Length; y++)
        {
            curArray[y] = !curArray[y];
            if (pixelChanged != null)
            {
                pixelChanged.Invoke(new PixelChangedEventArgs(i, y, false));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎只是调用"空"方法占用了该算法使用的cpu的大约20%.这不奇怪吗?我试图在调试和发布模式下编译解决方案,但没有发现任何明显的差异.

这意味着我在此循环中进行的每个方法调用都会使代码减慢大约0.4秒.由于迷宫生成器代码当前包含许多单独的方法调用,这些调用超出了不同的操作,因此开始获得实质性的安装.

我还检查了Stack Overflow上的谷歌和其他帖子,但还没有真正找到解决方案.

是否可以自动优化这样的代码?(也许像项目Roslyn这样的东西?)或者我应该把所有东西放在一个大方法中?

编辑:我也对这两种案例中JIT/CLR代码差异的分析感兴趣.(所以问题实际上来自哪里)

Edit2: 所有代码都是在发布模式下编译的

Gil*_*lad 5

这是一个问题,JIT 对方法进行了内联优化(其中整个方法代码实际上被注入到调用父代码中),但这只发生在编译为 32 字节或更少的方法中。我不知道为什么存在 32 字节限制,并且还希望在 C# 中看到一个“内联”关键字,就像在 C/C++ 中完全针对这些问题。


Mar*_*ell 5

我要尝试的第一件事就是让它静态而不是实例:

public static void GoDrawPixel(PixelChangedEventHandler pixelChanged,
    int x, int y, bool enabled)
{
    if (pixelChanged != null)
    {
        pixelChanged.Invoke(new PixelChangedEventArgs(x, y, enabled));
    }
}
Run Code Online (Sandbox Code Playgroud)

这改变了一些事情:

  • 堆栈语义保持可比性(它加载一个引用,2个int和一个bool)
  • callvirt成为call-避免了一些小的开销
  • ldarg0/ ldfld对(this.pixelChanged)成为单ldarg0

接下来我要看的是PixelChangedEventArgs; 如果它避免大量分配,那么作为结构传递它可能会更便宜; 或者只是:

pixelChanged(x, y, enabled);
Run Code Online (Sandbox Code Playgroud)

(原始参数而不是包装器对象 - 需要更改签名)