Java Forum / GUI / December 2005
efficiency of JList setElementAt()
Raymond Cruz - 19 Dec 2005 22:49 GMT I have an application that displays about 130 text lines in a scrollable JList via the DefaultListModel. Approximately 1 entry is modified each second which is done by determining the position of the item and invoking setElementAt of the DefaultListModel object. My Athlon XP-1800+ machine consumes 31% of the system CPU when these operations occur but if I execute all the program logic with the single exception of the setElementAt call, it consumes only 3% of the CPU. Is it reasonable that modifying one element of a list once a second would consume so much CPU? Also, the modification time increases approximately linearly with list size. If I make the list 1/2 the size about 19% of the CPU is consumed.
It seems that if my list size grows, frequency of updates increases, or if I operate more than a single list at a time (all of which are planned), I will drag even a quite fast machine to its knees. Are there more efficient alternatives available? The target machine is Windows, JRE 1.4.2_08 and the development environment is Eclipse 3.1.
RC
Thomas Hawtin - 19 Dec 2005 23:23 GMT > I have an application that displays about 130 text lines in a scrollable > JList via the DefaultListModel. Approximately 1 entry is modified each [quoted text clipped - 12 lines] > alternatives available? The target machine is Windows, JRE 1.4.2_08 and the > development environment is Eclipse 3.1. PL&F revalidates and repaints the entire list if any of it changes. Fortunately, there should only be so much on the screen that can be revalidate and repainted. However, the quoted times do seem excessive.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Raymond Cruz - 19 Dec 2005 23:27 GMT > > I have an application that displays about 130 text lines in a scrollable > > JList via the DefaultListModel. Approximately 1 entry is modified each [quoted text clipped - 18 lines] > > Tom Hawtin The problem isn't very sensitive to how much is on the screen. For the examples given, approximately 75% of the list was outside of the scroller's viewport. In fact, if the frame that displays the JList is minimized to a null display, the timing improves by only about 15%.
RC
Thomas Hawtin - 20 Dec 2005 00:01 GMT > The problem isn't very sensitive to how much is on the screen. For the > examples given, approximately 75% of the list was outside of the scroller's > viewport. In fact, if the frame that displays the JList is minimized to a > null display, the timing improves by only about 15%. You probably need to use a profiler on it (or do a ctrl-\ or ctrl-break on it randomly from the console). Possibly it is something to do with validating the layout, the model/list elements might be slow or perhaps you are extremely short on video RAM.
I wrote a little benchmark. On 266 MHz PII it settled down to 3-3.5% CPU. On my 2.26ish GHz Celeron, it came in comfortably under 1%.
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*;
class ListSpeed { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { swing(); } }); } private static final java.util.Random random = new java.util.Random(); private static void swing() { assert java.awt.EventQueue.isDispatchThread(); JFrame frame = new JFrame("ListSpeed"); final DefaultListModel model = new DefaultListModel(); for (int ct=0; ct<50; ++ct) { model.addElement("some data "+new java.util.Date()); } final JList list = new JList(model); frame.add(list);//new JScrollPane(list));// frame.pack(); frame.setVisible(true); new javax.swing.Timer(1000 /* millis */, new ActionListener() { private boolean set; public void actionPerformed(ActionEvent event) { model.setElementAt( "new data "+new java.util.Date(), random.nextInt(model.size()) ); } }).start(); } }
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Raymond Cruz - 20 Dec 2005 00:58 GMT Thanks Tom. I agree with your results when I run your program so that made me immediately consider something that I hadn't considered important before. My list objects are HTML strings solely for the purpose of highlighting a few words in a different color. This appears to make the list update about an order of magnitude more costly! If you modify your program to produce strings about 3 times as long, make the strings HTML with a font color tag, and increase the list size to about 130, I think you'll get the kind of results I cited in my first post. (Note: by virtue of the fact that your program did not compile with my 1.4.2 JDK, I suspect you are using an earlier Java version and I know that HTML support has been introduced over time so I'm not sure if you'll be able to use HTML strings).
So I have a new question -- is there a way to produce different font colors that is much more efficient than using HTML strings in Java?
Thanks again for your persistence.
RC
> > The problem isn't very sensitive to how much is on the screen. For the > > examples given, approximately 75% of the list was outside of the scroller's [quoted text clipped - 47 lines] > > Tom Hawtin Thomas Hawtin - 20 Dec 2005 01:26 GMT > results I cited in my first post. (Note: by virtue of the fact that your > program did not compile with my 1.4.2 JDK, I suspect you are using an > earlier Java version and I know that HTML support has been introduced over > time so I'm not sure if you'll be able to use HTML strings). Later version. It was only the assert that trips up 1.4 with default options. If you add -source 1.4 (which should have been the default if Sun were awake), then it will compile.
> So I have a new question -- is there a way to produce different font colors > that is much more efficient than using HTML strings in Java? A ListCellRenderer is what you want. It's pretty easy to modify the DefaultListCellRenderer to change colour of the whole text. To just do part of the text, you will need something a little more sophisticated. Instead of Strings in the ListModel, use a custom class describing the required display. The ListCellRenderer can then cast the cell data back and paint something appropriate. I suggest keeping the renderer as simple as possible, pushing as much logic back to the cell data model as possible.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Roedy Green - 20 Dec 2005 05:08 GMT >So I have a new question -- is there a way to produce different font colors >that is much more efficient than using HTML strings in Java? For JTree you write customCellRenderers. That should be many times faster than using HTML. Presumably there is a similar feature for your component.
The key is a method something like this:
/** * Returns the component used for drawing the cell. This method is * used to configure the renderer appropriately before drawing. * Associated TableModel must implement DangerTableModel. * * @param table the <code>JTable</code> that is asking the * renderer to draw; can be <code>null</code> * @param value the value of the cell to be rendered. It is * up to the specific renderer to interpret * and draw the value. For example, if * <code>value</code> * is the string "true", it could be rendered as a * string or it could be rendered as a check * box that is checked. <code>null</code> is a * valid value * @param isSelected true if the cell is to be rendered with the * selection highlighted; otherwise false * @param hasFocus if true, render cell appropriately. For * example, put a special border on the cell, if * the cell can be edited, render in the color used * to indicate editing * @param row the row index of the cell being drawn. When * drawing the header, the value of * <code>row</code> is -1 * @param column the column index of the cell being drawn */ public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component cell = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column);
int dangerLevel = ((DangerTableModel)(table.getModel())).getDangerLevel( row );
// normal cell.setForeground( winAttrScheme.getForeground( dangerLevel ) ); cell.setBackground( winAttrScheme.getBackground( dangerLevel ) );
cell.setFont( winAttrScheme.getFont ( dangerLevel ) ); return cell; }
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Thomas Hawtin - 22 Dec 2005 01:50 GMT > Thanks Tom. I agree with your results when I run your program so that made > me immediately consider something that I hadn't considered important before. [quoted text clipped - 10 lines] > So I have a new question -- is there a way to produce different font colors > that is much more efficient than using HTML strings in Java? It occurred to me that you can keep your HTML and make it go at a reasonable speed (unless you run low on memory). The Swing cell renderer design is based on assumptions that construction is expensive and updating values is cheap. Clearly, even for the humble JLabel, the second is not correct when the value is HTML. So let's cache.
The approach I have taken is to have a renderer lazily create a conventional renderer for each index. The renderers are softly referenced, in a naive manner. There are lots of variations with different trade-offs. This seems to work quite well for me. It is worth noting that the classical approach is probably going to be faster to show the initial screen. And my class name is too cute for production use.
The only necessary addition to original program is:
list.setCellRenderer(new DaisyListCellRenderer());
I think I shall add an improved version to my weblog.
Tom Hawtin
(Usual disclaimer.)
import java.lang.ref.Reference; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collections; import java.util.List; import java.util.Map; import javax.swing.*;
class DaisyListCellRenderer implements ListCellRenderer { private final Map< JList,List<Reference<ListCellRenderer>> > caches = new java.util.WeakHashMap< JList,List<Reference<ListCellRenderer>> >(); public java.awt.Component getListCellRendererComponent( javax.swing.JList list, Object value, int index, boolean isSelected, boolean cellHasFocus ) { // Find cache for this list. List<Reference<ListCellRenderer>> cache = caches.get(list); if (cache == null) { cache = new java.util.ArrayList<Reference<ListCellRenderer>>(); caches.put(list, cache); }
// Cache list may need extending. int shortfall = index+1 - cache.size(); if (shortfall > 0) { assert cache.size()+shortfall == index+1; cache.addAll( Collections.<Reference<ListCellRenderer>>nCopies( shortfall, null ) ); }
// Find renderer. Reference<ListCellRenderer> rendererRef = cache.get(index); ListCellRenderer renderer = rendererRef==null ? null : rendererRef.get(); if (renderer == null) { renderer = createRenderer(/*...*/); cache.set( index, new java.lang.ref.SoftReference<ListCellRenderer>( renderer ) ); }
// Forward. return renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); } private ListCellRenderer createRenderer(/*...*/) { return new javax.swing.DefaultListCellRenderer(); } } class LighstSpeed { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { swing(); } }); } private static final java.util.Random random = new java.util.Random(); private static void swing() { assert java.awt.EventQueue.isDispatchThread(); JFrame frame = new JFrame("LighstSpeed"); final DefaultListModel model = new DefaultListModel(); for (int ct=0; ct<50; ++ct) { model.addElement("<html>some data "+new java.util.Date()); } final JList list = new JList(model); list.setCellRenderer(new DaisyListCellRenderer()); frame.add(list);//new JScrollPane(list));// frame.pack(); frame.setVisible(true); new javax.swing.Timer(1000, new ActionListener() { private boolean set; public void actionPerformed(ActionEvent event) { model.setElementAt( "<html>new data "+new java.util.Date(), random.nextInt(model.size()) ); } }).start(); } }
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Raymond Cruz - 22 Dec 2005 17:11 GMT I wrote a cell renderer tailored to my application and it has resulted in a performance improvement by at least a factor of 5. I include it below in case it might be useful to anyone and for a critique if anyone is so motivated since I don't consider myself to be much more than an advanced beginner in Java.
However I still have performance questions and hopes for better performance. The construction of my list is incremental since it builds as messages arrive. However after some number of minutes it reaches steady state in size and order and afterwards single cells are modified in place with new data. My hope and expectation is that only a single cell would be invalidated and re-rendered in this operation but it appears that either the entire list is invalidated or all cells from the changed cell to the end of the list are invalidated. I base this conclusion on the fact that the performance penalty for my updates increases fairly linearly with list size.
Is it possible to achieve constant update time for such a list? What would one need to do to achieve that goal?
RC
/* * This class is a cell renderer that supports the capability of * highlighting substrings in a JList of strings with a different * color. This capability and more can be achieved by using HTML * with a much higher performance penalty. Initially table entry * color is black but it can be changed within the value string by * a substring consisting of "\u00A7RRGGBB" where '\u00A7 is the * unicode for a "section marker" and RRGGBB are the hexadecimal * values for the red, green, and blue color components. */
import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.FontMetrics; import javax.swing.JPanel; import javax.swing.JList; import javax.swing.ListCellRenderer;
public class ColorCellRenderer extends JPanel implements ListCellRenderer {
private static final long serialVersionUID = 1L; private static final String DELIMITER = "\u00A7"; private JList list; private String cv; private int fontSize; private boolean selected; private int maxWidth = 0;
// default constructor, 12 point font public ColorCellRenderer() { this.fontSize = 12; }
// this constructor allows font point size to be specified public ColorCellRenderer(int fontSize) { this.fontSize = fontSize; }
// implement the abstract method of the interface public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { this.list = list; selected = isSelected; cv = value.toString(); return this; }
// override of paint() to render the cell. interpret substrings // that specify font color changes and paint the background of the // selected cell. public void paint(java.awt.Graphics g) { int text_x = 0; int text_y; boolean colorChange = false;
g.setFont(new Font("Arial", Font.PLAIN, fontSize)); FontMetrics fm = g.getFontMetrics(); int fontHeight = fm.getHeight();
if(selected) { g.setColor(list.getSelectionBackground()); g.fillRect(0, 0, this.getWidth(), this.getHeight()); } g.setColor(Color.black); text_y = fontHeight-2; java.util.StringTokenizer st = new java.util.StringTokenizer(cv, DELIMITER, true); while(st.hasMoreTokens()) { String s = st.nextToken(); if(colorChange) { colorChange = false; if(s.length() >= 6) { int newColor; String colorString = s.substring(0, 6); try { newColor = Integer.parseInt(colorString, 16); } catch(Exception e) {newColor = 0;} g.setColor(new Color(newColor)); s = s.substring(6); } } if(s.equals(DELIMITER)) { colorChange = true; } else { g.drawString(s, text_x, text_y); text_x += fm.stringWidth(s); } } if(text_x > maxWidth) maxWidth = text_x; setPreferredSize(new java.awt.Dimension(maxWidth, fontHeight+1)); g.dispose(); } }
> > <snip> > > So I have a new question -- is there a way to produce different font colors [quoted text clipped - 8 lines] > The approach I have taken is to have a renderer lazily create a > conventional renderer for each index. <snip> Thomas Hawtin - 22 Dec 2005 18:42 GMT > However I still have performance questions and hopes for better performance. > The construction of my list is incremental since it builds as messages [quoted text clipped - 5 lines] > the list are invalidated. I base this conclusion on the fact that the > performance penalty for my updates increases fairly linearly with list size. Telling the graphics card to go off and paint some strings shouldn't be the taxing part of the operation.
> Is it possible to achieve constant update time for such a list? What would > one need to do to achieve that goal? The Basic PL&F (javax.swing.plaf.basic.BasicListUI) always repaints the entire control. I guess you could arrange for it to be double buffered, but I doubt that would be a sensible use of effort.
> public class ColorCellRenderer extends JPanel implements ListCellRenderer { You might as well extend JComponent (the idea that JPanel is automatically opaque is untrue in general).
> [...] > public void paint(java.awt.Graphics g) { Probably should be paintComponent, I think.
> int text_x = 0; > int text_y; > boolean colorChange = false; It is generally better to move declaration down to where they are used.
> g.setFont(new Font("Arial", Font.PLAIN, fontSize)); Creating a Font can be expensive, depending on platforms. The two obvious options are caching and put the Font within the value object.
Renderers work out better if they are as thin as possible, with the real meat down in the model side of things. So if the values from the model already parsed the string and had Fonts and Colors in place, it'd probably be faster, more understandable, more testable, etc.
> [...] > try { > newColor = Integer.parseInt(colorString, 16); > } catch(Exception e) {newColor = 0;} You can be a bit more specific about which exception you want to catch.
> g.setColor(new Color(newColor)); > s = s.substring(6); [quoted text clipped - 10 lines] > maxWidth = text_x; > setPreferredSize(new java.awt.Dimension(maxWidth, fontHeight+1)); It's not a good idea to mutate a component within paint. This should be done in getListCellRendererComponent. You might get away with it painting over again, but it isn't fast or pretty.
> g.dispose(); It wasn't your Graphics object, so don't dispose it.
> } > } Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Roedy Green - 20 Dec 2005 05:03 GMT >I have an application that displays about 130 text lines in a scrollable >JList via the DefaultListModel. Approximately 1 entry is modified each [quoted text clipped - 4 lines] >consumes only 3% of the CPU. Is it reasonable that modifying one element of >a list once a second would consume so much CPU? look at the code for DefaultListModel.setElementAt public void setElementAt(Object obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index);
delegate is an ordinary Vector. The problem coming from the busywork inspired by fireContentsChanged. Just what are you doing to render that row?
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|