Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Thursday, April 14, 2016

JavaFX TableView Scroll Time With PropertyValueFactory


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
I used Java VirtualVM to capture the amount of time spent scrolling.  See the following screenshot.

Java VisualVM Measuring Time Spent Scrolling
Initially, I though that Java Reflection might cause a lag with scrolling a large result set.  This turned out not to be the case as the current implementation of PropertyValueFactory caches the Reflection information.

10 comments:

  1. hmm ... probably overlooking something - but what are you measuring?

    Cheers, Jeanette

    ReplyDelete
    Replies
    1. Hi,

      I 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

      Delete
    2. 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)

      Delete
    3. You're right. I'm finishing up another blog post and I'll update this with a few "scrollTo" calls in the timing measurement.

      Delete
    4. Repeated 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.

      Thanks for looking this over.

      Delete
    5. I rewrote the post with a better test case.

      Delete
  2. One 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.

    ReplyDelete
    Replies
    1. I 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.

      Delete
  3. I'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 :
    fname.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!!

    ReplyDelete
  4. in my case what I did was disable the ordering on the columns and set the following attributes:
    cache="true" editable="false" cacheHint="SPEED"

    ReplyDelete