【问题标题】:My 32 bit headache is now a 64bit migraine?!? (or 64bit .NET CLR Runtime issues)我的 32 位头痛现在是 64 位偏头痛?!? (或 64 位 .NET CLR 运行时问题)
【发布时间】:2010-10-12 17:03:25
【问题描述】:

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

我正在编写一个新的 .NET 应用程序,它将同时部署在 32 位和 64 位中。关于移植应用程序的问题有很多问题——我不关心"gotchas" from a programming/porting standpoint。 (即:正确处理本机/COM 互操作、嵌入在结构中的引用类型改变结构的大小等)

但是,this question and it's answer 让我思考 - 我还忽略了哪些其他问题?

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

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

以下是我目前知道的一些问题:

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

谢谢大家!

----编辑-----

只是为了澄清-

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

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

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

【问题讨论】:

  • 如果标题提到 .NET 32 位和 64 位运行时,这个帖子会更有用(通过 Google 或 Stacko-search 等很容易找到)。

标签: c# .net vb.net clr jit


【解决方案1】:

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

    分析器不应显着影响您的计时结果。如果探查器开销真的“显着”,那么您可能无法从代码中挤出更多的速度,并且应该考虑查看您的硬件瓶颈(磁盘、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 位无关,是吗?

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        关于 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);
                }
            }
        }
        

        【讨论】:

        【解决方案5】:

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

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

        重新加入竞争条件并导致潜在的空引用异常。

        【讨论】:

        • 有趣....是只发生在 64 位 JIT 上的优化,还是也发生在 32 位 JIT 上?
        • 在 32 位中不会发生。这不是我的谈话,所以我无法验证这一点,但谈话持续了一个小时左右,所以除非周围有其他 64 位抖动,否则它很可能就是你正在处理的那个跨度>
        • IIRC 在这种情况下,32 位抖动实际上不符合规范,无论如何都应该以这种方式进行优化。但这是一种在通过不同线程触发事件和解钩时用于防止竞争条件的技巧
        • 这个问题只存在于.NET 1.x on x64;自从引入 .NET 2.0 内存模型以来,这一直不是问题;见code.logos.com/blog/2008/11/events_and_threads_part_4.htmlmsdn.microsoft.com/magazine/cc163715.aspx
        【解决方案6】:

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

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

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

        【讨论】:

        • 瓦尔登:我同意。但是,我的应用程序非常受 CPU 限制。它是高度数学化的,并且有许多多小时的运行时过程。我花了很多时间分析和优化细节,这非常有帮助。不过,分析器很困难,因为它们会禁用 JIT 问题。
        【解决方案7】:

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

        【讨论】:

        • 是的——不过,我不太关心这些类型的问题。我更关心隐藏的问题的性能/内存/运行时问题。
        • +1 - 我的一个第三方库遇到了这个问题。我必须在我的安装程序中同时包含 32 位和 64 位版本并安装适当的版本。
        【解决方案8】:

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

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

        【讨论】:

        • 正如我所说,我的应用程序受 CPU 限制很大。我有 5 小时运行时间的进程。您评论的另一面是 3% 的时间,这不是万恶之源。以 Rico Mariani 对此的评论为例——如果它只在 3% 的时间里重要,那意味着 33 行代码中的一行对于优化很重要。
        • 出于好奇,如果您在 VS 中针对 64 位平台而不是默认的 Any CPU,这些问题是否仍然存在?
        • 是的。都是核心平台的问题。 64 位 CLR 的 JIT 是与 32 位 JIT 完全不同的代码库,因此它的执行方式非常不同。
        猜你喜欢
        • 2012-10-26
        • 2014-05-11
        • 2010-09-16
        • 1970-01-01
        • 1970-01-01
        • 2015-01-18
        • 1970-01-01
        • 2018-03-07
        • 1970-01-01
        相关资源
        最近更新 更多