This post has been overhauled to examine scrolling in a large table rather than loading. One of the readers (thanks @kleopatra_jx) pointed out that my original post wasn't correct because the different PropertyValueFactory implementations were only called a few times. In this updated post, I've made sure that the PropertyValueFactory's call method is invoked via a method that will scroll through the whole TableView.
This screenshot shows a JavaFX application consisting of a TableView and 2 Buttons. The TableView contains two columns: one for a field Id and one for a field Data. Pressing the Load Button creates 20,000,000 objects and adds them to the TableView.
Loading a Large TableView |
PropertyValueFactory objects are used to display the items on the screen. The factory is invoked when an object scrolls onto the screen. I used the following method to scroll through the entire result set. The Thread was needed because a simple loop seemed to consolidate the scroll operations, scrolling to the last issued scrollTo(). I verified that the Thread approach did not consolidate the calls; the PropertyValueFactory was called many more times.
private void scrollEntireTable() { new Thread() { @Override public void run() { for( int i=50; i<NUM_RECORDS; i=i+50 ) { final int pos = i; Platform.runLater(() -> tblObjects.scrollTo( pos )); } } }.start(); }
The first approach I timed used Java Reflection to take a passed-in string (ex, "id" and "data") and return a newly-created ObservableValue.
tcId.setCellValueFactory(new PropertyValueFactory<MyObject,Number>("id")); tcData.setCellValueFactory(new PropertyValueFactory<MyObject,String>("data"));
These results are reported in the trials under "Using Default PropertyValueFactoryImplementation".
The second approach I timed did not use Java Reflection. It accessed the specific value using a direct method call.
tcId.setCellValueFactory(new PropertyValueFactory("id") { @Override public ObservableValue call(CellDataFeatures param) { return new ReadOnlyObjectWrapper (param.getValue().getId()); } }); tcData.setCellValueFactory(new PropertyValueFactory ("data"){ @Override public ObservableValue call(CellDataFeatures param) { return new ReadOnlyObjectWrapper (param.getValue().getData()); } });
The non-Reflection version is listed in the Non-Reflection PropertyValueFactory section.
The results were a little surprising. It turns out the the default implementation performed better (saving 30 seconds on all the scrolling). I looked at the code and don't think that Reflection is an issue at all. The current implementation in the JRE caches the class info so it's a one-time performance hit. Recall that this is a 20 million record set being scrolled through sequentially so the gap closes with less scrolling.
CPU Results |
Java VisualVM Measuring Time Spent Scrolling |
hmm ... probably overlooking something - but what are you measuring?
ReplyDeleteCheers, Jeanette
Hi,
DeleteI appended the code for the loadTables() method. The timing metrics are on generating 20 million POJOs, 20 million FX property-based objects, and loading the TableView.
Before I looked at the source for PropertyValueFactory.java, I had expected Java Reflection to weigh down the loading method. This wasn't the case since the Reflection information is cached
Thanks, but still a bit confused: how comes the PropertyValueFactory into play while _loading_ the data? It's used only when _displaying_ a cell (without extensive scrolling that's in the order of tens of accesses)
DeleteYou're right. I'm finishing up another blog post and I'll update this with a few "scrollTo" calls in the timing measurement.
DeleteRepeated scrollTo calls seem to be coalescing. I made 400,000 scrollTo calls, but only the final scrollTo seemed to take effect. I think I'll just profile the PropertyValueFactory.call() variations outright (without the TableView, etc) and remove most of this post.
DeleteThanks for looking this over.
I rewrote the post with a better test case.
DeleteOne thing you might want to explore is what the performance implications are when the domain model has observableValue *Property methods in them, so that in the non-reflection approach you do not need to create ReadOnlyObjectWrapper instances. I would be interested to see if there is substantial cost involved with the creation of these wrapper instances.
ReplyDeleteI was also going to see if some kind of pool of ObservableValue objects might help. A pool much larger than the displayable items, but substantially smaller than the large input set. This would be to keep the RAM down and maybe save time on object creation.
DeleteI've used your solution and it's work fine when I make param.getValue() but in my case I've 6 column with this code :
ReplyDeletefname.setCellValueFactory(new Callback, ObservableValue>() {
@Override
public ObservableValue call(TableColumn.CellDataFeatures param) {
return new ReadOnlyObjectWrapper(param.getValue().getFname());
}
});
it doesn't work well ,the scroll's still slow , please any idea!!
in my case what I did was disable the ordering on the columns and set the following attributes:
ReplyDeletecache="true" editable="false" cacheHint="SPEED"