Java、C#还是传统的C,哪个更快?

28

目前,我正在决定在哪个平台上构建一款科学计算产品,我正在考虑使用C#、Java或者纯粹的C语言与Intel编译器配合使用Core2 Quad CPU。该产品主要涉及整数运算。

到目前为止,我的基准测试结果显示Java和C语言大致相当,而.NET/C#落后于它们约5%。然而,我的一些同事声称,只要给JIT足够的时间来完成其工作,使用适当的优化,.NET将会超过这两种语言。

我一直认为应用程序启动后几分钟内(在我的情况下可能是几秒钟),JIT就已经完成了它的任务,所以我不确定是否应该相信他们。

有人可以解释一下这种情况吗?.NET是否能够击败Java?(还是最好坚持使用C语言?)

代码高度多线程,并且数据集大小达到了数千亿字节。

在这种情况下,Haskell/Erlang等选项并不可行,因为有大量现有的遗留C代码将被移植到新系统中,而将C代码移植到Java/C#比移植到Haskell或Erlang要简单得多。(除非这些语言提供了显著的加速)。

编辑:我们正在考虑转向C#或Java,因为它们在理论上可能更快。我们能减少的每个百分比都可以使我们每年节省数万美元。目前,我们只是试图评估C、Java或c#哪一个会更快。


2
C++有什么问题吗?从C语言移植到C++可能比转向Java或C#容易得多。 - GManNickG
1
关于特定的“足够时间让JIT完成其工作”,当前的MS JIT不会重新编译方法。一些JVM会这样做。一旦您通过了初始的JIT开销(如果您的代码正在处理大量数据,则非常小),现代化的JVM可能会变得更好,但CLR不会。 - ShuggyCoUk
我可以重申我的回答中的一个观点(有很多),你需要提供关于你正在使用的jvm/.Net运行时/架构/c编译器和优化/Debugvs Release构建等具体信息。无论你是在进行已检查还是未检查的算术运算等等... - ShuggyCoUk
2
这不是社区维基吗???你们在睡觉吗?? - RubyDubee
"Java和C在某些方面相当" 你确定吗? - ascanio
显示剩余4条评论
26个回答

67
问题中的关键信息是:
每节省1% 的处理时间可以让我们每年省下数万美元。
因此,您需要考虑刨去每个百分比的成本。如果优化工作每年需要花费数万美元,则不值得进行。您可以通过解雇程序员来获得更大的节省。
使用合适的技能(这些技能今天越来越稀有,因此更昂贵),您可以手动编写汇编代码以获得尽可能快的代码。稍微不那么稀有(和昂贵)的技能,您可以使用一些非常丑陋的C代码实现几乎相同的效果。随着您从中挤出的性能越来越高,它将耗费您更多的开发工作量,并且在付出更大努力时,回报将递减。如果这里的利润仍然保持在“每年数万美元”的水平,那么到了某个点,这样做就不再值得。实际上,我敢打赌,您已经达到了这一点,因为“每年数万美元”在一个薪资范围内,可能无法购买到手动优化复杂程序所需的技能。
我猜测,如果您已经用C语言编写了代码,则将其全部重写为另一种语言的直接翻译的工作将浪费90% 的精力。很可能它会执行得更慢,因为您没有利用平台的功能,而是针对其进行了操作,例如尝试使用Java就像C语言一样。
此外,在您现有的代码中,将存在对运行时间做出重要贡献(它们经常运行)和其他完全不相关的部分(它们很少运行)。因此,如果您有加速程序的想法,则在不影响运行时间的程序部分上浪费时间是没有经济意义的。
因此,请使用分析器查找热点,并查看现有代码中浪费时间的位置。
当我注意到与“多线程”有关的参考时,更新如下:

在这种情况下,如果您将精力集中于消除瓶颈,使得您的程序能够在大量核心上良好扩展,那么它将自动以一种可以超越任何其他优化的速度每年变得更快。明年这个时候,四核处理器将成为台式机的标准配置。再过一年,八核处理器将会变得更便宜(我一年前以几千美元买了一个),到那时候,我预测32核计算机的价格将低于一名开发人员。


2
我同意用更少的话来表达观点。此外,如果可能的话,通过利用GPU并从英特尔自身获取帮助,您可以更好地节省大量资金,尤其是对于一个酷炫/大型项目。 - Mafti
2
目前不清楚32核心的计算机是否具备足够带宽来在所有核心上进行数据密集型计算。值得一提的是,可以考虑分布式存储扩展,如MapReduce或MPI。这可能会使他立即扩展到数千个核心。请参见以下内容。 - Todd Gamblin
5
+1 对于问题经济方面的关注。 - M. Anthony Aiello

31

非常抱歉,这并不是一个简单的问题。它会取决于具体情况。C# 绝对不慢,你很难说 "Java 更快" 或者 "C# 更快"。C 是一种非常不同的语言...... 如果你使用得当,它可能会更快 - 前提是你要写得非常精确;但在大多数情况下,速度将会差不多,但编写起来却更加困难。

同时也要考虑您采用的方式 - 锁策略、并行方法、主代码体等。

关于 JIT - 您可以使用 NGEN 进行优化,但是如果您正在访问相同的代码,则应该尽早对其进行 JIT 编译。

C# / Java 相比 C 有一个非常有用的特性,即它们有潜力更好地利用本地 CPU(优化等),而无需您担心。

此外 - 对于 .NET,请考虑诸如 "Parallel Extensions"(将在 4.0 中捆绑)之类的东西,它提供了更强大的线程支持(与没有 PFX 的 .NET 相比)。


正如已经指出的那样,可用核心数量随时间增加而增加。Parallel Extensions应该允许任何现有代码在开发人员毫不费力地添加这些核心时最大限度地利用它们。如果提到Parallel Extensions,将会得到+1分。 - Grant Wagner
C#/Java的一个非常有用的特性(相比于C)是它们具有潜力——请解释一下,C#/Java虚拟机/ JT如何可以做出比优化针对本地CPU的C编译器更好(即更快)的优化? - mctylr
1
@mctylr,它们不需要处理别名问题,在优化时可以访问运行时行为,它们有更多的自由来操纵内部(如逃逸分析),因为与C语言不同,内部是隐藏的。这仍然是潜力巨大的,但它正在快速发展。 - ShuggyCoUk

14

不用担心语言问题,重点并行化!

如果您有一个高度多线程、数据密集型的科学代码,那么我认为您不需要太担心语言问题。相反,您应该专注于使您的应用程序并行化,特别是使其能够超越单个节点进行扩展。这将带来比仅仅切换语言更高的性能。

只要您被限制在单个节点上,您的应用程序将会饱受计算能力和带宽的限制。在即将推出的众核机器上,不清楚您是否拥有足够的带宽来处理所有核心上的数据密集型计算。您可以进行计算密集型工作(就像GPU一样),但是如果您需要向每个核心流式传输大量数据,则可能无法满足所有核心的需求。

我认为您应该考虑两个选项:

  1. MapReduce
    你的问题听起来很适合使用类似 Hadoop 这样专门用于数据密集型作业的工具。

    Hadoop 在 Linux 上扩展到了 10,000 个节点,你可以将你的工作转移到别人的(例如 Amazon、Microsoft)或你自己的计算云中。它是用 Java 编写的,所以在移植方面,你可以从 Java 中调用你现有的 C 代码,或者你可以将整个项目移植到 Java 中。

  2. MPI
    如果你不想麻烦地移植到 MapReduce,或者由于某些原因,你的并行范式不适合 MapReduce 模型,那么你可以考虑将你的应用程序改为使用 MPI。这也将允许你扩展到(可能数千个)核心。MPI 是计算密集型、分布式内存应用程序的事实标准,我相信有 Java 绑定,但大多数人使用 C、C++ 和 Fortran 来使用 MPI。因此,你可以保留 C 代码,并专注于并行化性能密集部分。如果你感兴趣,可以先看看 OpenMPI


1
+1虽然不要低估分发复杂程序所需的时间,因为这可能会证明不值得花费这些精力和金钱! - Ed James

11

那些基准测试结果真让我感到意外。

对于计算密集型的应用程序,我会大胆地打赌 C 会更快。你可能会编写像漏斗一样泄露内存并且存在有趣的线程相关缺陷的代码,但它应该会更快。

我能想到 Java 或 C# 更快的唯一原因是测试运行时长较短。如果几乎没有进行 GC,你将避免实际释放内存所带来的开销。如果该进程是迭代或并行的,请在你认为完成了一些对象后尝试插入 GC.Collect(在将事物设置为 null 或删除引用之后)。

此外,如果你正在处理千兆字节的数据,我认为使用 C 可以获得确定性的内存分配,这将使你更好地处理内存问题。如果你在接近分配内存时进行释放,堆将基本上不会出现碎片。在 GC 环境中,由于碎片而导致程序在经过一定时间后使用的内存比你猜想的要多得多。

对我而言,这似乎是需要使用 C 的项目,但需要额外关注内存的分配和释放。我的赌注是如果在完整数据集上运行,C# 或 Java 将失败。


5
你的先入为主观念已经过时了。现代垃圾回收器非常擅长优化内存释放,尤其是在分配和释放的时间接近时。它们甚至可以比C风格的malloc/free表现更好。 - Michael Borgwardt
2
说不准,但我知道的是,如果我在C#中的循环中分配一个变量大小的缓冲区(大约8K),而不进行GC.Collect操作,那我会死得很快。即使每次迭代都释放缓冲区。LOH真让人头疼。 - Darren Clark
据我所知,8K不足以进入LOH。请发布一个简短但完整的程序来演示问题。 - Jon Skeet
1
嗯...你们两个都是对的,而我错了。我有一个旧程序出了问题,但它分配的内存超过了8K,而我记错了LOH对象的大小。它还运行在1.0上,我无法在3.5上重现这个问题。所以Michael也是对的。不过,这些基准测试结果仍然让我感到惊讶。 - Darren Clark
@user492238:有趣。我的也是CLR 4,但是64位的。 - Jon Skeet
显示剩余7条评论

10

相当长的一段时间以前,Raymond ChenRico Mariani 在他们的博客中系列地逐步优化了一个文件加载到字典工具中的过程。虽然.NET起初更快(即容易迅速完成),但C/Win32方法最终明显更快——但是需要考虑较大的复杂性(例如使用自定义分配器)。

最终哪个更快的答案将严重取决于你愿意在每种方法上花费多少时间来挤出每微秒的时间。那种努力(假设你正确地执行,并由真实的分析器数据指导)将比语言/平台选择产生更大的差异。


第一个和最后一个性能博客条目:

(最后一个链接提供了结果的总体摘要和一些分析。)


5
这将很大程度上取决于您具体在做什么。我有Java代码可以击败C代码。我有Java代码比C++代码慢得多(我不使用C# / .NET,因此无法评论那些语言)。
所以,这取决于您在做什么,我相信您可以找到一些在语言X中比语言Y更快的东西。
您是否尝试通过性能分析器运行C#代码,看看它花费了最长时间的地方(同样适用于Java和C)。也许您需要进行一些不同的操作。
Java HotSpot VM比.NET VM更成熟(至少可以追溯到1994年),因此可能取决于两者的代码生成能力。

此外,Java在不同的操作系统(Linux)上有更好的支持。 - trunkc
如果你的“不同操作系统”是Linux,那么不用担心——Mono在Linux上得到了很好的支持。但是,Java在更多“晦涩难懂”的设备上有更好的渗透率。对于服务器工作(通常是Windows或Linux/Unix),C#和Java在这方面没有太大区别。 - Marc Gravell

5
你说“代码是多线程的”,这意味着算法可以并行化。此外,你提到“数据集的大小达到几个TB”。
优化就是找出并消除瓶颈。
显然,瓶颈在于数据集的带宽。考虑到数据的大小,我猜测数据保存在服务器上而不是桌面机器上。你没有提供使用的算法的任何细节。算法所花费的时间是否大于读/写数据/结果所花费的时间?算法是否处理总数据的子集?
我假设算法处理数据块而不是整个数据集。
你需要考虑两种情况:
1. 算法处理数据的时间比获取数据的时间长。在这种情况下,你需要优化算法。
2. 算法处理数据的时间比获取数据的时间短。在这种情况下,你需要增加算法和数据之间的带宽。
在第一种情况下,你需要一位能编写良好汇编代码的开发人员,以充分利用处理器的优势,如SIMD、GPU和多核。无论做什么,都不要只增加线程数量,因为一旦线程数超过核心数,你的代码就会变慢!这是由于切换线程上下文所增加的开销造成的。另一个选择是使用类似SETI的分布式处理系统(你的组织中有多少台计算机用于管理目的-想想所有这些闲置的处理能力!)。正如bh213所提到的,C#/Java可能比使用SIMD等编写良好的C / C ++慢一个数量级。但这是一种现今少见的技能。
在后一种情况下,当你受带宽限制时,你需要改善连接数据与处理器的网络。在这里,请确保你使用最新的以太网设备-1Gbps到处都有(PC卡、交换机、路由器等)。不要使用无线网络,因为它比有线网络慢。如果有大量其他流量,请考虑并行使用专用网络和“办公”网络。请考虑将数据存储在靠近客户端的位置-对于每五个或更多的客户端,请使用专用服务器直接连接到每个客户端,从服务器镜像数据。
如果能够节省几个百分点的处理时间就可以节省“数万美元”,那么请认真考虑聘请顾问,实际上需要两个 - 一个软件,一个网络。他们应该可以轻松地通过节省的费用来回报自己。我相信这里有很多人都有资格提供帮助。
但是,如果降低成本是最终目标,请考虑谷歌的方法 - 编写代码使CPU保持在100%以下。这直接节省了能源,并通过减少冷却间接节省了成本。您会想要更多的性价比,因此又回到了C / C ++ - Java / C#的开销更大,开销=更多的CPU工作=更多的能量/热量=更多的成本。
因此,总之,在节省金钱方面,与您选择的语言有关的事情远不止这些。

将带宽问题引入数据集,而不是专注于原始计算速度,这是一个很好的想法。 - Grant Wagner

4
如果系统中已经有大量的遗留C代码,为什么要转向C#和Java呢?如果您想利用处理速度的任何改进,则最好使用C,因为它比C#和Java更接近硬件,它们有运行时环境的开销。离硬件越近,运行速度就应该越快。高级语言,如C#和Java,将导致更快的开发时间...但是C...或者更好的汇编语言将导致更快的处理时间...但需要更长的开发时间。

4

我参加了几个TopCoder马拉松比赛,其中表现是获胜的关键。

我的选择是C#。我认为C#的解决方案略优于Java,并且比C++稍慢...直到有人用C++编写了一个快了一个数量级的代码。你可以使用Intel编译器,而获胜的代码充满了SIMD指令,这在C#或Java中无法复制。但如果SIMD不是一个选项,只要注意正确使用内存(例如,注意缓存未命中并尝试将内存访问限制为L2缓存的大小),C#和Java应该足够好。


关于SIMD:.NET在Mono实现中通过Mono.Simd命名空间提供了SIMD支持。 - Konrad Rudolph
微软表示他们非常希望也能暴露SIMD功能。而Mono则表示他们愿意根据微软提出的任何API进行更改(尽管微软应该勉强承认他们错过了这个机会,这是一个体面的做法...)。 - Jörg W Mittag
...并且只需采用Mono API)。不管怎样,这些都不会在.NET 5.0甚至更晚之前发生。 - Jörg W Mittag

4
您的问题表述不够清晰(至少标题是这样),因为它暗示了这种差异是普遍存在的,并且适用于所有java/c#/c代码实例。
值得庆幸的是,问题的正文表述更好,因为它提供了一个合理详细的说明,说明了您的代码正在做的事情。它没有说明您使用的c#/java运行时版本(或提供者),也没有说明目标架构或代码将在哪台机器上运行。这些因素会产生很大的差异。
您已经进行了一些基准测试,这很好。以下是一些关于您看到结果的原因的建议:
  • 你在编写高效的C#代码方面可能不如Java/C熟练(这不是批评,甚至不太可能,但这是你需要考虑的一个真实可能性)
  • JVM的后续版本对于无争用锁有一些严重的优化,这可能会使你更有优势(特别是与你正在使用的c实现线程原语进行比较)
  • 由于Java代码似乎与C代码相比运行良好,因此你很可能并不太依赖堆分配策略(分析会告诉你这一点)。
  • 由于C#代码的运行效率不如Java代码(假设代码是可比较的),那么存在几个可能的原因:
    • 你正在使用(不必要的)虚拟函数,JVM将内联但CLR不会
    • 最新的JVM执行逃逸分析,这可能会使某些代码路径更加高效(尤其是涉及生命周期为栈绑定的字符串操作的路径)
    • 只有最新的32位CLR会内联涉及非原始结构的方法
    • 一些JVM JIT编译器使用热点式机制,试图检测代码的“热点”并花费更多的精力重新编译它们。

如果不了解代码大部分时间所做的工作,就不可能提出具体的建议。我可以很容易地编写代码,在CLR下通过使用结构体而不是对象或针对CLR的运行时特定功能(如非装箱泛型)实现更好的性能,但这并不是一般性的陈述。


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