托管代码可以像未托管代码一样快速地执行计算吗

本文关键字:托管代码 执行 计算 一样 | 更新日期: 2023-09-27 17:58:07

我最近对不同的国际象棋引擎很感兴趣。在这个领域有许多开源和闭源项目。它们都是用C/C++编写的。这是一件显而易见的事情——你有一个计算密集型的任务,你使用C/C++,这样你就可以获得可移植性和速度。这似乎不明智。

然而,我想对这一想法提出质疑。当.NET刚出现的时候,有很多人说.NET的想法行不通,因为.NET程序注定会超级慢。事实上,这并没有发生。有人在VM、JIT等方面做得很好,我们现在在大多数任务中都有不错的性能。但不是全部。微软从未承诺.NET将适用于所有任务,并承认某些任务仍然需要C/C++。

回到计算繁重的任务的问题——有没有一种方法可以编写.NET程序,使其执行的计算不会比使用相同算法的非托管代码差得多?我会对"持续"的速度损失感到高兴,但任何比这更糟糕的事情都会成为问题。

你觉得怎么样?对于托管代码中的计算,我们能否在速度上接近非托管代码,或者非托管代码是唯一可行的答案?如果可以,怎么办?如果我们不能,为什么?

更新:这里有很多好的反馈。我将接受投票最多的答案。

托管代码可以像未托管代码一样快速地执行计算吗

"能"吗?是的,当然。即使没有不安全/不可验证的代码,经过良好优化的.NET代码也可以胜过本机代码。举个例子,Jon Harrop博士在这个话题上的回答:科学计算中的F#性能

会吗?通常情况下,不会,除非您采取方式来避免分配。

.NET不是超级慢的,但它也不属于母语。对于一款更喜欢安全和更短开发周期的商业应用程序来说,速度差是很容易接受的。如果你没有在CPU上使用每个周期,那么你使用多少并不重要,事实上,许多甚至大多数应用程序根本不需要这种性能。然而,当确实需要这种性能时,.NET不会提供。

更重要的是,它还不够可控。在C++中,您可以销毁每一个资源,管理每一个分配。当你真的不想这样做的时候,这是一个很大的负担——但当你需要微调每个分配的额外性能时,这是不可能击败的。

另一件需要考虑的事情是编译器。我的意思是,JIT可以访问有关程序和目标CPU的更多信息。然而,它确实每次都必须从头开始重新编译,而且这样做的时间限制比C++编译器大得多,这从本质上限制了它的能力。CLR语义,就像每次为每个对象分配堆一样,也从根本上限制了它的性能。托管GC分配非常快,但它没有堆栈分配,更重要的是,它是去分配。

编辑:当然,.NET提供了与(大多数)本机语言不同的内存控制范式,这意味着对于垃圾收集特别适合的应用程序来说,.NET代码可能比本机代码运行得更快。然而,这与托管代码与本机代码无关,只是为正确的工作选择了正确的算法,这并不意味着从本机代码中使用等效的GC算法不会更快。

简短回答是,长回答有足够的工作量。

有一些高频率的交易应用程序是用托管C#.NET编写的。很少有其他应用程序能达到交易引擎所要求的时间关键性。总体概念是,您开发的软件非常高效,您的应用程序将不需要垃圾收集器为非0代对象调用自己。如果垃圾收集器在任何时候启动,您都会有一个持续数十或数百毫秒的巨大延迟(从计算时间的角度来看),这是不可接受的。

您可以使用不安全的和指针来获得"原始"内存访问,这可以显著提高速度,但代价是对您的东西承担更多责任(记住固定您的对象)。在这一点上,您只是在移动字节。

垃圾收集压力可能是一件有趣的事情,但也有一些策略(对象池)。

这似乎是一个过于宽泛的问题。

这里有一些免费的提示:是的,您可以使用unsafeunchecked、结构数组,最重要的是使用C++/CLI。

C++的内联、compiletime模板扩展(以及类似的优化)等永远不会匹配。

但底线是:这取决于问题。computations。Mono有漂亮的扩展来使用SIMD指令,在Win32上,您必须使用本机才能获得这些指令。Interop在作弊。

不过,根据我的经验,移植玩具项目(如解析器和Chess引擎)将导致至少一个数量级的速度差异,无论你如何优化。NET方面的东西。我认为这主要与堆管理和服务例程(System.String、System.IO)有关

可能会有很大的陷阱。NET(过度使用Linq、lambdas,意外地依赖Enum.HasFlag来执行像位操作一样的操作…)等YMMV,并仔细选择您的武器

通常,托管代码与编译代码相比至少会有一些速度损失,这与代码的大小成比例。当VM首次JIT编译您的代码时,就会出现这种损失。假设JIT编译器和普通编译器一样好,那么在那之后,代码也会执行同样的操作。

然而,根据它的编写方式,JIT编译器甚至可能比普通编译器执行得更好。JIT编译器比普通编译器更了解目标平台——它知道什么代码是"热门"的,它可以缓存已验证的纯函数的结果,它知道目标平台支持哪些指令集扩展,等等,而取决于编译器,以及你(和你的预期应用程序)允许它的专业化程度,编译器可能不能几乎同样地进行优化。

老实说,这完全取决于您的应用程序。特别是对于像国际象棋引擎这样的算法,JIT编译语言中的代码,遵循预期的语义和规则模式,可能比C/C++中的等效代码运行得更快。

写一些东西并测试一下!