【问题标题】:Java performance optimizationJava性能优化
【发布时间】:2014-07-05 12:13:02
【问题描述】:

对于 JOGL 游戏,我的 fps 非常低,现在经过一些测试,我发现问题不在于 JOGL 部分,而在于纯 Java 计算。我需要定义很多float变量,占用了90%的时间。

我测试了 45 个浮点变量,其中只有 16 个获得了初始值。其余的只是float z1; float z2; 等。根据System.currentTimeMillis(),这大约需要5-10 毫秒。

但是这个带有 45 个浮点数的代码在一个由双循环调用的方法中。该方法总共被调用了 49 次 (7*7)。所有这些都在 JOGL 方法中在 JFrame 中绘制游戏,但是由于浮点变量太多,总共需要 100ms,这意味着只有 10fps。

所以基本上问题是我必须初始化 45*49=2205 个浮点数。有什么方法可以优化它以获得更好的 fps?

例如,double 会比 float 快吗?还是先在循环外定义变量,然后在循环内给它们赋值?有谁知道让这段代码运行得更快的方法?提前非常感谢。

编辑

根据要求,这里是源代码:

for (int x = -4; x < 3; x++) { // Loops 7 times
    for (int y = -4; y < 3; y++) { // Loops 7 times
        long t1 = System.currentTimeMillis();
        float z0  = terrain.getHeight(x-1, y-1); // Simple method, but takes up about half of the time
        float z1  = terrain.getHeight(x  , y-1);
        float z3  = terrain.getHeight(x+1, y-1);
        float z4  = terrain.getHeight(x+2, y-1);
        float z5  = terrain.getHeight(x-1, y  );
        float z6  = terrain.getHeight(x  , y  );
        float z7;
        float z8;
        float z9;
        float z10 = terrain.getHeight(x+1, y  );
        float z11 = terrain.getHeight(x+2, y  );
        float z12;
        float z13;
        float z14;
        float z15;
        float z16;
        float z17;
        float z18;
        float z19;
        float z20;
        float z21;
        float z22;
        float z23;
        float z24;
        float z25;
        float z26;
        float z27;
        float z28;
        float z29;
        float z30;
        float z31;
        float z32;
        float z33 = terrain.getHeight(x-1, y+1);
        float z34 = terrain.getHeight(x  , y+1);
        float z35;
        float z36;
        float z37;
        float z38 = terrain.getHeight(x+1, y+1);
        float z39 = terrain.getHeight(x+2, y+1);
        float z40 = terrain.getHeight(x-1, y+2);
        float z41 = terrain.getHeight(x  , y+2);
        float z43 = terrain.getHeight(x+1, y+2);
        float z44 = terrain.getHeight(x+2, y+2);
        t1 = System.currentTimeMillis() - t1;
        // Some other code where I use these variables.
        // Takes between 0-1 ms in total.
    }
}

编辑

我现在测试了 getHeight() 方法,它占用了大约一半的时间。使用这种方法的7个变量加起来大约5ms,总共是10个。下面是getHeight()中使用的代码:

public float getHeight(float x, float y) {
    long t1 = System.currentTimeMillis();
    Coordinate c = new Coordinate(x, y);
    for (Entry<Coordinate, Float> e : heightMap.entrySet()) { // heightMap = HashMap<Coordinate, Float>
        if (e.getKey().x == c.x && e.getKey().y == c.y) {
            System.out.println("getHeight: " + (System.currentTimeMillis() - t1) + " ms");
            return e.getValue();
        }
    }
    return 0f;
}

Coordinate 是我自己创建的一个类,它有一个构造函数,带有两个用于 x 和 y 的浮点参数,并将它们全局保存在类本身中。

我之所以不使用heightMap.get(c),是因为这总是抛出一个NullPointerException,而上面给出的代码永远不会到达return 0f;的最后一行。

编辑

在这个 [link] (Why are custom objects not equivalent keys for a HashMap?) 问题中找到了问题的解决方案,即我必须将 public boolean equals(Object other)public int hashCode() 添加到我的自定义坐标类中。现在 getHeight 方法可以与 heightMap.get(c) 一起使用,它消除了其中的循环并使程序更快。现在总共(49 个循环)大约需要 1 毫秒。

【问题讨论】:

  • 请发布您的代码。
  • 在我的机器上,生成 2205 个随机浮点数需要 649 毫秒,用这些随机值初始化一个包含 2205 个浮点数的数组并将它们打印到 System.out,1000 次。所以用随机值初始化 2205 个浮点数并打印它们需要 0.65 毫秒。我看不出仅仅在你的机器上初始化 2205 个浮点数需要 100 毫秒。您可能没有正确测量,并且您可能正在尝试优化错误的部分。没有代码,反正我们也无话可说。
  • 哇,你听说过数组吗?
  • “初始化”那些未初始化的变量几乎没有任何成本。成本(一点)仅仅是拥有这么多,因为您的堆栈帧变得太大而无法轻松容纳单个缓存行。
  • 您在getHeight 中有一个println。把它拿出来,你就会放大。

标签: java performance


【解决方案1】:

请注意,某些操作系统必须使用全屏独占模式才能为您提供足够的资源。

在循环外定义变量将无济于事,因为 Java 优化了您的代码,而在循环内定义变量实际上为 Java 提供了提高性能的提示。我认为(我只能猜测,因为您没有发布任何代码)是,您可以考虑使用一个 long 数组。它们在循环中使用非常有效,并且它们也在你的内存中一个接一个地分配,因此可以有效地使用缓存。

对我来说,填充 2025 Longs 所需的时间略高于 1 毫秒,包括调用 random.nextLong() 方法。

public Long fillLongs(int numberofLongs) {

    long[] longs = new long[numberofLongs];
    Random r = new Random();

    long start = System.currentTimeMillis();

    for (long l : longs) {
        l = r.nextLong();
    }

    long end = System.currentTimeMillis();

    return end - start;

}

使用并行流,此任务花费的时间更短。通常在 1 毫秒以下。

public Long fillLongs(int numberofLongs) {

    Long[] longs = new Long[numberofLongs];
    List<Long> longList = Arrays.asList(longs);
    Random r = new Random();

    long start = System.currentTimeMillis();

    longList.parallelStream().forEach(l -> {
        l = r.nextLong();
    });

    long end = System.currentTimeMillis();

    return end - start;

}

【讨论】:

    【解决方案2】:

    对于 Java 中的高性能计算,您可以考虑使用 JNI(Java 本地接口)——尽管它需要 C++ 知识。如需快速入门,请查看here

    【讨论】:

    • 最初,Java 已经在 HPC 中使用多年,例如 IBM。其次,他的计算非常简单,C 只会在一些非常特殊的情况下提供加速。第三,他从未说过他的目标是Android,而J2SE 在桌面环境中支持JNI。最后,他的问题来自于对哈希映射的幼稚使用。
    猜你喜欢
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 2013-05-08
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 2015-07-19
    相关资源
    最近更新 更多