JDK-5085626 : Exponential performance regression in AWT components (multiple monitors)
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.4.2
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2004-08-11
  • Updated: 2008-11-05
  • Resolved: 2004-09-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other JDK 6
1.4.2_06 06Fixed 1.4.2_07Fixed 6Fixed
Related Reports
Relates :  
Relates :  
Description
Name: rmT116609			Date: 08/11/2004


FULL PRODUCT VERSION :
java version "1.5.0-beta2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta2-b51)
Java HotSpot(TM) Client VM (build 1.5.0-beta2-b51, mixed mode, sharing)

*BUT*

This occurs for any JDK 1.4+

ADDITIONAL OS VERSION INFORMATION :
Windows XP
Windows (Any)

EXTRA RELEVANT SYSTEM CONFIGURATION :
My video setup is two NEC MultiSync LCD 1980SX monitors using
an NVIDIA Quadro NVS graphics card, though this problem would occur in any multi monitor environment.

A DESCRIPTION OF THE PROBLEM :
import java.awt.Frame;
import java.awt.Label;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Button;
import java.awt.BorderLayout;
import java.awt.Panel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * Instructions:
 *
 * The following snippet demonstrates a severe performance regression in AWT
 * starting in 1.4 (merlin), and still present in the current version of the JDK
 * (tiger beta 2).  In a multimonitor Windows XP environment set up as a single
 * display area (my video setup is two NEC MultiSync LCD 1980SX monitors using
 * an NVIDIA Quadro NVS graphics card), take the window and drag it rapidly
 * between the two monitors.  In the AWT program, the event dispatch thread is
 * frozen while all of the surface datas are being recreated in the hierarchy
 * (in Win32SurfaceData.initOps).  Note that the Swing program does not exhibit
 * the behavior because it does not have native peers.
 */
public class Test {

    private static final int NESTED_PANELS = 25;

    // This one is very slow!
    public static void testAWT() {
        Frame frame = new Frame("AWT Test");
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent ev) {
                System.exit(0);
            }
        });
        frame.setLayout(new BorderLayout());
        Label label = new Label("Hello world");
        frame.add(label, BorderLayout.NORTH);
        Panel panel = new Panel(new BorderLayout());
        Panel currentPanel = panel;
        for (int i = 0; i < NESTED_PANELS; i++) {
            Panel newPanel = new Panel(new BorderLayout());
            currentPanel.add(newPanel, BorderLayout.CENTER);
            currentPanel = newPanel;
        }
        currentPanel.add(new Label("Test"));
        frame.add(panel, BorderLayout.CENTER);
        Button btn = new Button("OK");
        frame.add(btn, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    // This one works swell!
    public static void testSwing() {
        JFrame frame = new JFrame("Swing Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());
        JLabel label = new JLabel("Hello world");
        frame.getContentPane().add(label, BorderLayout.NORTH);
        JPanel panel = new JPanel(new BorderLayout());
        JPanel currentPanel = panel;
        for (int i = 0; i < NESTED_PANELS; i++) {
            JPanel newPanel = new JPanel(new BorderLayout());
            currentPanel.add(newPanel, BorderLayout.CENTER);
            currentPanel = newPanel;
        }
        currentPanel.add(new Label("Test"));
        frame.getContentPane().add(panel, BorderLayout.CENTER);
        Button btn = new Button("OK");
        frame.getContentPane().add(btn, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        testAWT();
        // testSwing();
    }
}


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See the above code snippet documentation.  Drag the window from one monitor to the other.  Try to press the button.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
See the Swing version for bug-free behavior.
ACTUAL -
Exponential performance degredation depending on number of levels in the component hierarchy.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Stuck in Win32SurfaceData.initOps for all of the components while it exponentially goes through the hierarchy.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.Frame;
import java.awt.Label;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Button;
import java.awt.BorderLayout;
import java.awt.Panel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * Instructions:
 *
 * The following snippet demonstrates a severe performance regression in AWT
 * starting in 1.4 (merlin), and still present in the current version of the JDK
 * (tiger beta 2).  In a multimonitor Windows XP environment set up as a single
 * display area (my video setup is two NEC MultiSync LCD 1980SX monitors using
 * an NVIDIA Quadro NVS graphics card), take the window and drag it rapidly
 * between the two monitors.  In the AWT program, the event dispatch thread is
 * frozen while all of the surface datas are being recreated in the hierarchy
 * (in Win32SurfaceData.initOps).  Note that the Swing program does not exhibit
 * the behavior because it does not have native peers.
 */
public class Test {

    private static final int NESTED_PANELS = 25;

    // This one is very slow!
    public static void testAWT() {
        Frame frame = new Frame("AWT Test");
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent ev) {
                System.exit(0);
            }
        });
        frame.setLayout(new BorderLayout());
        Label label = new Label("Hello world");
        frame.add(label, BorderLayout.NORTH);
        Panel panel = new Panel(new BorderLayout());
        Panel currentPanel = panel;
        for (int i = 0; i < NESTED_PANELS; i++) {
            Panel newPanel = new Panel(new BorderLayout());
            currentPanel.add(newPanel, BorderLayout.CENTER);
            currentPanel = newPanel;
        }
        currentPanel.add(new Label("Test"));
        frame.add(panel, BorderLayout.CENTER);
        Button btn = new Button("OK");
        frame.add(btn, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    // This one works swell!
    public static void testSwing() {
        JFrame frame = new JFrame("Swing Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());
        JLabel label = new JLabel("Hello world");
        frame.getContentPane().add(label, BorderLayout.NORTH);
        JPanel panel = new JPanel(new BorderLayout());
        JPanel currentPanel = panel;
        for (int i = 0; i < NESTED_PANELS; i++) {
            JPanel newPanel = new JPanel(new BorderLayout());
            currentPanel.add(newPanel, BorderLayout.CENTER);
            currentPanel = newPanel;
        }
        currentPanel.add(new Label("Test"));
        frame.getContentPane().add(panel, BorderLayout.CENTER);
        Button btn = new Button("OK");
        frame.getContentPane().add(btn, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        testAWT();
        // testSwing();
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The fix (WPanelPeer.java) :

private void recursiveDisplayChanged(Component component)
    {
        if(component instanceof Container)
        {
            Component acomponent[] = ((Container)component).getComponents();
            for(int i = 0; i < acomponent.length; i++)
                recursiveDisplayChanged(acomponent[i]);
         }
         
        java.awt.peer.ComponentPeer componentpeer = component.getPeer();
        //sanjiv: added this if condition so that WPanelPeer displayChanged() is not called        	//which will again call this method
       System.out.println("Component : " + component.getName());
        if(componentpeer != null && (componentpeer instanceof WPanelPeer)){
         super.displayChanged();
        }
        else
        if(componentpeer != null && (componentpeer instanceof WComponentPeer))
        {
            WComponentPeer wcomponentpeer = (WComponentPeer)componentpeer;
            wcomponentpeer.displayChanged();
        }
    }
(Incident Review ID: 296521) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.4.2_06 1.4.2_07 1.5.0_01 mustang FIXED IN: 1.4.2_06 1.4.2_07 1.5.0_01 mustang INTEGRATED IN: 1.4.2_06 1.4.2_07 1.5.0_01
02-10-2004

EVALUATION Name: dm97671 Date: 08/11/2004 ###@###.### reproduced this. The fix looks from the first sight. Customer request, will be escalated, should fix in Mustang as soon as possible. ###@###.### 2004-08-11 ====================================================================== I've hand-traced a simple, 3-panel example. Consider Panel1, which contains Panel2, which in turn contains panel 3: Panel1 | Panel2 | Panel3 Panel1.displayChanged() would look like this: Panel1.displayChanged() | +-Panel1.super.displayChanged() +-Panel1.recursiveDisplayChanged(Panel2) // Recurse Panel2's children | +-Panel1.recursiveDisplayChanged(Panel3) | +-Panel3.displayChanged() | +-Panel3.super.displayChanged() | +-Panel2.displayChanged() | +-Panel2.super.displayChanged() +-Panel2.recursiveDisplayChanged(Panel3) // RecursePanel2's children again +-Panel3.displayChanged() +-Panel3.super.displayChanged() I haven't traced a more complicated example, but I can see how we could rack up a lot of extra calls with a moderately complicated GUI. The suggested fix does fix this bug, though all calls to recursiveDisplayChanged() use the top-level Panel as the context object. The trace of the 3 Panel example would look like this: Panel1.displayChanged() | +-Panel1.super.displayChanged() +-Panel1.recursiveDisplayChanged(Panel2) // Recurse Panel2's children | +-Panel1.recursiveDisplayChanged(Panel3) | +-Panel3.displayChanged() | +-Panel3.super.displayChanged() | +-Panel1.super.displayChanged() and Panel2.super.displayChanged() is not called. Although this shouldn't cause problems at present, I fear it could lead to some subtle, hidden bug in the future. The high-level problem is that we're calling recursiveDisplayChanged() on WPanelPeers from two places: from inside recursiveDisplayChanged() itself, and also from displayChanged(). The suggested fix eliminates the call made in displayChanged(). We could, instead, avoid calling WPanelPeer.recursiveDisplayChanged() from inside recursiveDisplayChanged() (which admittedly renders the name "recursiveDisplayChanged" yet more confusing). That fix would look something like this: --- private void recursiveDisplayChanged(Component c) { + ComponentPeer peer = c.getPeer(); ! if (c instanceof Container && !(peer instanceof WPanelPeer)) { Component children[] = ((Container)c).getComponents(); for (int i = 0; i < children.length; ++i) { recursiveDisplayChanged(children[i]); } } - if (peer != null && peer instanceof WComponentPeer) { WComponentPeer wPeer = (WComponentPeer)peer; wPeer.displayChanged(); } } --- The new call trace for my 3-Panel example would look like: Panel1.displayChanged() | +-Panel1.super.displayChanged() +-Panel1.recursiveDisplayChanged(Panel2) | +-Panel2.displayChanged() | +-Panel2.super.displayChanged(); +-Panel2.recursiveDisplayChanged(Panel3) | +-Panel3.displayChanged() | +-Panel3.super.displayChanged() I think this is the behavior we want, though it sure doesn't do much for code readability. =\ Hopefully some well-place comments will engender understanding for WPanelPeer's posterity. ###@###.### 2004-08-23
23-08-2004

SUGGESTED FIX ------- WPanelPeer.java ------- *** /tmp/sccs.lOai75 Mon Aug 23 17:37:45 2004 --- WPanelPeer.java Thu Aug 19 13:19:59 2004 *************** *** 95,107 **** * receive the message. */ private void recursiveDisplayChanged(Component c) { ! if (c instanceof Container) { Component children[] = ((Container)c).getComponents(); for (int i = 0; i < children.length; ++i) { recursiveDisplayChanged(children[i]); } } - ComponentPeer peer = c.getPeer(); if (peer != null && peer instanceof WComponentPeer) { WComponentPeer wPeer = (WComponentPeer)peer; wPeer.displayChanged(); --- 95,130 ---- * receive the message. */ private void recursiveDisplayChanged(Component c) { ! // 5085626: ! // recursiveDisplayChanged() was being called multiple times for ! // WPanelPeers. It would be called by the panelPeer's parent from ! // WPanelPeer.displayChanged() (which would recurse the panelPeer's ! // children), and then again during the panelPeer's call to ! // displayChanged() (recursing the panelPeer's children again): ! // ! // parentPanelPeer.displayChanged() ! // +-parentPanelPeer.recursiveDisplayChanged(panelPeer's target) ! // +-recursiveDisplayChanged(panelPeer's target's children) ! // +-panelPeer.displayChanged() ! // +-recursiveDisplayChanged(panelPeer's target children) *again!* ! // ! // The fix is to only allow WPanelPeers to recurse their children from ! // displayChanged(). Though this means that recursiveDisplayChanged() ! // is not truly "recursive", it does ensure that displayChanged() is ! // called for all WPanelPeers. ! // ! // Note that it is important to recurse through the Component hierarchy ! // and not just throught the peer hierarchy. This is to ensure that ! // heavyweights buried inside lightweight containers (which don't have ! // WPanelPeers) are updated after a display change. See also 4452373. ! ! ComponentPeer peer = c.getPeer(); ! if (c instanceof Container && !(peer instanceof WPanelPeer)) { Component children[] = ((Container)c).getComponents(); for (int i = 0; i < children.length; ++i) { recursiveDisplayChanged(children[i]); } } if (peer != null && peer instanceof WComponentPeer) { WComponentPeer wPeer = (WComponentPeer)peer; wPeer.displayChanged(); ###@###.### 2004-08-23
23-08-2004