Mono项目:为什么Mono比.NET快?

39
我很惊讶发现mono比.NET更快。有人知道为什么吗?我本来以为mono比.NET慢,但至少在我的实验中并非如此。我有一台带有.NET框架的Windows XP笔记本电脑。我在vmware vmplayer上运行CentOS,并想尝试一下mono。于是我下载了Mono 2.6.1源代码,并在vmplayer的CentOS上安装了它。我编写了一个使用.Net 2.0的测试WebService应用程序,在Windows上运行成功后,将二进制文件传输到了位于vmplayer上的CentOS上,而没有重新编译,并在CentOS上执行它。太好了,它成功了!生活真美好,但是另外一件事吸引了我的注意力。在CentOS上执行应用程序似乎更快。我简直不敢相信自己的眼睛。
为了确认我的观察结果,我将网络排除在外,因为网络响应取决于许多外部因素。我从互联网上获取了一个小型虚拟循环代码,在Visual Studio中编译,并在Windows和CentOS上执行,结果如下:
Output on windows console is HelloConsole\bin\Debug>HelloConsole.exe
Result =2.66666664666712E+24
37443.6077769661 ms


Output on Centos console is [rupert@bagvapp Math.Pow]$ mono HelloConsole.exe
Result =2.66666664666712E+24
28790.6286 ms

如果有人能够解释这种行为,那就太好了。我这个外行的理解是Mono的实现比.NET Framework更高效。即使我假设Mono的Math库实现只是高效的一部分。但很多实现,如处理财务数据、图形计算都很依赖Math库。在直接使用Mono/Centos而不是vmware上进行测试会更有趣,但这需要一些时间。也许下个周末我会尝试一下。

public static void DummyLoop()
    {
        double sumOfPowers = 0;
        int count = Convert.ToInt32(ConfigurationManager.AppSettings["count"]);

            for (int i = 0; i < count; i++)
            {
                sumOfPowers += Math.Pow(i, 2);   
            }


        Console.WriteLine("Result =" + sumOfPowers);   
    }


    static void Main(string[] args)
    {
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        DummyLoop();
        stopWatch.Stop();
        double ms = (stopWatch.ElapsedTicks * 1000.0) / Stopwatch.Frequency;
        Console.WriteLine(string.Concat(ms.ToString(), " ms"));
        Console.ReadLine();
    }

3
多么不寻常。 - Hans Passant
嗨Ben M,Convert.ToInt32和ConfigurationManager.AppSettings不在循环中。所以这只是一个增量。有比循环更好的基准测试方法吗? - funwithcoding
2
虽然这个特定案例很有趣,但你也应该对更复杂的东西进行基准测试。我们大多数程序在mono上运行时会慢2-3倍,不包括IO。 - nos
@Eclipsed4utoo 从控制台外部操作IDE。 - funwithcoding
2
你并没有通过这个测试真正地对 Mono 进行基准测试 - 实际上,你正在测试一种特定数学函数 Math.pow() 的实现,这很可能不能反映出框架和 VM 性能的整体表现。如果你简化循环内部的计算,比如像 sumOfPowers += 1 这样的无足轻重的操作,那么你就会测试 for 循环以及一个微不足道的变量赋值,这可以告诉你一些东西,但仍然不能作为 Mono 的基准测试,因为你也(非常)在测试 C# 编译器实现的性能。许多因素... - mindplay.dk
显示剩余7条评论
4个回答

59

不久之前在Mono邮件列表上曾经讨论过这个问题。他们提供的理由是,在Linux下,Mono经过了极致优化(具体细节未提及)。但是如果你在Linux上运行Mono,那么不仅仅是Mono这一个变量。因为它是一个不同的操作系统,不同的进程调度程序以及各种内核级子系统可能对性能产生显着影响,这与Mono团队的工作是无关的。Mono还大量使用编译器内部函数,而.NET则可能不会使用。

然而,在Windows上比较Mono on Windows和.NET on Windows时,大多数情况下你可能会发现.NET比Mono表现更好。但是即使如此,Mono也不一定总是输。实际上,对于专门针对Mono进行优化的代码(例如使用Mono.SIMD),无论在哪个平台上,你都可以得到比.NET高出一个数量级的性能表现。


1
@joemoe 谢谢澄清。我会尝试其他比较,比如I/O、数据库。似乎Mono针对Linux进行了优化,而.NET则针对Windows进行了优化。由于Linux在许多方面都胜过Windows,因此,在Linux上运行的Mono打败了在Windows上运行的.NET是有道理的。 - funwithcoding
2
“为了澄清事情,Mono从代码源生成的IL代码在所有操作系统中都是相同的,但是从IL代码生成的本地代码(JITed)因操作系统而异。生成的本地代码更或少更有效取决于操作系统。” - funwithcoding
5
需要注意的另一件事是,在Mono上需要大量内存分配和垃圾回收的任何操作都会变慢,但他们正在使用新的垃圾回收器来加快速度。 - trampster
1
当前的垃圾收集器存在两个主要问题:(1) 堆中对象数量线性增加时,收集性能会下降。(2) 过多的分配/释放可能导致内存碎片化。新的垃圾收集器大多数情况下消除了这些问题。 - joemoe
它更昂贵。仅复制GC仅触及活动对象,并且整个代可以一次性释放。引用计数仅触及可能死亡的对象,并利用自由列表样式分配器,因此存在每个对象的解除分配成本,这在复制GC中根本不存在,因此与死亡对象数量成线性关系。如果您的代主要由死对象组成,则引用计数显然更加昂贵。这甚至没有触及引用计数的增量计数更新成本。引用计数还需要本地跟踪以收集循环,因此您甚至没有节省跟踪成本。 - naasking
显示剩余7条评论

10

你在这里没有做出很好的比较。你只是在比较两个函数中的一个(Math.Pow)。实际上,一个真正的应用程序会做更多的事情而不仅仅是这一个函数。

Mono的Math.Pow似乎通过在C中实现来进行了优化。我不知道.Net如何实现它。它可能完全是由托管代码实现的。

很可能你会发现两个运行时都足够快,适合日常需求。


@jpobst 这就是我想要表达的意思,不仅 Math.Pow,许多数学函数在 Mono 中似乎都更快。 想象一下一个进行大量数学运算或科学应用程序的财务应用程序,对于它们来说,Mono 可能比 .NET 更好,并且使用起来更容易。我完全同意两个运行时对于日常需求都足够快。实际上,对于大多数业务应用程序,我们不像开发时间那样关心性能,否则我们就不会首选使用 C#。如果只是为了性能,我们就会使用 C++、C 或汇编语言。 - funwithcoding
我想了解为什么在Mono中速度更快,即使它们共享相同的IL。现在我意识到,尽管IL相同,但操作系统不同,并且将IL转换为本机代码的方式也不同。 - funwithcoding

-3

这个测试真正测试的只是计时器的实现 - 这些测试通常很糟糕,因为没有两个操作系统实现它们的方式相同。有些使用硬计时器非常精确(Linux),而有些则基于系统需求可以被抢占的计时器(Windows)。如果您想要对调用进行基准测试,则必须避免中间调用,否则您可能正在计时它们而不是您认为正在测试的内容。Math POW 在两个平台上几乎是相同的,因为计算它所需的数学方法已经有一百年之久了。我更倾向于关注计时器的操作系统差异。


-19
嗯,我并不感到惊讶同样的字节码执行得更快。
如果你尝试使用Mono的Windows版本,我预计性能差异会更小,除非涉及到SIMD优化操作的情况。

12
仅仅是主观猜测。 - spoulson
13
“Linux在任何方面都比Windows快得多。”【需要引用】 - Sasha Chedygov
4
“@Arafangion: 这是有影响的。用在营销上的钱就不能用在产品本身上了。如果营销做得好,产品本身就不需要那么好也能成功。” - Marko
12
对一个非常真实但可能有偏见的回答被投下很多负票感到惊讶。 - Lilith River
6
点赞这篇意外遭到厌恶的帖子。 - csvan
显示剩余6条评论

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