Java 8相比Java 6在GUI性能方面表现不佳

31

在Java 6中,以下代码按预期运行,但在Java 8中需要更长时间。有趣的是,组件使用相同的方法setEnable()来启用和禁用组件,但禁用调用比启用调用花费的时间要长得多,几乎是两倍。在Java 8中,禁用操作比Java 1.6中的禁用操作要耗时得多。问题是为什么会发生这种情况?这是Java 8的性能问题吗?

以下是Java 6的结果:

    Sun Microsystems Inc. 1.6.0_45
    Initializing GUI
    GUI initialized in 1105 ms
    Disabling
    Disabled in 687 ms
    Enabling
    Enabled in 375 ms

以下是Java 8的结果:

    Oracle Corporation 1.8.0_25
    Initializing GUI
    GUI initialized in 604 ms
    Disabling
    Disabled in 6341 ms
    Enabling
    Enabled in 370 ms

代 码:

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class TestGUI extends JFrame implements ActionListener {

    private static final long serialVersionUID = 1L;

    public TestGUI() {
        initGUI();
    }

    public void actionPerformed(ActionEvent e) {
        String text;
        if(e.getActionCommand().equals("Enable-ALL")){
            enableAll();
            text= "Disable-ALL";
        }
        else{
            disableAll();
            text= "Enable-ALL";
        }
        ((JButton)e.getSource()).setText(text);
        ((JButton)e.getSource()).setEnabled(true);

    }


    private  void initGUI() {
        long m = System.currentTimeMillis();
        System.out.println("Initializing GUI");
        setTitle(System.getProperty("java.vendor") + " " + System.getProperty("java.version"));
        setLayout(new FlowLayout());

        JButton b = new JButton("Disable-ALL ");
        b.addActionListener(this);
        add(b);

        for (int i = 1; i < 10001; i++) {
            b = new JButton("Button " + i);
            add(b);
        }
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(600, 600);
        setVisible(true);
        m = System.currentTimeMillis() - m;
        System.out.println("GUI initialized in " + m + " ms");
    }

    private void disableAll() {
        long m = System.currentTimeMillis();
        System.out.println("Disabling");
        for (Component c : getContentPane().getComponents()) {
            c.setEnabled(false);
        }

        m = System.currentTimeMillis() - m;
        System.out.println("Disabled in " + m + " ms");
    }

    private void enableAll() {
        long m = System.currentTimeMillis();
        System.out.println("Enabling");
        for (Component c : getContentPane().getComponents()) {
            c.setEnabled(true);
            invalidate();
        }
        m = System.currentTimeMillis() - m;
        System.out.println("Enabled in " + m + " ms");
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                System.out.println(System.getProperty("java.vendor") + " "
                        + System.getProperty("java.version"));
                new TestGUI();
            }
        });
    }
}

4
这不是衡量Java应用程序性能的正确方法。目前,您包括仅发生一次的类加载时间成本。使用JMH库进行微基准测试。 - Vic
4
没错,你说得对,这不是基准测试。但是作为用户,我可以感受到速度慢。正如你在Java 1.8中看到的,禁用需要6341毫秒,而启用仅需要370毫秒。他们都使用同一个方法setEnable(),为什么会出现这种情况呢? - Mithat Bozkurt
2
6秒钟是很长的时间 - 如果您运行几次,是否会得到类似的数字? - assylias
3
@Vic 我同意mbsau的一点。你可以拥有世界上所有的指标,但归根结底,你的系统只有用户认为它是好的才是真正好的。 - hfontanez
4
个人简介。 - Stephen C
显示剩余5条评论
2个回答

26
根据我的分析,该操作大部分时间花费在方法Thread.holdsLock中,这是一项确实代价高昂的操作,被Component.checkTreeLock调用,而后者又被Component.updateCursorImmediately间接调用。

通常情况下,在更新多个组件时,您可以通过在操作前调用getContentPane().setVisible(false);,然后在操作后调用getContentPane().setVisible(true);来避免昂贵的可视化更新。
private void disableAll() {
    long m = System.currentTimeMillis();
    System.out.println("Disabling");
    getContentPane().setVisible(false);
    for (Component c : getContentPane().getComponents()) {
        c.setEnabled(false);
    }
    getContentPane().setVisible(true);

    m = System.currentTimeMillis() - m;
    System.out.println("Disabled in " + m + " ms");
}

无论是哪种视觉更新导致的问题,这些问题都将消失。

因此,在这里您不需要考虑如何正确进行基准测试,尽管当操作只需几秒钟时并没有太大关系,但我建议学习System.currentTimeMillis()System.nanoTime()之间的区别,因为后者是测量经过时间的正确工具。


谢谢Holger。在我们的应用程序中,视图中没有像上面代码示例中的组件数量。这个小代码片段重现了我们的GUI性能问题。 我们实际的问题是,当组件(或框架)在从服务器获取要显示的数据后更新自身时。数据通过异步方式从服务器获取。也许在帧初始化时会有多个数据获取请求发送到服务器。因此,在这些情况下,没有确定的点可以在getContentPane().setVisible()方法中使用。我真的需要改变代码吗?因为它在Java 1.6中运行良好。 - Mithat Bozkurt
5
您可以向Oracle提交一个关于性能问题的错误报告。但即使下一个版本中修复了该问题,您也需要决定如何处理使用受影响版本的用户。对我来说,更新后将之前简单的操作变成冗长操作并不是第一次出现这种情况,即使是小型更新也可能会发生。因此,坏消息是GUI编程是解决问题的过程。我不了解您的应用程序,无法给出更具体的建议。但如果您收到异步事件,就必须在某个时候将一些动作放在EDT上,以便您可以掌控它。 - Holger
3
  1. 从Sun被Oracle收购以来,AWT/Swing被忽略了。
  2. 图形算法和渲染逻辑是史前级别的。
  3. 核心已经针对今天的JavaFX进行了优化,与今天的渲染逻辑接近,并且具有相当好的GPU最优化(在所有情况下比使用AWT/Swing API更好)。
  4. 这里有一些半错误,对于旧API的部分缺失优化。
- mKorbel
5
@mKorbel:我们曾经尝试使用JavaFX处理一些实际任务,但测试之后就放弃了。我不理解为什么这些开发人员喜欢那个在Java3D和Java Media Framework中已经失败的场景图概念。问题不在于JavaFX在关键部分比AWT/Swing有更多的错误,而是你甚至无法绕过这些错误,因为你不能执行FX开发人员未预见到的任何操作(而且显然他们有非常狭隘的视野)。好吧,如果你喜欢使用CSS美化不工作的用户界面,那就去吧... - Holger
@Holger 有趣的事实和好观点(瘦客户端不需要漂亮但无用的UI外观,大多数情况下对键盘和鼠标事件都是盲目的,“基于任何东西”),无论如何感谢。 - mKorbel

5

我还没有完全理解,但似乎在8中事件处理可能已经发生了变化。首先禁用父元素可以加快处理速度:

getContentPane().setEnabled(false);
for (Component c : getContentPane().getComponents()) {
    c.setEnabled(false);
}
getContentPane().setEnabled(true);

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