Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Override ListGrid.createRecordComponent rendering performance

    Hello,

    Firstly my setup details: GWT 2.1, SmartGWT 3.0, browser = FireFox 13.0.1
    Version lower left corner of dev console: SmartClient Version: 8.2/LGPL Development Only (built 2011-12-05)

    Pre-amble:

    We have an engine that allows users to build their own UI screen templates and a renderer which currently outputs HTML in line with the screen templates created by the user.
    We are in the process of rewriting our renderer to target SmartGWT 3.0, moving rendering to the client in order to take load off the server, amongst other benefits. For this purpose GWT/SmartGWT is a great fit.
    When a user builds a screen with a table component, they can configure it to be paginated, or more importantly not, and specify exactly what
    is shown in each cell of the row within the table. It should also be noted that we require all rows to be rendered upon
    initial table display and so do not use the "incremental rendering" associated with setShowAllRecords(false).

    To render the users defined table, i have been using ListGrid with the following configuration:

    setShowRecordComponents(true) with setShowRecordComponentsByCell(true)
    - to force createRecordComponent to be called for every cell

    setShowAllRecords(true);
    - to ensure all records are rendered when the table is first displayed

    ListGrid.createRecordComponent override
    - creating the widget for each cell

    However, I have run into an issue regarding rendering performance when overriding ListGrid.createRecordComponent.
    I have created a test class below which i hope you will be able to use to reproduce the results i have observed.

    Code:
    public class FxTestEntryPoint implements EntryPoint {
    
    	public static int recordCount = 0;
    	public static int cellRendererCount = 0;
    
    	public void onModuleLoad() {
    		FxTestTable fxtt = new FxTestTable();
    		fxtt.setWidth100();
    		fxtt.setHeight100();
    		String[] colNames = new String[] {"id","text1","text2","text3","text4","num1","num2","num3","num4"};
    		
    		ListGridField[] fields = new ListGridField[9];
    		for (int count = 0; count < 9; count++) {
    			String colName = colNames[count];
    			String columnLabel = colNames[count];
    			ListGridField field = new ListGridField(colName, columnLabel);
    			fields[count] = field;
    		}
    		
    		fxtt.setFields(fields);
    		fxtt.setDataSource(new TestDS());
    		fxtt.draw();
    	}
    
    	private class TestDS extends GwtRpcDataSource {
    		public TestDS() {
    			DataSourceField primaryKey = new DataSourceField("id", FieldType.TEXT);
    			primaryKey.setPrimaryKey(true);
    			primaryKey.setHidden(true);
    			addField(primaryKey);
    
    			String[] colNames = new String[] {"id","text1","text2","text3","text4","num1","num2","num3","num4"};
    
    			for (int count = 0; count < 9; count++) {
    				String propertyName = colNames[count];
    				String columnLabel = colNames[count];
    				DataSourceField field = new DataSourceTextField(propertyName, columnLabel);
    				addField(field);
    			}
    
    		}
    		
    		@Override
    		protected void executeFetch(String requestId, DSRequest request, DSResponse response) {
    			int numRecords = 200;
    			recordCount = 0;
    			ListGridRecord[] records = new ListGridRecord[numRecords];
    			for (int i = 0; i < numRecords; i++) {
    				ListGridRecord rec = new ListGridRecord();
    				rec.setAttribute("id", "id_" + recordCount);
    				rec.setAttribute("text1", "text1_" + recordCount);
    				rec.setAttribute("text2", "text2_" + recordCount);
    				rec.setAttribute("text3", "text3_" + recordCount);
    				rec.setAttribute("text4", "text4_" + recordCount);
    				rec.setAttribute("num1", "num1_" + recordCount);
    				rec.setAttribute("num2", "num2_" + recordCount);
    				rec.setAttribute("num3", "num3_" + recordCount);
    				rec.setAttribute("num4", "num4_" + recordCount);
    				records[i] = rec;
    				recordCount++;
    			}
    			response.setData(records);
    			processResponse (requestId, response);
    		}
    
    		protected void executeAdd(String requestId, DSRequest request, DSResponse response) {}
    		protected void executeUpdate(String requestId, DSRequest request, DSResponse response) {}
    		protected void executeRemove(String requestId, DSRequest request, DSResponse response) {}
    	}
    
    	private class FxTestTable extends ListGrid {
    
    		private boolean useSuper = false;
    
    		public FxTestTable() {
    			setAutoFetchData(true);
    			setShowRecordComponents(true);
    			setShowRecordComponentsByCell(true);
    			setShowAllRecords(true);
    			setShowHeader(true);
    			
    			String[] colNames = new String[] {"id","text1","text2","text3","text4","num1","num2","num3","num4"};
    			
    			ListGridField[] fields = new ListGridField[9];
    			for (int count = 0; count < 9; count++) {
    				String colName = colNames[count];
    				String columnLabel = colNames[count];
    				
    				ListGridField field = new ListGridField(colName, columnLabel);
    				fields[count] = field;
    			}
    			
    			setFields(fields);
    		}
    		
    		protected Canvas createRecordComponent (ListGridRecord record, Integer colNumZeroIndexed) {
    			if (useSuper) {
    				return super.createRecordComponent (record, colNumZeroIndexed);
    			}
    			else {
    				// Output simple widget so that we can compare rendering times with full cell renderer
    				HLayout hl = new HLayout();
    				hl.setContents("" + cellRendererCount++);
    				return hl;
    			}
    		}
    	}
    
    }
    The test class contains a ListGrid implementation FxTestTable which switches, via the FxTestTable.useSuper flag, between 2 rendering modes within the createRecordComponent method override:
    Mode 1 - a call to super.createRecordComponent
    Mode 2 - creating a very simple widget directly and returning it

    When running the test class in both modes for different numbers of records i observed the following results:
    (adjust numRecords int in TestDS.executeFetch to ammend the test data set size)

    Al timings were made manually with a stop watch so not entirely accurate but you get the idea.

    100 records (9 columns)
    Mode 1 (5 runs, time in seconds) = 1.1 ,1.2, 1.2, 1.1, 1.0
    Mode 2 (5 runs, time in seconds) = 5.1, 5.0, 5.1, 5.0, 5.0 (approx 5x slower)

    200 records (9 columns)
    Mode 1 (5 runs, time in seconds) = 1.3, 1.4, 1.3, 1.2, 1.3
    Mode 2 (5 runs, time in seconds) = 13.0, 13.2, 13.0, 13.1, 13.2 (approx 10x slower)

    500 records (9 columns)
    Mode 1 (5 runs, time in seconds) = 1.9, 2.0, 2.0, 1.9, 2.0
    Mode 2 (3 runs, time in mins:seconds) = 1:03, 1:04, 1:04 (approx 30x slower)

    1000 records (9 columns)
    Mode 1 (5 runs, time in seconds) = 3.0, 3.2, 3.1, 3.0, 3.1
    Mode 2 (3 runs, time in mins:seconds) = 4:20, 4:28, 4:02 (approx 80x slower)

    2500 records (9 columns)
    Mode 1 (5 runs, time in seconds) = 6.8, 7.0, 6.9, 7.2, 7.1
    Mode 2 = not tested
    (but based on non linear mode 2 performance degrade from 100 through 200, 500 and 1000 records i estimate somewhere around 15 - 20 minutes)

    So my questions are.....

    Q. Why is returning even a simple widget so much more time consuming than a call to super.createRecordComponent?
    Where is this extra time spent? Surely at some point super.createRecordComponent creates the default widget used for a ListGrid cell.

    Q. Why does performance degrade non linearly, unlike that observed using mode 1?

    ps i have tried upgrading to GWT 2.4 with SmartGWT 3.1p but my timings were almost identical in all data set sizes for mode 2.

    Any light you can shed on this will be greatly appreciated. At the moment this is a show stopper for us and our new renderer.

    Many thanks

    manny31

    #2
    There is no "default widget" - when you call Super, no widget is created.

    Performance in non-linear because operations on the browser's DOM will generally slow down as you add more DOM elements.

    This is one reason why incremental rendering is the default, and it's even more important when using recordComponents.

    Comment


      #3
      I'd like to put in my 5 cents. Performance of ListGrid becomes pretty sad when using setShowRecordComponents(true), setShowRecordComponentsByCell(true).

      I'm using icons menu in list grid like here, 2 rendered icons in each row and 75 records shown on the screen makes noticable drop in scrolling performance.

      Even i'm not using setShowAllRecords(true) and implemented setRecordComponentPoolingMode(RecordComponentPoolingMode.RECYCLE) with correct and pretty optimal createRecordComponent and updateRecordComponent methods - performance of listgrid scrolling is dropping considerably.

      Separate columns of type ListGridFieldType.ICON are performing much better, but this approach is not suitable for me.

      Comment


        #4
        That's an example that we'll probably revise to show more substantial components embedded in cells rather than just ImgButtons. Something as simple as a pair of icons, is best created with a CellFormatter rather than with recordComponents.

        We will likely add some settings in the future to make it a bit easier to do a multiple icon field, so that you don't have to understand the appropriate HTML to write out.

        Comment


          #5
          Well, is there a simple way to attach handlers to html icons returned from CellFormatter? If no - it's not a good replacement for recordComponents...

          Comment


            #6
            Depends what you mean by "simple". It can be done in the normal way of attaching event handlers in HTML, or it can be done via a CellClick handler that does coordinate checking. Both are straightforward but are not quite as simple as the createRecordComponent approach.

            Comment


              #7
              Hi, so can you please outline the differences in DOM manipulation for the 2 modes in the examples?

              1. super.createRecordComponent

              2. A simple widget returned by createRecordComponent

              You say that there is no default widget created in mode 1 but I'm guessing there is still some DOM manipulation going on under the hood to output each cell? Is this optimized?

              If this is the case, id like to understand why the DOM manipulation that goes on under the hood in mode 2 is so much more inefficient than that performed in mode 1?

              Any information you could provide is gratefully received. Right now this issue is a potential show stopper for us as we need to use custom widgets regularly in our SmartGWT tables.

              Comment


                #8
                The grid creates an HTML <table> regardless of what you do in createRecordComponent, with one table cell and potentially some other DOM as needed on a per-browser basis to work around browser bugs with clipping, sizing and centering.

                1. does nothing

                2. positions and possibly sizes whatever component you created over the cell and draws it and clears it as described in docs.

                Returning some simple HTML via a CellFormatter is more efficient than creating a general purpose component in (2) above.

                Comment


                  #9
                  Thanks very much for your reply, this has given me another avenue of investigation. Could you possibly point me towards the docs you refer to in point 2?

                  Comment


                    #10
                    The docs for showRecordComponents provide an overview and link to all the properties that control positioning, sizing and when components are drawn/redrawn/recycled.

                    Comment

                    Working...
                    X