9

从在 64 位 JIT 和 32 位 JIT 下运行 .NET 应用程序切换时,在性能、内存等方面发生了哪些不寻常的、意想不到的后果?我对好的方面感兴趣,但对人们遇到的令人惊讶的坏问题更感兴趣。

我正在编写一个新的 .NET 应用程序,它将同时部署在 32 位和 64 位上。与移植应用程序有关的问题有很多问题 -从编程/移植的角度来看,我并不关心“陷阱”。(即:正确处理本机/COM 互操作、嵌入在结构中的引用类型改变结构的大小等)

然而,这个问题及其答案让我思考——我忽略了哪些其他问题?

有很多问题和博客文章绕开了这个问题,或者触及了它的一个方面,但我还没有看到任何东西可以汇编出一份体面的问题清单。

特别是 - 我的应用程序非常受 CPU 限制,并且具有巨大的内存使用模式(因此首先需要 64 位),并且本质上是图形化的。我担心在 64 位 Windows(使用 .NET 3.5sp1)上运行的 CLR 或 JIT 中可能存在哪些其他隐藏问题。

以下是我目前知道的几个问题:

我想知道人们在 64 位 Windows 上的 JIT 中发现了哪些其他具体问题,以及是否有任何性能变通方法。

谢谢你们!

- - 编辑 - - -

只是为了澄清 -

我知道尝试及早优化通常是不好的。我知道第二次猜测系统通常是不好的。我也知道 64 位的可移植性有其自身的问题——我们每天在 64 位系统上运行和测试以帮助解决这个问题。等等

但是,我的应用程序不是您的典型业务应用程序。这是一个科学的软件应用程序。我们有许多进程在所有内核(它是高度线程化的)上一次使用 100% 的 CPU 数小时。

我花了很多时间来分析应用程序,这会产生很大的不同。但是,大多数分析器禁用了 JIT 的许多功能,因此当您在分析器下运行时,可能很难确定内存分配、JIT 内联等小细节。因此我需要这个问题。

4

8 回答 8

4

.NET 中一个特别麻烦的性能问题与糟糕的 JIT 相关:

https://connect.microsoft.com/VisualStudio/feedback/details/93858/struct-methods-should-be-inlined?wa=wsignin1.0

基本上,内联和结构在 x64 上不能很好地协同工作(尽管该页面表明内联现在可以工作,但随后的冗余副本并没有消除,考虑到微小的性能差异,这听起来很可疑)。

无论如何,在与 .NET 搏斗足够长的时间之后,我的解决方案是使用 C++ 来处理任何数字密集型的事情。即使在 .NET 的“好”情况下,您不处理结构并使用优化边界检查的数组,C++ 也胜过.NET

如果您正在做比点积更复杂的事情,那么情况会很快变得更糟;.NET 代码更长 + 可读性更低(因为您需要手动内联内容和/或不能使用泛型),而且速度要慢得多。

我已经转而在 C++ 中使用Eigen:它绝对很棒,代码可读性强,性能高;然后,一个精简的 C++/CLI 包装器提供了计算引擎和 .NET 世界之间的粘合剂。

Eigen 通过模板元编程工作;in 将向量表达式编译为 SSE 内在指令,并为您执行许多最讨厌的与缓存相关的循环展开和重新排列;虽然专注于线性代数,但它也适用于整数和非矩阵数组表达式。

因此,例如,如果P是一个矩阵,这种东西就可以工作:

1.0 /  (P.transpose() * P).diagonal().sum();

...它不分配 P 的临时转置变体,也不计算整个矩阵乘积,而只计算它需要的字段。

因此,如果您可以在完全信任下运行 - 只需通过 C++/CLI 使用 C++,它就会工作得更好。

于 2010-02-25T14:34:57.267 回答
3

我记得我经常从一个 IRC 频道听到一个问题。在这种情况下,它优化了临时副本:

EventHandler temp = SomeEvent;
if(temp != null)
{
    temp(this, EventArgs.Empty);
}

放回竞争条件并导致潜在的空引用异常。

于 2009-03-11T15:49:08.603 回答
1

大多数情况下,Visual Studio 和编译器在向您隐藏问题方面做得很好。但是,我知道如果您将应用程序设置为自动检测平台(x86 与 x64)并且对 32 位第 3 方 dll 有任何依赖关系,可能会出现一个主要问题。在这种情况下,在 64 位平台上,它将尝试使用 64 位约定和结构调用 dll,但它不起作用。

于 2009-03-11T15:32:49.080 回答
1

您提到了移植问题,这些是需要关注的问题。我(显然)不知道你的应用程序,但试图猜测 JIT 通常完全是浪费时间。编写 JIT 的人对 x86/x64 芯片架构有着深入的了解,并且很可能比地球上任何其他人都知道哪些性能更​​好,哪些性能更​​差。

是的,您可能有一个不同且独特的极端案例,但如果您“正在编写应用程序”,那么我不会担心 JIT 编译器。很可能有一个愚蠢的循环可以在某个地方避免,这将使您从尝试对 JIT 进行二次猜测中获得 100 倍的性能改进。让我想起了我们在编写 ORM 时遇到的问题,我们会查看代码并认为我们可以从中提取一些机器指令......当然,代码然后通过网络连接到数据库服务器,所以我们在其他地方以毫秒为界的过程中修剪了微秒。

性能调整的通用规则...如果您没有测量您的性能,您不知道您的瓶颈在哪里,您只是认为您知道...您可能错了。

于 2009-03-11T15:38:34.900 回答
1

关于 Quibblesome 的回答:

我尝试在没有调试器的情况下在我的 Windows 7 x64 中以发布模式运行以下代码,并且从未抛出NullReferenceException 。

using System;
using System.Threading;

namespace EventsMultithreadingTest
{
    public class Program
    {
        private static Action<object> _delegate = new Action<object>(Program_Event);
        public static event Action<object> Event;

        public static void Main(string[] args)
        {
            Thread thread = new Thread(delegate()
                {
                    while (true)
                    {
                        Action<object> ev = Event;

                        if (ev != null)
                        {
                            ev.Invoke(null);
                        }
                    }
                });
            thread.Start();

            while (true)
            {
                Event += _delegate;
                Event -= _delegate;
            }
        }

        static void Program_Event(object obj)
        {
            object.Equals(null, null);
        }
    }
}
于 2009-12-11T21:48:47.703 回答
0

我相信 64 JIT 尚未完全开发/移植以利用此类 64 位架构 CPU,因此它存在问题,您可能会获得程序集的“模拟”行为,这可能会导致问题和意外行为。我会研究可以避免这种情况的情况和/或看看是否有好的快速 64 c++ 编译器来编写时间关键的计算和算法。但是,即使您很难找到信息或没有时间阅读反汇编代码,我很确定在托管代码之外进行大量计算会减少您可能遇到的任何问题并提高性能[有点确定您已经在这样做但只是提一下:)]

于 2010-02-06T07:57:48.833 回答
0

探查器不应显着影响您的计时结果。如果探查器开销确实“显着”,那么您可能无法从代码中挤出更多的速度,并且应该考虑查看您的硬件瓶颈(磁盘、RAM 还是 CPU?)并进行升级。(听起来你受 CPU 限制,所以这就是开始的地方)

一般来说,.net 和 JIT 可以让您摆脱 64 位的大部分移植问题。如您所知,存在与寄存器大小相关的影响(内存使用变化、编组为本机代码、需要程序的所有部分都是本机 64 位构建)和一些性能差异(更大的内存映射、更多寄存器、更宽的总线等等),所以我不能告诉你任何比你在这方面已经知道的更多的事情。我看到的其他问题是操作系统而不是 C# 问题——例如,现在有不同的注册表配置单元用于 64 位和 WOW64 应用程序,因此必须仔细编写一些注册表访问。

担心 JIT 将如何处理您的代码并尝试对其进行调整以使其更好地工作通常是一个坏主意,因为 JIT 可能会随着 .net 4、5 或 6 的变化而发生变化,并且您的“优化”可能会变得效率低下,或更糟糕的是,错误。还要记住,JIT 专门为运行它的 CPU 编译代码,因此对您的开发 PC 的改进可能不会是对不同 PC 的改进。在今天的 CPU 上使用今天的 JIT 所得到的结果可能会在几年后升级某些东西时给您带来麻烦。

具体来说,您引用“x64 上未内联属性”。当您运行整个代码库将所有属性转换为字段时,很可能会有一个新的 64 位 JIT 执行内联属性。实际上,它可能比您的“解决方法”代码表现得更好。让 Microsoft 为您优化。

你正确地指出你的记忆档案可以改变。因此,您可能需要更多 RAM、更快的虚拟内存磁盘和更大的 CPU 缓存。所有硬件问题。您可以通过使用(例如) Int32 而不是 int 来减少影响,但这可能不会产生太大影响并且可能会损害性能(因为您的 CPU 可能比半尺寸 32 位值更有效地处理本机 64 位值)。

您说“启动时间可以更长”,但这在您说以 100% CPU运行数小时的应用程序中似乎无关紧要。

那么你真正担心的是什么?也许你的代码在 32 位 PC 上计时,然后在 64 位 PC 上执行相同的任务。跑 4 小时有半小时的差异吗?还是相差只有3秒?还是 64 位 PC 实际上更快?也许您正在寻找不存在的问题的解决方案。

所以回到通常的、更通用的建议。识别瓶颈的概况和时间。查看您正在应用的算法和数学过程,并尝试用更有效的算法和数学过程来改进/替换它们。检查您的多线程方法是否有助于而不是损害您的性能(即避免等待和锁定)。尝试减少内存分配/释放 - 例如重用对象而不是用新对象替换它们。尽量减少频繁的函数调用和虚函数的使用。切换到 C++ 并摆脱 .net 强加的垃圾收集、边界检查等固有开销。嗯。这些都与 64 位无关,不是吗?

于 2010-02-06T08:34:22.757 回答
-1

我对 64 位问题不太熟悉,但我确实有一条评论:

我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。——唐纳德·克努斯

于 2009-03-11T15:26:44.803 回答