Visual Studio C# 2010 Express的调试模式比发布模式运行更快

7
我有一个 Windows Forms 应用程序,其中恰好有两个线程。这些线程彼此之间没有任何交互,第一个线程运行时不会干扰第二个线程。它们之间没有同步,因为没有必要这样做。第一个线程处理应用程序的 UI,改变颜色和标签,并有一个定时器每 200 毫秒启动一次以捕获一些用户输入。第二个线程更加复杂,通过其代码不断地运行,直到用户通过退出应用程序来关闭它。
第二个线程首先从内存中读取数据并将其存储到列表中,然后使用这些数据进行一些计算。我有一个 StopWatch 类计时器来测量完成第二个线程的一次迭代所需的时间。该计时器在线程的开头被重置并启动,然后在线程完成一次迭代后停止并打印到控制台。这就是我获取性能数据的地方。我允许线程运行至少 1000 次迭代,然后计算平均值,不包括第一次运行。
DEBUG 版本的构建(即由 VSHOST 运行或在 Visual Studio C# 2010 Express 中按 F5 运行的构建)。计时平均值为 0.00035 秒,即 0.35 毫秒。
当应用程序在 VSHOST 外运行时,即通过按 Ctrl-F5 或运行从 BUILD 按钮生成的 .exe 文件时。我还使用了 REBUILD 进行测试,但没有任何改变。计时平均值为 0.365 秒,即 365 毫秒。这意味着 Release Build 要慢大约 1000 倍。
我完全不知道发生了什么。为什么 VSHOST 可以让程序运行得如此迅速。我确保所有变量初始化都被考虑并正确处理。话虽如此,我不知道为什么会发生这种情况。有没有关于为什么出现这样性能下降的任何见解?
作为一个旁注,我使用的电脑是 64 位,具有四核 i7 和超线程技术,16 GB 的内存和双 HD6750。因此,似乎问题不在于有过多的线程,这里可能存在问题的唯一事情是超线程。下面是我的应用程序所做的一段代码片段,但是由于慢读取内存地址是导致缓慢的原因,因此不可能提供工作代码。
namespace Test Snippet
{
public struct Data
{
    public float X;
    public float Y;
    public float Z;
    public float dX;
    public float dY;

    public Data(int c)
    {
        this.X = ReadFloat(Base + 0x50 + (c * 0x10));
        this.Y = ReadFloat(Base + 0x50 + (c * 0x10));
        this.Z = ReadFloat(Base + 0x50 + (c * 0x10));
        if (this.Z == 1)
        {
            targetindex = c;
        }
        this.dX = 0;
        this.dY = 0;
    }
}
class Class1
{
    public int Base = new int();
    public List<Data> data = new List<Data>();
    public int targetindex = new int();
    public Data targetdata = new Data();

    public void GetData()
    {
        while (true)
        {
            data.Clear();
            for (int c = 0; c < 64; c++)
            {
                Data tempdata = new Data();
                teampdata = new Data(c);
                data.Add(tempdata);
            }
            if (data.Count != 0)
            {
                targetdata = data[targetindex];
                data.RemoveAt(targetindex);
                targetdata.dX = ReadFloat(Base + 0x66);
                targetdata.dY = ReadFloat(Base + 0x65);
                Data[] tempdatarray = new Data[data.Count];
                for (int j = 0; j < tempdatarray.Length; j++)
                {
                    tempdatarray[j].dX = (float)Math.Acos(targetdata.dX * 10);
                    tempdatarray[j].dY = (float)Math.Acos(targetdata.dY * 10);
                }
            }

        }
    }
}

编辑:我尝试了相同的步骤,但没有使用线程。我让计时器调用线程函数来捕获用户输入。我得到了相同的结果。这意味着线程似乎不是问题所在。我还在另一台计算机上进行了测试,出现巨大差异的原因不明。这让我相信我的计算机可能有问题,或者与我的处理器如何处理线程有关,因为它具有超线程功能。是否有人知道超线程是否会导致多线程应用程序出现问题,而该程序未在程序内显式地利用它。说实话,我不知道如何设置。


2
你尝试过使用真正的性能分析工具对应用程序进行分析吗? - Daniel Mann
我目前正在尝试获取一小段代码,以便说明问题。我会尽快编辑并提供一些源代码。 - Nomad101
你如何读取秒表的结果?- 你必须在线程2中有一些I/O或者写入一个被线程1读取的区域。 - Danny Varod
4
如果我要猜的话,我会猜是垃圾回收器的问题。在调试模式下,垃圾回收器的运行速度更慢,因此你可能无法回收那个数组,而是在下一个循环中重新使用它;而在发布模式下,垃圾回收器可能会在变量超出范围时立即释放它。这是我的最佳猜测。 - Darren Kopp
你在计时中只调用了一次函数吗?如果涉及缓存或JIT编译器,则运行100次迭代的计时并保留最快时间与仅调用一次的计时应该会有所不同。 - Mark Tolonen
显示剩余7条评论
3个回答

1

我没有看到任何内容表明您正在选择发布版本。这是工具栏上的一个选项。如果您直接运行调试版本,可能会寻找找不到的东西。

编辑:除了我错过的标题!!!:-)


这实际上是一个有趣的点...我不确定它是否会对问题的结论产生影响,但正如@MikeKulls所建议的那样,您确定正在运行程序集的发布版本和可能经过优化的版本吗?!? - Fernando Espinosa
另一个要点:假设上述不是问题,你的调试配置和发布配置是否具有相同的平台目标? - Fernando Espinosa
是的,我已经查看了.csproj文件,并确保发布和调试都使用相同的平台。至于我使用哪个配置,我会在每次运行时重新构建项目,先打开调试配置,然后再打开发布配置。我甚至尝试过使用发布功能。另外,我还尝试过在不使用主机进程的情况下运行调试版本,即在进程中取消选中使用主机进程。结果与发布版本相同。这让我相信问题与主机进程有关。 - Nomad101
哈哈哈,没事的,我知道你会明白的 ;) - Nomad101

1

首先,你应该进行一些性能分析。可以使用性能分析工具或者只需要使用计时器在某个地方打印一些消息来显示某些操作所需的时间 - 这样至少可以确定哪行代码运行缓慢,即使它不能告诉你为什么在调试器下运行得如此缓慢。没有这些信息,你就只能猜测。

现在,让我们继续猜测...

我认为问题与控制台的使用有关,基于以下观察结果:

  • 写入控制台窗口本身实际上相对较慢 - 当运行一个向控制台写入大量内容的应用程序时,你会发现这一点。如果你保持窗口打开,则运行需要很长时间,但是如果你最小化控制台窗口,则相同的操作可以运行得更快。
  • 据我了解,你正在每0.35毫秒写入1条消息到控制台。那是很多的消息。
  • 根据你运行应用程序的方式,Visual Studio实际上将控制台输出重定向到Visual Studo内部的“输出”窗口进行调试。
我的猜测是,在 Visual Studio 中的控制台窗口比非调试时使用的等效机制要快得多,而额外减速的原因实际上是您的日志记录代码。尝试删除基于控制台的日志记录并改为记录到文件中,以查看是否有任何差异,或者仅减少记录消息的次数,例如记录完成100次迭代所需的时间-这将减少控制台对性能产生的影响(如果有的话)。

0
问题与超线程无关。我找不到链接,但是英特尔在2004年有一篇很好的技术描述,介绍了它的工作原理(没有任何营销炒作)。简而言之,Core 0可能是一个真正的核心,而Core 1可能是一个逻辑核心,与Core 0共享相同的硬件。从我们的角度来看(应用程序开发人员),Core 0和Core 1都是真实的,我们不必关心Core 1是逻辑核心这个事实(除了显而易见的事实,即逻辑核心只能提供大约13-30%的性能提升,这也在技术说明中提到)。Windows在真实核心和逻辑核心之间调度线程方面做得非常好。您只需要创建两个线程,Windows会在Core 0和Core 1上分别运行一个线程。如果您想进行实验,可以在BIOS中禁用超线程,为您的线程编程设置处理器亲和力或从任务管理器设置亲和力。

话虽如此,尝试使用HyperThreading并不能帮助您解决问题。您应该像已经提到的那样对发布版本进行分析。还要查找事件日志中的奇怪错误。运行sysinternal的Process Explorer以查看是否在I/O中花费了太多时间。谁知道,也许发布版本在这台机器上会触发某些设备驱动程序的古怪行为。

编辑:这是英特尔的技术描述(来自维基百科),实际上是2002年的:http://download.intel.com/technology/itj/2002/volume06issue01/vol6iss1_hyper_threading_technology.pdf


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接