Skip to main content

Efficiency of JXTable sorting

38 replies [Last post]
osbald
Offline
Joined: 2003-06-13

Just getting some of the real client data though my ui and I've got a few scalability issues. One of these is sorted JXTables and JXLists seem to feel a bit sluggish when you put 160,000 rows+ into them (who'd have thought).

Aside from the obvious "why are you wasting your time trying to show so many rows to the user?". Can anything be done performance-wise. Even changing the sort column is causing the old grey "come back later" freeze - it isn't obvious if/how I can do the sorting off the EDT or even if it'd be safe to do so.

Is this a "..when we align SwingX with Mustang type thing"?

- Richard

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
osbald
Offline
Joined: 2003-06-13

It'll need some experimentation to work out which of these techniques benefit JXTables Sorting & Filtering the most. Esp check the sort indexes aren't being reset on every insert. Or else maintaining the table models row order so it closely matches that of the views active sorter may perform better (choose the most common ordering).

The problem is there's not a lot of enthusiasm for doing this with the existing code as it's intended to be replaced post 1.0 (when we switch over to the 6.0 models). Deeper tips can be gleaned from Scott Violets Xmas Tree article: http://java.sun.com/products/jfc/tsc/articles/ChristmasTree/ (take care to read all the details and caveats).

I'd probably not intern all Strings, but booleans and known enum like values might be good candidates.

Oh using beansbindings (rather than a tablemodel) would probably be a bad idea too.

But the biggest speed and usability increases will still be to keep those tables skinny when at all possible.

baileys
Offline
Joined: 2005-10-11

>
> Oh using beansbindings (rather than a tablemodel)
> would probably be a bad idea too.
>

pity for me :(
I do use beansbinding (an ObservableList) without tablemodel.
Maybe I'll take some time later on to change my code.

aberrant
Offline
Joined: 2006-02-02

I've put 100,000 rows in a plain JTable with a sorter (TableSorter pre 1.6) and had good performance. This was a telecommunications management system and we had data coming much faster then once every 3 seconds (in our scalability tests). In general I've found the following techniques helpful.

1. Intern all strings. (http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html#intern())
If you are reading your data form some outside source it's a good idea to intern the strings when you plan on doing a lot of filtering. This will speed up any filtering algorithm as it ensures that == works for any two interned strings of the same content. It also has the added benefit of reducing memory usage by discarding duplicate strings. As an example a column that can be "yes" or "no" only needs 2 string regardless of the number of rows. Reading from an external source you may get 100,000 copies of "yes".

2. Write your own table model.
I would avoid default table model. Most of the time it just adds overhead by requiring you to create a new Vector for each row. I find table models based on a single ArrayList of a business logic class scale better.

3. Batch updates if you can.
Every time insertRow() is called on DefaultTabelModel it fires a fireTableRowsInserted(row, row) event. Hence if you sit in a loop and call insertRow() 100 times you generate 100 events. This was just fine before sorting as the table just called repaint() and that gets manager by the repaint manager (which ignores most of them). With sorting and filtering the sorter + filterer gets 100 updates and so it resorts/refilters 100 times. A custom table model could provide a mechanism for adding a collections of objects and fire just one fireTableRowsInserted(row, row) event. This means you could add 10,000 records and only sort/filter once.

4. Finding an item is hard.
If you need to update or remove a specific item in a table then finding that item quickly becomes really important. When your backing data stricture is a List (Vector, ArrayList) then you are looking at looping through every item until you find the one you are looking for. If I have 90,000 rows and need to update 5 items the worst case is ~450,000 iterations. Using a secondary data structure like a set or map can reduce the search time greatly. The cost is some more memory and some added work in your custom table model to keep both the list and set set/map in sync.

Why did I need 100,000 rows in a table? Because the customer demanded it.

Hope this helps.

Collin

ullenboom
Offline
Joined: 2005-09-01

You should never ever intern() Strings :-) You can of course use an own Map but not using the VM data structure for Strings. One interesting debate: http://wiki.eclipse.org/index.php/Performance_Bloopers.

Kind regards,

Christian Ullenboom | http://www.tutego.com/

aberrant
Offline
Joined: 2006-02-02

I'm sorry I see nothing specific advising against using intern() on that page. There is some reference to degrading the performance of intern with too many entries added. But, there is no data to back that up, nor reference to a specific article with that information.

I've seen first hand the benefits of intern. If someone for some reason someone didn't trust intern() then a set would also work.

This has a lot of information on intern.
http://mindprod.com/jgloss/interned.html

Message was edited by: aberrant

osbald
Offline
Joined: 2003-06-13

I did have a quick look at Sorter and even though this is kinda pointless as it not being supported I wasn't sure the sort indicies where being maintained? looked to me like refresh(true) was called a lot which reverted to the natural model order in reset()?

This wouldn't be good for updates which ought to work well with the Shuttle Sorts partial ordering behavior as it'd actually keep resorting the whole table every time? I don't have the grok the code fully now but it might be worth checking how this actually works.

It stuck me that the inital sort or on a column change ought to be a quick sort, while updates to an already mostly ordered table would benefit from the shuttle sort (although the difference from quick sort may be negelable).

osbald
Offline
Joined: 2003-06-13

Sorry I worked around the problem instead. Ultimately having 10,000 rows of anything isn't necessarily useful nor comprehensible for humans. What I did was add filters to my tables to force the users to focus into what information they're really after. In a lot of cases a graph or chart might be preferable to summarize the data trends.

I still have the issue if the users ignore my work and set all filters to *. But I think it's common sense that its not helpful and makes life really slow.

Because its EOL and the way it works as a filter that fires a TableDataChanged on sort toggles it's tricky to intercept. Suppose you could delay the change, copy the data (indicies) and sort it in the background and replace all rows once finished. But you'd need to stop any changes to the table while this was happening, or work in an interrupt to abandon and restart on any changes? (might end up starving?) . Same kinds of issue with updates and the shouldUpdateOnChange flags being discussed in another thread.

I did wonder how much implementing a new Sort might improve the performance on a large set. If I remember the default Sorter was a Shuttle Short which is pretty much an enhanced Bubble Sort. It works best for semi-ordered data, but if you change the column order on a large set you'll take a larger hit.. I think.. been a while since my algorithms 101

PS Almost ashamed to suggest this, but while the cats away.. What'd happen if you did a reverse() on the sort indicies when you do a toggle()? ah assuming you've toggled the same sorted column.

Message was edited by: osbald

Paul Taylor

So the table model still contains 10,000 records but it applies a swing
filter to limit the rows, so the sorting is ok if the no of visible rows
is low even if the total number of rows is large ?
I have the same problem, but more of a problem for me is that vertical
scrolling can be very slow with 10,000 rows, and fiddly. What I was
thnking about doing was only show 500 rows in the table at one time, as
they scrolled down the list retrive additional rows and remove the
earlier ones, then the scrollbar would be more manageable and the
application more responsive. I read an article about doing this with
decorators on vanilla swing tables, but has anyone done anything like it
with swingx tables ?

paul

jdnc-interest@javadesktop.org wrote:
> Sorry I worked around the problem instead. Ultimately having 10,000 rows of anything isn't necessarily useful nor comprehensible for humans. What I did was add filters to my tables to force the users to focus into what information they're really after. In a lot of cases a graph or chart might be preferable to summarize the data trends.
>
> I still have the issue if the users ignore my work and set all filters to *. But I think it's common sense that its not helpful and makes life really slow.
>
> Because its EOL and the way it works as a filter that fires a TableDataChanged on sort toggles it's tricky to intercept. Suppose you could delay the change, copy the data and sort it in the background and replace all rows once finished. But'd you'd need to stop any changes to the table while this was happening, or work in an interrupt to abandon and restart on any changes? (might end up starving?) . Same kinds of issue with updates and the shouldUpdateOnChange flags being discussed in another thread.
>
> I did wonder how much implementing a new Sort might improve the performance on a large set. If I remember the default Sorter was a Shuttle Short which is pretty much an enhanced Bubble Sort. It works best for semi-ordered data, but if you change the column order on a large set you'll take a larger hit.. I think.. been a while since my algorithms 101
> [Message sent by forum member 'osbald' (osbald)]
>
> http://forums.java.net/jive/thread.jspa?messageID=275945
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
> For additional commands, e-mail: jdnc-help@jdnc.dev.java.net
>
>
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

osbald
Offline
Joined: 2003-06-13

> I read an article about doing this with decorators on vanilla swing tables, but has
> anyone done anything like it with swingx tables

I don't think so but I'd like one of those too please

One of the issues here is doing the filtering and sorting on the client side wouldn't be very efficient. With lots of data you take a hit over network latency & data retrieval even assuming you've go the local memory to store them all. You won't know what your next 500 rows are until you've retrieved everything and applied the active sorts & filters. So you'd end up needing to translate parts of the tables view (filters & sorting) into the servers state, query methods and models.

The scrollbar is also tricky as it height and position is normally proportional to the number of rows and your current position in them. Which you might not necessarily know for arbitrarily long data sets (or at least in database terms perform duplicate queries with sorts & filters with with count(*)). So the thumb would need to be fixed in size and do a little bounce at the end (like on dzone, Google Reader or MSN live search results)?

You've still got the same comprehension problem though, how is a user to find the row(s) their interested in among the many thousands. What can a person sensibly do when presented with that much tabular raw data? It's a strong indication alternative visualizations or approaches may be called for.

Paul Taylor

jdnc-interest@javadesktop.org wrote:
>> I read an article about doing this with decorators on vanilla swing tables, but has
>> anyone done anything like it with swingx tables
>>
>
> I don't think so but I'd like one of those too please
>
> One of the issues here is doing the filtering and sorting on the client side wouldn't be very efficient. With lots of data you take a hit over network latency & data retrieval even assuming you've go the local memory to store them all. You won't know what your next 500 rows are until you've retrieved everything and applied the active sorts & filters. So you'd end up needing to translate parts of the tables view (filters & sorting) into the servers state, query methods and models.
>
>
There no server/client distinction in my application, actually Ive just
compared to Winamp and one simple improvement maybe that the Winamp
scrollbar size still stays about an inch wide even with thousands of
tracks whereas in swingx the width of the scrollbar gets very small.
> You've still got the same comprehension problem though, how is a user to find the row(s) their interested in among the many thousands. What can a person sensibly do when presented with that much data? It's a strong indication alternative visualizations or approaches may be called for.
> [Message sent by forum member 'osbald' (osbald)]
I dont know about your data, but in my case and I suspect other cases
the JXTreeTable might be alternative

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

osbald
Offline
Joined: 2003-06-13

>There no server/client distinction in my application, actually Ive just
>compared to Winamp and one simple improvement maybe that the Winamp
>scrollbar size still stays about an inch wide even with thousands of
>tracks whereas in swingx the width of the scrollbar gets very small.

There are other problems though. Querying the data when the user clicks or drags the scrollbar is likely to be too late. What the user needs here is instant feedback. So it's more likely you'll want to cache enough rows in the background to fulfill the user clicking up, down a row or page. The proportional click in the track too jump in 30% say might remain a problem though.

Also with lots of rows the scrollbar won't have enough pixels for pages of data. Each single pixel increment will relate to a leap of hundreds if not thousand of rows. So you won't be able to see all the data in the viewport while paging up & down (only the top-most visiblerow count for each page). To see every row you'd have to single step through which'd be really very tedious.

osbald
Offline
Joined: 2003-06-13

Those problems and JXTables sorting and filtering become useless as they'll only work on the 500 rows in your model not the whole of your actual data set.

jidesoftware
Offline
Joined: 2004-10-07

Hi James,

Actually no, JIDE SortableTable doesn't pre-index the data as you guessed. We only look at the data until toggleSortOrder method is called just like other implemention.

-David

jplemieux
Offline
Joined: 2004-11-05

David,

I'm surprised to hear that as I don't know how else to make sense of your other mysterious post which said:

"Regardless what the actual data type is, JIDE SortableTable just sorts an int array... That's also why we get the same performance result for int or String..."

If you're actually sorting ints when the real data type is String, and you're not creating an index ahead of time, then I'm at a loss for what's actually happening behind a JIDE SortableTable.

James

jidesoftware
Offline
Joined: 2004-10-07

I see. When I said "sort an int array", I simply mean that an int array is the data structure the sort algorithm will modify. When we do the sorting, we still use the int value in the array as the row index to look up in the table model to find what the real data is.

Thanks,

-David

kschaefe
Offline
Joined: 2006-06-08

So effectively, then this is a translation mechanism from the current view to the model.

Karl

jidesoftware
Offline
Joined: 2004-10-07

That's right.

We used this design a lot in JIDE Grids product. We called it TableModelWrapper. For example, SortabletableModel, FilterableTableModel and GroupTableModel are all TableModelWrappers. TableModepWrapper can wrap another TableModel or TableModelWrapper to form a pipe. It is very similar to Glazed List way of piping list to another list. Very interesting design and powerful.

Thanks,

-David

jidesoftware
Offline
Joined: 2004-10-07

Hi Karl,

I just noticed a bug in your original code (or JTable's bug) that JTable will sort by String even the data is Integer. You have to explicit sepcify the data type in getColumnClass. Once I made this change, JTable sorting ints is almost as fast as JXTable and JIDE's SortableTable.

[code]
TableModel model = new DefaultTableModel(getIntData(), names){
public Class getColumnClass(int columnIndex) {
return Integer.class;
}
};
[/code]

-David

jplemieux
Offline
Joined: 2004-11-05

Hey Karl,

I took the liberty of racing Glazed Lists against JTable and JXTable using your sample test case as a benchmark. Our code doesn't do anything like pre-index rows for each sortable column (as it appears JideSoft does). Also, Glazed Lists isn't specifically a sorting API, it's a data transformation API, so if you're doing that sort of thing, you would probably get a lot of mileage out of using it. Also, backgrounding the sort would be trivial with Glazed Lists. The numbers look like this on my box:

Sorting JTable (ints):
1223 ms
3090 ms
1853 ms
1853 ms

Sorting JXTable (ints):
141 ms
419 ms
53 ms
417 ms

Sorting JTable with Glazed Lists (ints):
681 ms
872 ms
726 ms
731 ms

Sorting JTable (strings):
527 ms
1502 ms
1533 ms
1501 ms

Sorting JXTable (strings):
527 ms
1599 ms
711 ms
1605 ms

Sorting JTable with Glazed Lists (strings):
773 ms
841 ms
839 ms
1046 ms

The takeaway is that Glazed Lists is maintaining a tree for the sorted order, so our solution will never be as fast as sorting the data in place, however, we are reasonably quick.

Here is the modified test case if you're curious about how the numbers were derived and what the code looks like:

[code]
import java.util.Random;
import java.util.Vector;

import javax.swing.JTable;

import org.jdesktop.swingx.JXTable;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventTableModel;
import ca.odell.glazedlists.swing.TableComparatorChooser;

public class JTableSortTest {
public static void main(String[] args) {
Vector names = new Vector(2);
names.add("Row");
names.add("Random");

// int tests
Vector> intData = getIntData();
JTable table = new JTable(intData, names);
table.setAutoCreateRowSorter(true);
System.err.println("Sorting JTable (ints):");
testJTable(table, 4);

JXTable xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (ints):");
testJXTable(xTable, 4);

EventList> intDataEventList = GlazedLists.eventList(intData);
SortedList> sortedIntDataEventList = new SortedList>(intDataEventList, null);
JTable glazedTable = new JTable(new EventTableModel>(sortedIntDataEventList, new VectorDataTableFormat()));
TableComparatorChooser tcc = TableComparatorChooser.install(glazedTable, sortedIntDataEventList, TableComparatorChooser.SINGLE_COLUMN);
System.err.println("\nSorting JTable with Glazed Lists (ints):");
testGlazedTable(tcc, 4);

// string tests
Vector> stringData = getStringData();
table = new JTable(stringData, names);
table.setAutoCreateRowSorter(true);
System.err.println("\nSorting JTable (strings):");
testJTable(table, 4);

xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (strings):");
testJXTable(xTable, 4);

EventList> stringDataEventList = GlazedLists.eventList(stringData);
SortedList> sortedStringDataEventList = new SortedList>(stringDataEventList, null);
glazedTable = new JTable(new EventTableModel>(sortedStringDataEventList, new VectorDataTableFormat()));
tcc = TableComparatorChooser.install(glazedTable, sortedStringDataEventList, TableComparatorChooser.SINGLE_COLUMN);
System.err.println("\nSorting JTable with Glazed Lists (strings):");
testGlazedTable(tcc, 4);
}

private static void testJTable(JTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.getRowSorter().toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static void testJXTable(JXTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static void testGlazedTable(TableComparatorChooser tcc, int iterations) {
for (int i = 0; i < iterations; i++) {
String sortString = i % 2 == 0 ? "column 0" : "column 0 reversed";
long time = System.currentTimeMillis();
tcc.fromString(sortString);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static Vector> getIntData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i);
row.add(r.nextInt());

data.add(row);
}

return data;
}

private static Vector> getStringData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i + "");
row.add(r.nextInt() + "");

data.add(row);
}

return data;
}

private static class VectorDataTableFormat implements TableFormat {
public int getColumnCount() {
return 2;
}

public String getColumnName(int column) {
switch (column) {
case 0: return "Row";
case 1: return "Random";
}
throw new IllegalArgumentException("Unknown column index: " + column);
}

public Object getColumnValue(Vector baseObject, int column) {
return baseObject.get(column);
}
}
}
[/code]

James

jessewilson
Offline
Joined: 2003-06-14

I did a sorting benchmark of my own a couple years ago:
http://publicobject.com/glazedlists/sortedjtables/

The most interesting thing about sorting benchmarks is that there are 2 different interesting use cases:
A - the time it takes when the user clicks on a column's header to resort the entire table
B - the time it takes when a row is inserted, updated or deleted within a sorted table

As James alluded to, Glazed Lists is particularly efficient for case B. This is what allows you to sort the table while issues are being downloaded in our issues browser demo:
https://glazedlists.dev.java.net/glazedlists-demo.jnlp

kschaefe
Offline
Joined: 2006-06-08

Hey, thanks for the input. Having never produced such a large table as Richard had, I was quite surprised by the inefficiency of the sorting. Not sure where to go with this data or what to do with it. I'd like to get some bug reports into Sun about the sorting efficiency, but I want to have a better understanding of the problem first.

Karl

osbald
Offline
Joined: 2003-06-13

Any ideas on the EDT issue? My dev box is a pretty meaty 2gigs dual core beast and I'm hitting a 5-10 sec dead spot when clicking on the table headers. Haven't profiled it at the moment, but in the debugger the server call returns pretty quickly so I know the rest of the delay is most likely to be swing related.
My row objects are pretty complex compound things - I suppose the equals() methods may get fairly involved which might be swallowing some significant cpu. I've also got a bunch of highlighters including conditional color ones on the same table (still haven't updated) - Don't know if they might introduce scalability issues of their own?

- Richard

kschaefe
Offline
Joined: 2006-06-08

Well, the Highlighter stuff is painting dependent, meaning they are only painted with the renderers, which are only painted for the visible region, so I doubt that painting or highlighters are to blame, unless the stack or renderers is really that large. My guess is that the culprit is the sorting, but I haven't had time to track this down.

Does my poor test case cover what you're experiencing? If not, can you give us a better one to use?

Also what memory arguments are you using? Are you resource starving the system in some way?

Karl

osbald
Offline
Joined: 2003-06-13

> Does my poor test case cover what you're experiencing? If not, can you give us a
> better one to use?

No, that pretty much seems to duplicate it although my real code feels worse as its doing a lot more - remote methods and more complex and often compound row/cell objects. Maybe it's just a psychological thing that I'm more sensitive to time delays in my own being-delivered-for-customer-testing-as-i-type code than throwaway demos .

> Also what memory arguments are you using? Are you resource starving the system in
> some way?

Is -Xmx512m enough? ;) Needed that as my app includes a graphical topology viewer which'll happily try to draw a graph of 20,000 entities and the various 160,000 connections between them at the moment.. Well I say happily - but obviously nothing could be further from the truth . This isn't being invoked in any other views, so its not part of the equation on the table sorting.. just similar amounts of data I'm trying to juggle across many component types.

I'm back around to thinking having many more rows (re pages) than vertical pixels in your components scrollbar isn't very sensible this morning. As you can't view/scroll all the results with the thumb due to the imprecision (at least not without going down a single line at a time).

But of course coming up with the artifical means to filter results meaningfully and uniformly without over-complicating the ui isn't always that simple either.

- Richard

baileys
Offline
Joined: 2005-10-11

Hi,

I understand that having more than xxxxx rows available to the user at one time is not very useful, that is why filters have to be applied. But let see it in a concrete way.
My financial-based application can be populated with a lot of data (say 10,000 rows) at startup, and at startup, no filters are set on my JXTable yet (three combos are available to the user so he can set filters on the table at a later time).
Every 3 seconds new data comes in, and is inserted into the table. If the user sorted the table on one column, and new data comes in, that may take some time.
Of course, if filters were set, that would take much less time, but could prevent the user to be able to see the last data that was added to the table.

How would you develop such a functionnality? Using what components?
I am really open to any suggestion.

kschaefe
Offline
Joined: 2006-06-08

What are your users looking for? What behavior are they expecting?

The simplest solution is to cache the new data in a separate location and allow the user to "refresh data" or whatever you want to call it and then add the new data to the table and sort.

Ensure that you're only making calls on the EDT that you have to be making. Install some debug code to verify your assumptions. You could create a custom sorting implementation that ran off EDT and published incremental updates to the table on the EDT. SwingWorker would be a good choice for that.

The solution space for this problem is pretty big, so it does really come down to what the user wants and/or expects.

Karl

baileys
Offline
Joined: 2005-10-11

Sorry to awake an old post, but I would like to know if there is something new about this issue?
I have a JXTable with more than 10,000 rows, that is populated with new rows every 3 seconds or so. the first sorting itself takes some time, and the insertion of a new row into an already sorted table is not very efficient.
I wonder if the JXTable is the most suitable for this kind of use.
Any comment?

kschaefe
Offline
Joined: 2006-06-08

SwingX sorting/filtering is EOL and is not maintained. When SwingX moves to Java 6, the current sorting will be removed in favor of core sorting.

Richard O. do you have any suggestions? I know you've stressed SwingX sorting.

Karl

rousseauhk
Offline
Joined: 2007-06-11

I'm hitting the same problem - but with as few as ~300 records where new records are inserted into a sorted JXTable every few seconds (use case B above).

Havent had time to investigate this much, but I wonder if we could delegate the sorting to a background thread it might improve UI responsiveness at least.

/steve

kschaefe
Offline
Joined: 2006-06-08

Possibly, it is a wait until Mustang thing. Though I have not personally tried to sort 160,000+ rows in Mustang yet, so it may be no better. I'll give it a whirl and let you know.

This reminds me: rbiar, have you looked into my SwingWorker backport using javax package-space question? If it's possible to use those package names, we could bring over the Mustang code now for sorting/filtering by porting the code in the proper pacakges.

Karl

kschaefe
Offline
Joined: 2006-06-08

I got these results:
Sorting JTable (ints):
1442 ms
3712 ms
3713 ms
3869 ms

Sorting JXTable (ints):
282 ms
454 ms
78 ms
454 ms

Sorting JTable (strings):
1081 ms
4652 ms
3243 ms
3306 ms

Sorting JXTable (strings):
1174 ms
3697 ms
1379 ms
4778 ms

I used two column tables with 160,000 lines using the DefaultTableModel. In both cases, I used default sorting methods in all cases.

Karl

jidesoftware
Offline
Joined: 2004-10-07

Hi Karl,

Can you post the test case you used here or email to support @ jidesoft.com? JIDE has a SortableTable and I would like to compare it with JTable and JXTable and post results here.

Thanks,

-David

kschaefe
Offline
Joined: 2006-06-08

Embarassed by my utterly sloppy code, but here's the test:

[code]import java.util.Random;
import java.util.Vector;

import javax.swing.JTable;

import org.jdesktop.swingx.JXTable;

/**
*
*/
public class JTableSortTest {

/**
* @param args
*/
public static void main(String[] args) {
Vector names = new Vector(2);
names.add("Row");
names.add("Random");

JTable table = new JTable(getIntData(), names);
table.setAutoCreateRowSorter(true);
System.err.println("Sorting JTable (ints):");

testJTable(table, 4);

JXTable xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (ints):");

testJXTable(xTable, 4);

table = new JTable(getStringData(), names);
table.setAutoCreateRowSorter(true);
System.err.println("\nSorting JTable (strings):");

testJTable(table, 4);

xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (strings):");

testJXTable(xTable, 4);
}

private static void testJTable(JTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.getRowSorter().toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static void testJXTable(JXTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static Vector> getIntData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i);
row.add(r.nextInt());

data.add(row);
}

return data;
}

private static Vector> getStringData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i + "");
row.add(r.nextInt() + "");

data.add(row);
}

return data;
}
}[/code]

jidesoftware
Offline
Joined: 2004-10-07

Thanks Karl. Here is what I got.

Sorting JTable (ints):
3110 ms
5500 ms
3843 ms
4016 ms

Sorting JXTable (ints):
140 ms
1219 ms
109 ms
985 ms

Sorting JIDE's SortableTable (ints):
125 ms
687 ms
0 ms
79 ms
671 ms

Sorting JTable (strings):
1329 ms
2937 ms
2875 ms
2844 ms

Sorting JXTable (strings):
1000 ms
3032 ms
1234 ms
3016 ms

Sorting JIDE's SortableTable (strings):
94 ms
266 ms
0 ms
93 ms
266 ms

It looks like JIDE SortableTable beats the other two implementations in both cases. I haven't digged into to see the reason. It will be interesting to find out.

A quick note: I called testSortableTable(jideTable, 5); instead of 4 because JIDE SortableTable has three-way toggle: Sort ascending => Sort descending => Unsort. It can be customized but for the sake of simplicity, I didn't do it but just toggle it five times. The 3rd number is 0 because that's unsort. There is nothing to do but erasing the whole index.

And here is the modified source code with JIDE SortableTable. Feel free to try it yourself. I am using 2.0.2 release of JIDE Grids.

[code]
import java.util.Random;
import java.util.Vector;

import javax.swing.JTable;

import org.jdesktop.swingx.JXTable;
import com.jidesoft.grid.SortableTable;
import com.jidesoft.grid.SortableTableModel;

/**
*
*/
public class JTableSortTest {

/**
* @param args
*/
public static void main(String[] args) {
Vector names = new Vector(2);
names.add("Row");
names.add("Random");

JTable table = new JTable(getIntData(), names);
table.setAutoCreateRowSorter(true);
System.err.println("Sorting JTable (ints):");

testJTable(table, 4);

JXTable xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (ints):");

testJXTable(xTable, 4);

SortableTable jideTable = new SortableTable(table.getModel());
System.err.println("\nSorting JIDE's SortableTable (ints):");

testSortableTable(jideTable, 5);

table = new JTable(getStringData(), names);
table.setAutoCreateRowSorter(true);
System.err.println("\nSorting JTable (strings):");

testJTable(table, 4);

xTable = new JXTable(table.getModel());
System.err.println("\nSorting JXTable (strings):");

testJXTable(xTable, 4);

jideTable = new SortableTable(table.getModel());
System.err.println("\nSorting JIDE's SortableTable (strings):");

testSortableTable(jideTable, 5);
}

private static void testJTable(JTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.getRowSorter().toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static void testJXTable(JXTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
table.toggleSortOrder(0);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static void testSortableTable(SortableTable table, int iterations) {
for (int i = 0; i < iterations; i++) {
long time = System.currentTimeMillis();
((SortableTableModel) table.getModel()).toggleSortOrder(0, false);
System.err.println(System.currentTimeMillis() - time + " ms");
}
}

private static Vector> getIntData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i);
row.add(r.nextInt());

data.add(row);
}

return data;
}

private static Vector> getStringData() {
Random r = new Random();
Vector> data = new Vector>(160000);

for (int i = 0; i < 160000; i++) {
Vector row = new Vector(2);

row.add(i + "");
row.add(r.nextInt() + "");

data.add(row);
}

return data;
}
}[/code]

kschaefe
Offline
Joined: 2006-06-08

I found the numbers a little surprising as well. By comparison an Arrays.sort of a 1,000,000 element int array takes ~387 ms. I still need to do a Collections.sort for a List, but I'm guessing that will balloon the time.

Karl

jidesoftware
Offline
Joined: 2004-10-07

Regardless what the actual data type is, JIDE SortableTable just sorts an int array. I believe it should be faster than sorting List. That's also why we get the same performance result for int or String, comparing with JXTable is fast sorting int but slow when sorting String.

rbair
Offline
Joined: 2003-07-08

Hey David,

It isn't obvious to me how this works. How can you just sort an int array, regardless of the datatype? Somewhere there has to be the comparison between values. Are you just running the comparisons once, and then sorting on their relative order thereafter?

Richard

jidesoftware
Offline
Joined: 2004-10-07

Hi Richard,

Sorry for the confusion. I just answered James who asked the same question.

---
When I said "sort an int array", I simply mean that an int array is the data structure the sort algorithm will modify. When we do the sorting, we still use the int value in the array as the row index to look up in the table model to find what the real data is.
---

-David