【问题标题】:C++ (Intel) vs Java (Hotspot) vs C# benchmark questions (code and results included)C++ (Intel) vs Java (Hotspot) vs C# 基准测试问题(包括代码和结果)
【发布时间】:2012-05-02 20:28:35
【问题描述】:

我一直在比较三种主要语言之间的原始 CPU 性能速度(代码和结果如下)。我很好奇主要语言如何比较原始计算能力。我有一个理论认为,在不涉及内存开销的情况下,Java 和 C# 可能会与 C++ 相媲美。

我的问题:

1) 已编辑(C++ 时序现在更现实)

2) 我是否认为 JVM 在第一次迭代中花费了很长时间,但第二次它已经完成了分析并因此进行了优化? Hotspot 怎么知道在我的外部循环的第一次迭代之后完成优化而不是进行到一半?

3) 为什么 C# 的性能不像 Java 那样一开始就进行了大量优化? C# 与 Java 有什么不同?为什么 C# 速度较慢 - 仅仅是因为优化较少?

4) C# 测试时序在 2246 和 2262 毫秒之间振荡是否有任何具体原因,这可能是两个不同的时间,因为 CPU 有两个内核?

编辑:更新代码以显示 C# 代码中的秒表用法。

编辑:更正 C++ 计时代码和结果

设置:

  • C++:VS2010 和 Intel 编译器(内置发布模式,优化: O2,启用内在功能:是的,喜欢大小和速度:两者都不是, 省略帧指针:否,启用光纤安全优化:否,整体 程序优化:是的)

  • Java:Eclipse,Hotspot 64 位编译器版本 17,Java 1.6

  • C#:VS2010 和 .net 4.0(内置发布模式)

  • CPU:Intel E6600 (2.4GHz),运行频率为 2.7GHz,总线速度 300MHz,8GB 内存,DRAM 频率:375MHz

  • Win 7(64 位)

C++ 代码:

#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <fstream> 

using namespace std;


double PCFreq = 0.0;
__int64 CounterStart = 0;

void StartCounter()
{
    LARGE_INTEGER li;
    if(!QueryPerformanceFrequency(&li))
        cout << "QueryPerformanceFrequency failed!\n";

    PCFreq = li.QuadPart;

    QueryPerformanceCounter(&li);
    CounterStart = li.QuadPart;
}
double GetCounter()
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return double(li.QuadPart-CounterStart)/PCFreq;
}

static long counter = 0;

int _tmain(int argc, _TCHAR* argv[])
{

    for (int m = 0; m < 10; m++)
    {
        StartCounter();
        counter = 0;

        for (int j = 0; j < 3; j++)
        {
            //Just to test timing is working correctly
            //int* p = new int;

            for (long i = 0; i < 200000000; i++)
            {
                counter++;
            }
        }

        cout << GetCounter()*1000000 << " microseconds" << endl;
    }


    int p = 0;
    cin >> p;
    return 0;
}

C++ 结果:

7.19 微秒

1.89

2.27

1.51

4.92

10.22

10.22

9.84

9.84

10.6

Java 代码:

public class main {

    static long counter = 0;

    public static void main(String[] args) {

        for(int m=0; m<10; m++){
            long start = System.nanoTime();
            counter = 0;

            for(int j=0;j<3; j++){
                for(long i=0; i<200000000; i++){
                    counter++;
                }
            }

            System.out.println(((System.nanoTime()-start)/1000000) + " ms");
        }
    }
}

Java 结果:

5703 milliseconds
471 ms
468 ms
467 ms
469 ms
467 ms
467 ms
467 ms
469 ms
464 ms

C# 代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics

namespace t1
{
    class Program
    {
        static long counter = 0;

        static void Main(string[] args)
        {
            for (int m = 0; m < 10; m++)
            {
                Stopwatch s = new Stopwatch();
                s.Start();
                counter = 0;

                for (int j = 0; j < 3; j++)
                {

                    for (long i = 0; i < 200000000; i++)
                    {
                        counter++;
                    }

                }
                s.Stop();
                Console.WriteLine(s.Elapsed.TotalMilliseconds + " ms");
            }

            Console.ReadLine();
        }
    }
}

C# 结果:

2277 毫秒

2246 毫秒

2262 毫秒

2246 毫秒

2262 毫秒

2246 毫秒

2262 毫秒

2246 毫秒

2262 毫秒

2262 毫秒

【问题讨论】:

  • 对于 C++ 版本,请检查编译器的程序集输出。循环可以完全优化。
  • 您不需要一个出色的编译器来恒定折叠该循环,I 可以编写一个这样做的通道(对于原始类型)。我想 JVM/CLR JIT 不做同样事情的唯一原因是您的 Java/C# 基准测试存在问题(JIT 甚至会启动吗?)。
  • 对于 C#,您是在 Visual Studio 中运行还是在外部运行?在附加调试器的情况下运行会禁用大多数优化。仅在发布模式下构建是不够的。
  • @user1291492 我不确定您建议使用什么基准。我同意这样的微基准对大多数应用程序来说是无用的——但是,这适用于所有微基准,无论是否多线程。在微基准测试有用的情况下,应用程序可能仍然会在各个内核上进行大量列出,无论并行使用多少内核。当您使用 N 个内核时,知道在一个内核上处理数字的速度有多快仍然有一些好处。当然,在这两种情况下,您都需要知道实际计算了多长时间,而不是等待 I/O 或锁定。
  • DateTime.Now 对于在该级别测量时间没有用处。您正在寻找Stopwatch。此外,您似乎没有正确使用 QueryPerformanceCounter。 (stop-start)/freq = 秒。

标签: c# java c++ performance caching


【解决方案1】:

你的 C++ 代码中有一个逻辑问题,它使用 QueryPerformanceFrequency:

PCFreq = double(li.QuadPart)/1000000000.0; // <- this is not correct
PCFreq = li.QuadPart;                      // <- this is correct

您只需将 li.QuadPart 分配给 PCFreq 并在打印代码中转换为毫秒或纳秒:

// convert from seconds to milliseconds
cout << GetCounter() * 1000.0 << endl;

通过此更改,我可以获得 C++ 代码的实际计时。无论这些时间是否“有效”或在进行比较时是否有用,我都不会发表评论。

【讨论】:

  • 感谢六字母变量。我从网上的一个例子中得到了那个计时代码,我猜这只是虚假的结果,让它看起来是正确的。我已经修改了代码,现在需要 1 到 10 微秒。
【解决方案2】:

1 - Sixlettervariables 似乎指出了你在这个问题上的错误

2 - 热点将优化代码。这是一个类似的问题,它也看到循环加速了 10 倍。所以你看到的是预期的输出。 First time a Java loop is run SLOW, why? [Sun HotSpot 1.5, sparc]

3 - 我对 C# 的了解不够,无法提供帮助。它可能没有优化内部循环(你有 3 个不同的循环)。也许尝试将您正在测试的 2 个循环提取到一个完全独立的方法中,看看是否有帮助。

4 - DateTime 是表示日期和时间,而不是高精度计时。因此,它不是那么准确。据我所知,DateTime.Now 的分辨率为 10 毫秒

(仅供参考,这篇文章对 JIT、C# 和 C++ 优化有一些很好的解释,可能会对您有所帮助:C++ performance vs. Java/C#

【讨论】:

  • 没有热点确实没有优化代码 - 您可以轻松测试代码,因为性能是恒定的,与运行次数无关。我认为这与两个原因有关:1. 我们正在使用非私有静态变量(可以补救)和 2. OSR 只会 JIT 增量循环而不是外部块。这意味着即使我们将计数器定义为局部变量,它仍然不会删除循环。
  • @Voo,你多次提到“OSR”,这是什么意思?
  • @user997112 抱歉,我又违反了一项基本原则。 OSR 正在进行堆栈替换。 This article describes it simply for Java。基本上,JITed 方法仅在我们完成编译后使用,下次调用时使用。相反,OSR 编译由编译器运行的部分代码(例如,单个循环),我们在旅途中从解释代码转换为编译代码。有几个问题,除了微基准测试之外几乎没有用处。
  • 非常有趣!甲骨文网站是否包含大量描述内部运作的文章??
【解决方案3】:

我认为编译器可能会在编译时计算 counter 的值,而不是遍历你的循环。

我认为一个简单的计数器是一个非常糟糕的基准。

顺便说一下,尝试在方法中运行 java 代码。由于 JIT 优化,它可能会更快。 (但我不确定)

【讨论】:

  • 非常好!第一次迭代现在下降到 5700 毫秒,第二次下降到 8100 毫秒,然后剩下的约为 670 毫秒。这是否表明有大约 200 毫秒的方法开销?我刚刚重新运行,其余迭代的平均时间仍然为 670 毫秒...
  • 我认为Hotspot编译器需要一些时间来寻找代码,值得编译成混搭代码。
  • 你的意思是不是所有的代码都编译成机器码?如果是这样,我不明白......
【解决方案4】:

各种编译器的基准测试已经完成并很好地组合在一起。

The Computer Language Benchmark Games

Java 7 Server vs. GNU C++

C# Mono vs. GNU C++

C# Mono vs Java 7 Server

虽然 Java 7 Server 比 C# Mono 2.10.8 更快,但请查看 Java 7 使用的内存量。

【讨论】:

  • 看起来不错,但要注意具体细节。如果您查看示例中的代码,许多 C# 函数是次优的,而 Java 函数是高度优化的。例如:这里“MakeCumlative”Java 中的紧密循环得到密集的浮点数组(cpu 缓存最佳)shootout.alioth.debian.org/u64q/… 而 c# 得到一个臃肿的对象数组shootout.alioth.debian.org/u64q/… 这只是一个例子,我确信它可以双向,但很明显它不是苹果对苹果的比较
  • @Glenn 另外请记住,比较是针对 Mono 而不是 .NET。我“听说”.NET 4.0 非常快。当然,证明这一点会违反他们的 EULA。
  • 是的,我正在阅读一些文章,这些文章谈到了 MS.net 团队用来避免边界检查之类的所有编译器技巧。有点滞后.. 虽然单声道确实首先引入了真正的 64 位数组支持,所以在常见的原始情况下单声道可能更具竞争力。
猜你喜欢
  • 1970-01-01
  • 2011-11-27
  • 1970-01-01
  • 2012-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 1970-01-01
相关资源
最近更新 更多