我的32位头痛现在是64位偏头痛?!? (或64位.NET CLR运行时问题)

在64位JIT和32位JIT下运行.NET应用程序时,在性能,内存等方面出现了什么exception的意外后果? 我对好事感兴趣,但对人们遇到的令人惊讶的糟糕问题更感兴趣。

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

然而, 这个问题及其答案让我思考 – 我还有什么其他问题可以忽略?

有很多问题和博客文章绕过这个问题,或者涉及到它的一个方面,但我还没有看到任何编制了一个很好的问题清单。

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

以下是我目前了解的一些问题:

  • ( 现在我知道了 )属性,甚至自动属性,都没有在x64中内联。
  • 由于引用的大小,应用程序的内存配置文件也会更改,但也因为内存分配器具有不同的性能特征
  • 启动时间可能会受到x64的影响

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

谢谢你们!

– – 编辑 – – –

只是为了澄清 –

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

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

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

我记得经常在IRC频道听到一个问题。 它在这个实例中优化了临时副本:

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

将竞争条件重新置入并导致潜在的空引用exception。

.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代码更长+更不易读(因为你需要手动内联和/或不能使用generics),而且速度要慢得多。

我已经转而使用C ++中的Eigen :它非常棒,可以实现可读代码和高性能; 然后,一个瘦的C ++ / CLI包装器提供了计算引擎和.NET世界之间的粘合剂。

Eigen通过模板元编程工作; 将vector-expressions编译成SSE内部指令,并为你做很多与缓存相关的循环展开和重新排列; 虽然专注于线性代数,但它也适用于整数和非矩阵数组表达式。

所以,例如,如果P是一个矩阵,那么这种东西Just Works:

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

…不分配P的临时转置变量,并且不计算整个矩阵乘积而只计算它所需的字段。

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

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

你提到了移植问题,那些是需要关注的问题。 我(显然)不知道你的应用程序,但试图猜测JIT通常是完全浪费时间。 编写JIT的人对x86 / x64芯片架构有着深刻的理解,并且在所有可能的情况下都知道什么比这个星球上任何其他人都表现更好,性能更差。

是的,你可能有一个不同且独特的角落案例,但如果你“正在编写新的应用程序”,那么我不会担心JIT编译器。 可能有一个愚蠢的循环,可以避免在某个地方为你提供100倍的性能提升,你可以通过尝试再次猜测JIT来获得。 让我想起编写ORM时遇到的问题,我们会查看代码,并认为我们可以调出一些机器指令……当然,代码然后关闭并通过网络连接到数据库服务器因此,我们在一个以毫秒为界限的过程中微调了几秒。

普遍的性能调整规则…如果你没有衡量你的表现,你不知道你的瓶颈在哪里,你只是觉得你知道……而且你可能错了。

关于Quibblesome的回答:

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

 using System; using System.Threading; namespace EventsMultithreadingTest { public class Program { private static Action _delegate = new Action(Program_Event); public static event Action Event; public static void Main(string[] args) { Thread thread = new Thread(delegate() { while (true) { Action 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); } } } 

我认为64 JIT没有完全开发/移植以利用这样的64位架构CPU,所以它有问题,你可能会得到组件的’模拟’行为,这可能会导致问题和意外行为。 我将研究可以避免这种情况的情况和/或可能看看是否有好的快速64 c ++编译器来编写时间关键的计算和算法。 但即使您在查找信息方面遇到困难或没有时间阅读已拆解的代码,我也非常确定在托管代码之外进行大量计算会减少您可能遇到的任何问题并提高性能[有点确定您已经这样做了但只是提到:)]

分析器不应显着影响您的计时结果。 如果探查器开销真的很 “重要”,那么你可能无法从代码中挤出更多的速度,并且应该考虑查看你的硬件瓶颈(磁盘,RAM或CPU?)和升级。 (听起来你是CPU绑定的,所以这就是从哪里开始)

通常,.net和JIT可以解除大多数64位移植问题。 如您所知,存在与寄存器大小相关的影响(内存使用情况更改,编组到本机代码,需要程序的所有部分都是本机64位版本)以及一些性能差异(更大的内存映射,更多寄存器,更宽的总线)等),所以我不能告诉你任何比你已经知道的更多的东西。 我见过的其他问题是OS而不是C# – 例如,64位和WOW64应用程序现在有不同的注册表配置单元,因此必须仔细编写一些注册表访问。

担心JIT将对您的代码执行什么操作以及尝试调整它以使其更好地工作通常是一个坏主意,因为JIT可能会随着.net 4或5或6而改变,并且您的“优化”可能会变成效率低下,或者更糟糕的是,错误。 另外请记住,JIT专门为其运行的CPU编译代码,因此开发PC的改进可能不会改善不同的PC。 在今天的CPU上使用今天的JIT,你可能会在升级某些东西的时候咬你几年。

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

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

你说“启动时间可能会更长”,但这似乎与你说在100%CPU上运行数小时的应用程序无关。

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

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

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

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。 – 唐纳德克努特