United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-5085626 : Exponential performance regression in AWT components (multiple monitors)

Details
Type:
Bug
Submit Date:
2004-08-11
Status:
Resolved
Updated Date:
2008-11-05
Project Name:
JDK
Resolved Date:
2004-09-20
Component:
client-libs
OS:
windows_xp
Sub-Component:
java.awt
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
1.4.2
Fixed Versions:
1.4.2_06 (06)

Related Reports
Backport:
Backport:
Backport:
Relates:
Relates:

Sub Tasks

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


                                     
2004-10-02
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
                                     
2004-08-23
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
                                     
2004-08-23



Hardware and Software, Engineered to Work Together