Java ArrayList真的比C ++向量要慢吗?

我不想开始另一个毫无意义的关于Java或C ++是否是更好的语言的争论。 我想知道我为特定任务做的比较是否公平,测量的数据是否正确。

我们需要决定是否为我们的下一个项目使用Java或C ++。 我在C ++阵营,但我想为我的案例提供有力的论据。 我们的应用程序是一个特殊的应用程序,有以下需

  • 该程序必须运行相当快,并具有合理的内存效率。 我们不关心最近20%的表现。 然而,10倍的性能差异是一个阻碍。
  • 我们有很多数组。 我们不知道他们的大小。 因此,阵列可以在延迟的O(1)运行时间后面增长是非常重要的。
  • 数组中的元素由少量的基本数据类型组成。 典型的例子是一个整数或浮点数的元组。
  • 数组可以变大。 10 ^ 6个元素是标准的。 我们有10 ^ 7元素的应用,支持10 ^ 8会很好。
  • 我用C ++和Java实现了一个玩具程序。 首先,我介绍C ++版本:

    #include <iostream>
    #include <vector>
    #include <cstdlib>
    using namespace std;
    
    struct Point{
            float x, y;
    };
    
    int main(int argc, char*argv[]){
            int n = atoi(argv[1]);
    
            vector<Point>arr;
    
            for(int i=0; i<n; ++i){
                    Point p;
                    p.x = i;
                    p.y = i+0.5f;
                    arr.push_back(p);
            }
    
            float dotp = 0;
            for(int i=0; i<n; ++i)
                    dotp += arr[i].x * arr[i].y;
    
            cout << dotp << endl;
    }
    

    接下来是做同样事情的Java版本:

    import java.util.*;
    
    class Point{
            public float x, y;
    }
    
    class Main{
            static public void main(String[]args){
                    int n = Integer.parseInt(args[0]);
    
                    ArrayList<Point> arr = new ArrayList<Point>();
    
                    for(int i=0; i<n; ++i){
                            Point p = new Point();
                            p.x = i;
                            p.y = i+0.5f;
                            arr.add(p);
                    }
    
                    float dotp = 0;
                    for(int i=0; i<n; ++i)
                            dotp += arr.get(i).x * arr.get(i).y;
    
                    System.out.println(dotp);
            }
    }
    

    我将使用命令行的元素数传递给程序,以防止优化器在编译期间执行程序。 计算的值不是有用的。 唯一有趣的问题是程序的运行速度以及它们使用的内存量。 我从C ++开始:

    $ g++ --version
    g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
    $ g++ -O3 test.cpp -o test
    $ /usr/bin/time ./test 1000000
    3.33381e+17
    0.01user 0.00system 0:00.02elapsed 100%CPU (0avgtext+0avgdata 10084maxresident)k
    0inputs+0outputs (0major+2348minor)pagefaults 0swaps
    $ /usr/bin/time ./test 10000000
    3.36984e+20
    0.08user 0.01system 0:00.09elapsed 100%CPU (0avgtext+0avgdata 134380maxresident)k
    0inputs+0outputs (0major+4074minor)pagefaults 0swaps
    $ /usr/bin/time ./test 100000000
    2.42876e+23
    0.77user 0.09system 0:00.87elapsed 99%CPU (0avgtext+0avgdata 1050400maxresident)k
    0inputs+0outputs (0major+6540minor)pagefaults 0swaps
    

    “用户”时间是程序运行的时间。 对于10 ^ 6元素,它运行0.01秒,10 ^ 7元素0.08秒,10 ^ 8元素0.77秒。 “maxresident”是内核为程序提供的物理内存量(千字节)。 10 ^ 6的10MB,10 ^ 7的132MB,10 ^ 8的1GB。

    内存消耗听起来很正确。 具有x个元素的数组需要sizeof(float)* 2 * x = 8 * x个字节的内存。 10 ^ 6个元素大约8MB,10 ^ 7大约76MB,10 ^ 8大约762MB。

    接下来,我运行Java程序:

    $ javac -version
    javac 1.6.0_41
    $ javac Main.java
    $ java -version
    java version "1.7.0_131"
    OpenJDK Runtime Environment (IcedTea 2.6.9) (7u131-2.6.9-0ubuntu0.14.04.2)
    OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)
    $ /usr/bin/time java Main 1000000
    3.33381168E17
    0.16user 0.00system 0:00.09elapsed 173%CPU (0avgtext+0avgdata 79828maxresident)k
    0inputs+64outputs (0major+4314minor)pagefaults 0swaps
    $ /usr/bin/time java Main 10000000
    3.3698438E20
    5.23user 0.18system 0:02.07elapsed 261%CPU (0avgtext+0avgdata 424180maxresident)k
    0inputs+64outputs (0major+13508minor)pagefaults 0swaps
    $ /usr/bin/time java Main 100000000
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at Main.main(Main.java:14)
    Command exited with non-zero status 1
    3840.72user 13.06system 17:11.79elapsed 373%CPU (0avgtext+0avgdata 2281416maxresident)k
    0inputs+1408outputs (0major+139893minor)pagefaults 0swaps
    

    对于10 ^ 6元素,它需要0.16秒和78 MB。 对于10 ^ 7元素,它需要5.23秒和414 MB。 我试图运行10 ^ 8元素的程序,但是Java崩溃了。 它使用我机器的所有内核(在一个顺序程序中!),并运行17分钟,同时占用2.2GB。 我的机器有8 GB的内存。

    对于10 ^ 6元素,C ++是0.16 / 0.01 = 16倍,需要78/10 = 7.8倍的内存。 对于10 ^ 7元素,C ++的速度更快,为5.23 / 0.08 = 65倍,需要414/132 = 3.1倍的内存。 Java没有在10 ^ 8元素的测试实例上完成,而C ++程序在低于一秒的时间内完成。

    对于10 ^ 6 Java来说似乎易于管理,但并不理想。 对于10 ^ 7和10 ^ 8来说,这绝对是不行的。 我期望C ++在Java方面有轻微的性能优势,但不是那么激烈。

    最可能的解释是我的测试方法是错误的,或者我的Java代码中有一个不明显的性能瓶颈。 另一种解释是,OpenJDK JVM明显缺乏来自其他供应商的JVM。

    请向我解释为什么Java在这个基准测试中表现糟糕。 我是怎么无意中让Java看起来比它更糟?

    谢谢


    因此,不运行JIT解释了一小部分影响,但不是主要放缓。

    对,由于JIT的启动,Java启动速度很慢,需要一段时间才能全速运行。

    但是你描述的表现是灾难性的,还有另一个原因:你写了

    它使用了我机器的所有核心(在一个顺序程序中!)

    这一定是垃圾收集器。 GC运行困难意味着你的内存不足。 在我的机器上,时代已经到来

      28.689 millis for 1 M pairs
     143.104 millis for 10 M pairs
    3100.856 millis for 100 M pairs
      10.404 millis for 1 M pairs
     113.054 millis for 10 M pairs
    2528.371 millis for 100 M pairs
    

    这仍然是一个痛苦,但可能是一个可用的起点。 观察到第二轮运行速度更快,因为它得到了更好的优化。 请注意,这不是应该如何编写Java基准的!


    内存消耗的原因是你有一个引用List ,其中包含两个浮点数的对象,而不是浮点数对的vector 。 每个引用增加4或8字节的开销,每个对象增加更多。 另外,每个访问都有一个间接的方向。

    如果内存很重要,那么这是不正确的如何在Java中编写它的方式。 肯定有更好的方法(我让它试试),但代码可能会变得很难看。 没有值类型的Java在这样的计算中很糟糕。 恕我直言,它几乎在其他地方擅长(个人意见)。

    一个等效的C ++代码将使用vector<Point*> 。 如果你这样做,你的代码变得更慢,内存也更大,但仍比Java(对象头开销)好。


    我重写了代码,因此它使用一个PointArray将两个浮点数相邻存储在一个数组中。 没有测量任何东西,我声称内存消耗现在大致相同。 现在的时代是

      31.505 millis for 1 M pairs
     232.658 millis for 10 M pairs
    1870.664 millis for 100 M pairs
      17.536 millis for 1 M pairs
     219.222 millis for 10 M pairs
    1757.475 millis for 100 M pairs
    

    这仍然太慢了。 我想,这是绑定检查,不能在Java中关闭(除非你解决Unsafe )。 通常,JIT(即时编译器)可以将它们移出循环,这使得它们的成本可以忽略不计。

    这也可能是我的缓慢(因子1.5)数组大小调整(IIRC vector使用2因子)。 或者当你几乎完成时数组被调整大小时只是可惜。 由于你对阵列没有做任何事情,它可能会减轻很多。

    无论如何,当你想要快速处理基元数组时,至少需要一个好的Java程序员。 这可能需要一两天,直到你获得好的表现。 一个好的图书馆也可以做到。 在Java 10(或11,或...)之前使用List<Point>效率太低。

    链接地址: http://www.djcxy.com/p/96411.html

    上一篇: Is Java ArrayList really this much slower than C++ vector?

    下一篇: Build error when using Akavache + Mobile Center in a UWP project