【问题标题】:[Java]Arrays and Collections test, unexpected outcome?[Java]Arrays and Collections 测试,意外结果?
【发布时间】:2012-09-13 17:48:59
【问题描述】:

可能有数百个关于 Java 集合与数组的问题,但这是我真的没想到的。

我正在为我的游戏开发一个服务器,并且为了在客户端和服务器之间进行通信,您需要发送数据包(显然),所以我做了一些测试,我可以最好地使用哪个 Collection(或数组)来处理它们,HashMap, ArrayList 和一个 PacketHandler 数组。结果出乎我的意料,因为 ArrayList 赢了。

数据包处理结构就像字典的用法(PacketHandler 的索引),并且因为数组是字典使用的最原始形式,我认为它比 ArrayList 更容易执行。有人可以解释一下为什么会这样吗?

我的测试

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

public class Main {
  /**
   * Packet handler interface.
   */
  private interface PacketHandler {
    void handle();
  }

  /**
   * A dummy packet handler.
   */
  private class DummyPacketHandler implements PacketHandler {
    @Override
    public void handle() { }
  }

  public Main() {
    Random r = new Random();
    PacketHandler[] handlers = new PacketHandler[256];
    HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
    ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

    // packet handler initialization
    for (int i = 0; i < 255; i++) {
      DummyPacketHandler p = new DummyPacketHandler();
      handlers[i] = p;
      m.put(new Integer(i), p);
      list.add(p);
    }

    // array
    long time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      handlers[r.nextInt(255)].handle();
    System.out.println((System.currentTimeMillis() - time));

    // hashmap
    time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      m.get(new Integer(r.nextInt(255))).handle();
    System.out.println((System.currentTimeMillis() - time));

    // arraylist
    time = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++)
      list.get(r.nextInt(255)).handle();
    System.out.println((System.currentTimeMillis() - time));
  }

  public static void main(String[] args) {
    new Main();
  }
}

我觉得问题已经解决了,谢谢大家

【问题讨论】:

  • 在性能方面获胜?
  • 可能是因为你没有衡量你认为你正在衡量的东西。
  • 颠倒你调用它们的顺序——如果你先做 ArrayList,然后是 Map,然后是数组,ArrayList 仍然是最快的吗?
  • 请澄清一下,“胜利”是什么意思?
  • 最后一个处理程序[255] 是null 有什么原因吗?

标签: java arrays collections


【解决方案1】:

更简短的回答是 ArrayList 第一次优化得稍微好一些,但从长远来看仍然较慢。

JVM 在完全预热之前如何以及何时优化代码并不总是显而易见的,并且可以根据您的命令行选项在版本之间进行更改。

真正有趣的是当你重复测试时你会得到什么。这里有所不同的原因是代码是在后台分阶段编译的,因为您希望在代码已经与从一开始就一样快的情况下进行测试。


您可以采取一些措施来提高您的基准测试的可重复性。

  • 提前生成随机数,它们不是测试的一部分,但会减慢您的速度。
  • 将每个循环放在单独的方法中。第一个循环触发整个方法进行编译,无论好坏。
  • 重复测试 5 到 10 次,忽略第一次
  • 使用 System.nanoTime() 代替 currentTimeMillis() 在这里可能没有任何区别,但这是一个好习惯。
  • 尽可能使用自动装箱,以便它使用整数缓存或 Integer.valueOf(n) 来做同样的事情。 new Integer(n) 将始终创建一个对象。
  • 确保您的内部循环执行某些操作,否则 JIT 很可能会将其优化为空。 ;)

.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

public class Main {

    /**
     * Packet handler interface.
     */
    private interface PacketHandler {
        void handle();
    }

    /**
     * A dummy packet handler.
     */
    static class DummyPacketHandler implements PacketHandler {

        @Override
        public void handle() {
        }

    }

    public static void main(String[] args) {
        Random r = new Random();

        PacketHandler[] handlers = new PacketHandler[256];
        HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
        ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

        // packet handler initialization
        for (int i = 0; i < 256; i++) {
            DummyPacketHandler p = new DummyPacketHandler();
            handlers[i] = p;
            m.put(new Integer(i), p);
            list.add(p);
        }

        int runs = 10000000;
        int[] handlerToUse = new int[runs];
        for (int i = 0; i < runs; i++)
            handlerToUse[i] = r.nextInt(256);

        for (int i = 0; i < 5; i++) {
            testArray(handlers, runs, handlerToUse);
            testHashMap(m, runs, handlerToUse);
            testArrayList(list, runs, handlerToUse);
            System.out.println();
        }
    }

    private static void testArray(PacketHandler[] handlers, int runs, int[] handlerToUse) {
        // array
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            handlers[handlerToUse[i]].handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }

    private static void testHashMap(HashMap<Integer, PacketHandler> m, int runs, int[] handlerToUse) {
        // hashmap
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            m.get(handlerToUse[i]).handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }

    private static void testArrayList(ArrayList<PacketHandler> list, int runs, int[] handlerToUse) {
        // arraylist
        long time = System.nanoTime();
        for (int i = 0; i < runs; i++)
            list.get(handlerToUse[i]).handle();
        System.out.print((System.nanoTime() - time)/1e6+" ");
    }
}

array HashMap ArrayList打印

24.62537 263.185092 24.19565 
28.997305 206.956117 23.437585 
19.422327 224.894738 21.191718 
14.154433 194.014725 16.927638 
13.897081 163.383876 16.678818 

代码预热后,数组稍微快了一点。

【讨论】:

  • -“提前”是什么意思,比如将它们存储在数组中?而且我真的不认为 nanoTime() 会更好,因为它每个都迭代一百万次,但感谢提示
  • 这里并没有好多少,但从我的示例中可以看出,它下降到几毫秒。
  • @Jon,nanoTime 更好,因为高精度时钟更准确(不仅仅是更精确)。也就是说,curreTimeMillis 方法可以给你一个漂移的值。
  • 简短的回答是 ArrayList 第一次优化得稍微好一些,但从长远来看仍然比较慢。
  • @PeterLawrey,对不起,我的评论是在您更新测试并运行它之前。我认为像你最后的评论这样说是一个很好的答案。
【解决方案2】:

您的基准测试至少存在一些问题:

  • 您直接在 main 中运行测试,这意味着当您编译 main 方法时,JIT 编译器还没有时间优化所有代码,因为它还没有运行它
  • map 方法每次都会创建一个新的整数,这是不公平的:使用m.get(r.nextInt(255)).handle(); 允许使用整数缓存
  • 您需要多次运行测试才能得出结论
  • 您没有使用您在循环中执行的操作的结果,因此允许 JIT 简单地忽略它们
  • 监控 GC,因为它可能始终同时运行并偏向您的一个循环的结果,并在每个循环之间添加一个 System.gc() 调用。

但在做这一切之前,请阅读this post ;-)

稍微调整一下代码后,我得到了以下结果:

Array: 116
Map: 139
List: 117

因此,数组和列表在编译后几乎相同,而映射稍慢。

代码:

公共类主{

/**
 * Packet handler interface.
 */
private interface PacketHandler {

    int handle();
}

/**
 * A dummy packet handler.
 */
private class DummyPacketHandler implements PacketHandler {

    @Override
    public int handle() {
        return 123;
    }
}

public Main() {
    Random r = new Random();

    PacketHandler[] handlers = new PacketHandler[256];
    HashMap<Integer, PacketHandler> m = new HashMap<Integer, PacketHandler>();
    ArrayList<PacketHandler> list = new ArrayList<PacketHandler>();

    // packet handler initialization
    for (int i = 0; i < 255; i++) {
        DummyPacketHandler p = new DummyPacketHandler();
        handlers[i] = p;
        m.put(new Integer(i), p);
        list.add(p);
    }

    long sum = 0;
    runArray(handlers, r, 20000);
    runMap(m, r, 20000);
    runList(list, r, 20000);

    // array
    long time = System.nanoTime();
    sum += runArray(handlers, r, 10000000);
    System.out.println("Array: " + (System.nanoTime() - time) / 1000000);

    // hashmap
    time = System.nanoTime();
    sum += runMap(m, r, 10000000);
    System.out.println("Map: " + (System.nanoTime() - time) / 1000000);

    // arraylist
    time = System.nanoTime();
    sum += runList(list, r, 10000000);
    System.out.println("List: " + (System.nanoTime() - time) / 1000000);

    System.out.println(sum);
}

public static void main(String[] args) {
    new Main();
}


private long runArray(PacketHandler[] handlers, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += handlers[r.nextInt(255)].handle();
    return sum;
}

private long runMap(HashMap<Integer, PacketHandler> m, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += m.get(new Integer(r.nextInt(255))).handle();
    return sum;
}

private long runList(List<PacketHandler> list, Random r, int loops) {
    long sum = 0;
    for (int i = 0; i < loops; i++)
        sum += list.get(r.nextInt(255)).handle();
    return sum;
}

}

【讨论】:

  • 是的,我知道随机整数生成并不“公平”,但我认为我并不重要,因为我在每个集合/数组中生成它们
  • @Jon 我说的是只在地图上使用new Integer(random) => 每次都会创建一个新对象。
  • @Jon 相应地更正了我的帖子 - 我现在明白为什么不清楚了。
  • 啊,我明白了。是的,你是对的,但这实际上是测试的一部分;我想看看哪个更快,并且要使用 HashMap 来做到这一点,我必须创建那个 Integer 对象。或者你有什么不同的推荐吗?
  • @Jon 不,你不需要 - 只需传入一个 int,然后自动装箱即可完成剩下的工作。
猜你喜欢
  • 2016-09-22
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多