在64位下执行缓慢。可能的RyuJIT错误?

时间:2015-11-18 07:49:24

标签: c# performance visual-studio-2015 clr ryujit

我有以下C#代码尝试在发布模式下进行基准测试:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication54
{
class Program
{
    static void Main(string[] args)
    {
        int counter = 0;
        var sw = new Stopwatch();
        unchecked
        {
            int sum = 0;
            while (true)
            {
                try
                {
                    if (counter > 20)
                        throw new Exception("exception");
                }
                catch
                {
                }

                sw.Restart();
                for (int i = 0; i < int.MaxValue; i++)
                {
                    sum += i;
                }
                counter++;
                Console.WriteLine(sw.Elapsed);
            }

        }
    }
}
}

我在64位计算机上安装了VS 2015。当我在32位下运行代码时,它会在 0.6秒周围运行每次迭代,并打印到控制台。当我在64位下运行它时,每次迭代的持续时间只会跳到 4秒!我在我的同事计算机上尝试了示例代码,该计算机只安装了VS 2013。 32位和64位版本都运行 0.6秒

除此之外,如果我们只删除try catch块,它也会以 0.6秒运行,其中VS 2015为64位。

当有一个try catch块时,这看起来像是一个严重的RyuJIT回归。我是对的吗?

2 个答案:

答案 0 :(得分:11)

基准测试是一门艺术。对您的代码进行一些小修改:

   Console.WriteLine("{0}", sw.Elapsed, sum);

现在你会发现差异消失了。换句话说,x86版本现在和x64代码一样慢。你可以弄清楚RyuJIT没有做什么传统的抖动从这个微小的变化做了什么,它没有消除不必要的

   sum += i;

使用Debug&gt;查看生成的机器代码时可以看到的内容Windows&gt;拆卸。这在RyuJIT确实是一个怪癖。其死代码消除不如传统抖动那么彻底。否则不完全没有理由,微软重写了x64抖动,因为它无法轻易解决的错误。其中一个是优化器的一个相当讨厌的问题,它在优化方法上花费的时间没有上限。导致在具有非常大的物体的方法上行为相当差,它可能会在树林中出现几十毫秒并导致明显的执行暂停。

把它称为bug,呃,不是真的。写出合理的代码和抖动不会让你失望。优化确实永远从通常的位置开始,在程序员的耳朵之间。

答案 1 :(得分:0)

经过一些测试后,我得到了一些有趣的结果。我的测试围绕try catch块进行。正如OP指出的那样,如果你删除这个块,执行的时间是一样的。我已经进一步缩小了这一点并得出结论,这是因为counter块中if语句中的try变量。

让我们删除多余的throw

                try
                {
                    if (counter== 0) { }
                }
                catch
                {
                }

您将使用此代码获得与原始代码相同的结果。

让更改计数器为实际的int值:

                try
                {
                    if (1 == 0) { }
                }
                catch
                {
                }

使用此代码,64位版本的执行时间从4秒减少到大约1.7秒。仍然是32位版本的两倍。但我觉得这很有意思。不幸的是,在我快速的谷歌搜索后,我没有提出一个理由,但如果我发现为什么会发生这种情况,我会多挖一点并更新这个答案。

至于我们想要削减64位版本的剩余第二个版本,我可以看到,这只是在sum循环中将i增加for。 让我们改变这一点,以便sum不超过其界限:

            for (int i = 0; i < int.MaxValue; i++)
            {
                sum ++;
            }

此更改(以及try块中的更改)将使64位应用程序的执行时间减少到0.7秒。我对1秒钟时间差异的推理是由于64位版本需要处理int的自然方式,这自然是32位。

在32位版本中,有32位分配给Int32(sum)。当sum超出其界限时,很容易确定这一事实。

在64位版本中,有64位分配给Int32(sum)。当总和超过其界限时,需要有一种机制来检测这一点,这可能导致减速。甚至可能添加sum&amp;由于分配的冗余位增加,i需要更长的时间。

我在这里理论;所以不要把它当作福音。我以为我会发布我的发现。我相信其他人能够对我找到的问题有所了解。

-

更新

@HansPassant的回答指出sum += i;行可能被删除,因为它被认为是不必要的,这是完全合理的,sum没有在for循环之外使用。在他介绍for循环之外的sum值之后,我们注意到x86版本和x64版本一样慢。所以我决定做一些测试。让我们改变for循环并打印到以下内容:

                int x = 0;
                for (int i = 0; i < int.MaxValue; i++)
                {
                    sum += i;
                    x = sum;
                }
                counter++;
                Console.WriteLine(sw.Elapsed + "  " +  x);

您可以看到我在int x循环中引入了sum的新值for。 x的值不会写入控制台。 sum不会离开for循环。不管你信不信,这实际上将x64的执行时间缩短为0.7秒。但是,x86版本会跳跃到1.4秒。