JavaFX 画布延迟

5
我正在尝试将一些Java2D代码转换为JavaFX,但在JavaFX Canvas的性能问题上遇到了困难。在某个点上,我将不得不在屏幕上绘制数千个小圆。

我的问题是,在第一次绘制时,我的代码执行需要很长时间。但如果我必须执行第二次绘制,则仅需绘制的时间的一小部分(至少快10倍)。

我做错了什么吗?有没有办法防止这种初始延迟?

我编写了这段代码来测试它。 在这段代码中,我在1000 x 1000画布上以随机位置绘制了500,000个圆。 我将此代码链接到一个按钮单击事件,并且在第一次单击它时需要10秒钟才能执行。 但是,如果我再次单击它,只需要0.025秒。

private void paintCanvas() {
    long initTime = System.currentTimeMillis();

    GraphicsContext cg = canvas.getGraphicsContext2D();
    cg.setFill(Color.WHITE);
    cg.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
    cg.setFill(Color.rgb(0, 0, 0, 0.1));

    Random rand = new Random();
    for (int i = 0; i < 500000; i++) {     
        cg.fillOval(1000 * rand.nextFloat(), 1000 * rand.nextFloat(), 2, 2);
    }

    long endTime = System.currentTimeMillis();
    System.out.println("Time spent on drawing:" + (endTime - initTime)/1000.0f);        
}

实际上,新元素的最大数量没有限制。它可以根据用户的需求从几百个到数十万个不等。如果一些元素随着时间的推移出现,也是可以接受的。


我还没有在JavaFX 8上测试过它。但是,这是否意味着在JavaFX 2.2中的初始延迟是一个bug,在JavaFX 8.0+中已经得到解决了呢? - ItachiUchiha
这个赏金对于某人来说非常容易赚取,他们所需要做的就是发布一个回答,引用并给予Jim Graham邮件列表评论的信誉。 - jewelsea
@Jewelsea Jim的假设是错误的,认为JDK 8.0通过新的指数命令缓冲区升级解决了这个问题。我在Win7 4 Core i7 2.4Ghz上运行8u5 64位,正在渲染一个非常相似的画布,就像Renato一样,我看到单个画布上500000个椭圆的初始渲染时间为7秒。我用Renato的特定代码替换了我的代码,结果是一样的。无论Graham先生指的是什么,那种行为都不是默认的。 - Birdasaur
Renato的paintCanvas方法在运行Java 1.8.0_20-ea-b14,OS X 10.9.3,2012 MacBook Air,1.8GHz的情况下,用156毫秒填充了50万个椭圆。 - jewelsea
显示剩余2条评论
2个回答

1

大家好,感谢你们的帮助。我向OpenJFX邮件列表发送了相同的问题,其中一位开发人员回答了我。似乎我的JavaFX 2.2版本仍然使用旧模型来增加命令缓冲区。新版本JavaFX 8使用更高效的模型,使第一次绘制与后续绘制一样快。

以下是我收到的答案:

Jim Graham (james.graham at oracle.com)

Mon May 12 21:17:19 UTC 2014

This is likely due to growing the command buffer which was done linearly at one point (probably still done that way in 2.2), but is now exponential in 8.0. The first render time is nearly instantaneous in 8.0, but takes a long time as you found when I try it with one of my old 2.x builds...

      ...jim

0

我能想到一些可能的原因,但我们先从一个开始:

可能是JVM的即时编译器正在影响你的执行。这取决于你的JVM选项(无论是客户端还是服务器JIT,以及是否使用了AggresiveOpts)。

记住,JVM足够聪明,可以对循环进行优化。在我看来,你可以从这里开始,在执行时将以下内容添加到你的JVM选项中:-XX:+PrintCompilation,并查看控制台上的输出,你的方法应该在第一次执行时被编译,然后在第二次执行时不应该观察到任何编译。如果是这样的话,那么你就知道这段代码已经被编译并存储在CodeCache中,执行不是通过解释器进行的,而是通过直接编译的本地代码进行的,这将具有更好的性能。

让我们知道你的发现!

JVM选项参考(可能需要找到你特定的JVM文档): http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

顺便说一句,你能试着把开始时间降低到在实例化随机数之前吗?这样就可以取两个时间了,一个是在开头和随机数之前,第二个是在最后一个时间之后,最后当循环结束时,想要尝试分解代码花费的时间,看看是在观察时(循环或画布实例化)。


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