Results 1 to 8 of 8

Thread: grid performance with many rows -- FastGridView

  1. #1
    Sencha User
    Join Date
    Mar 2007
    Location
    Boulder, CO
    Posts
    69

    Default grid performance with many rows -- FastGridView

    My app needs to quickly unload many rows from a grid and replace them with many other rows.
    I've added a couple of optimizations to the previously posted lazy row rendering.
    Basically I'm just trying to avoid the slow DOM calls.
    For removing all rows from a grid (1000 rows x 3 columns), time went from 2.5 sec to 0.1 sec.
    For inserting into an empty grid (1500 rows x 3 columns), time went from 3.0 sec to 0.5 sec.
    (this is the time required to 'lazy render' the first two pages of rows.)

    With all three optimizations time went from 18 sec to 1.3 sec (remove 1000 rows, add 1500 rows).

    I've only tested this on Firefox.

    Thanks,
    Eric

    Code:
    YAHOO.ext.grid.FastGridView = function(){
    	YAHOO.ext.grid.FastGridView.superclass.constructor.call(this);
    	this.rendered = [];
    };
    YAHOO.extendX(YAHOO.ext.grid.FastGridView, YAHOO.ext.grid.GridView);
    
    YAHOO.ext.grid.FastGridView.prototype.init = function(grid){
    	YAHOO.ext.grid.FastGridView.superclass.init.call(this, grid);
    	this.grid.maxRowsToMeasure = 1; // hack
    	this.grid.addListener('bodyscroll', this.lazyRender, this, true);
    }
    
    YAHOO.ext.grid.FastGridView.prototype.insertRows
    	= function(dataModel, firstRow, lastRow)
    {
    	this.updateBodyHeight();
    	this.adjustForScroll(true);
    	var renderers = this.getColumnRenderers();
    	var dindexes = this.getDataIndexes();
    	var colCount = this.grid.colModel.getColumnCount();
    	var beforeRow = null;
    	var bt = this.getBodyTable();
    	if(firstRow < bt.childNodes.length){
    		beforeRow = bt.childNodes[firstRow];
    	}
    	if (firstRow == 0 && lastRow == dataModel.getRowCount() - 1)
    	{
    		var s = "";
    		var stripeRows = this.grid.stripeRows;
    		var stripe =  false;
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			var alt = stripe && stripeRows ? ' ygrid-row-alt' : '';
    			s += "<span class='ygrid-row"+ alt +"' style='top:"
    				+ (rowIndex * this.rowHeight) + "px' rowIndex:'"+rowIndex
    				+"'></span>";
    			this.rendered[rowIndex] = false;
    			stripe = !stripe;
    		}
    		bt.innerHTML = bt.innerHTML + s;
    	}
    	else
    	{
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			var row = document.createElement('span');
    			row.className = 'ygrid-row';
    			row.style.top = (rowIndex * this.rowHeight) + 'px';
    			this.rendered[rowIndex] = false;
    			if(beforeRow){
    				bt.insertBefore(row, beforeRow);
    			}else{
    				bt.appendChild(row);
    			}
    		}
    		this.updateRowIndexes(firstRow);
    	}
    	this.adjustForScroll();
    	this.lazyRender();
    };
    
    YAHOO.ext.grid.FastGridView.prototype.lazyRender = function()
    {
    	var y = this.wrap.scrollTop;
    	var rowHeight = this.getRowHeight();
    	var rowIndex = (y == 0 ? 0 : Math.floor(y / rowHeight));
    	var grid = this.grid;
    	var dataModel = grid.getDataModel();
    	var rowCount = dataModel.getRowCount();
    	if (rowIndex >= rowCount ){ return; }
    	var numDisplayedRows = Math.ceil(this.wrap.clientHeight / rowHeight);
    	var lastRowIndex = Math.min(rowCount - 1, rowIndex + numDisplayedRows * 2);
    
    	var colCount = grid.colModel.getColumnCount();
    	var renderers = this.getColumnRenderers();
    	var dindexes = this.getDataIndexes();
    	while (rowIndex <= lastRowIndex)
    	{
    		if (!this.rendered[rowIndex])
    		{
    			var row = grid.getRow(rowIndex);
    			this.renderRow(dataModel, row, rowIndex, colCount, renderers, dindexes);
    			this.rendered[rowIndex] = true;
    		}
    		++rowIndex;
    	}
    }
    
    
    YAHOO.ext.grid.FastGridView.prototype.deleteRows
    	= function(dataModel, firstRow, lastRow)
    {
    	this.updateBodyHeight();
    	// first make sure they are deselected
    	var bt = this.getBodyTable();
    
    	if (!this.grid.dataModel.getRowCount())
    	{
    		this.grid.selModel.clearSelections();
    		bt.innerHTML = "";
    	}
    	else
    	{
    		this.grid.selModel.deselectRange(firstRow, lastRow);
    		var rows = []; // get references because the rowIndex will change
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			rows.push(bt.childNodes[rowIndex]);
    		}
    		for(var i = 0; i < rows.length; i++){
    			bt.removeChild(rows[i]);
    			rows[i] = null;
    		}
    		rows = null;
    		this.updateRowIndexes(firstRow);
    		this.adjustForScroll();
    	}
    };

  2. #2
    Sencha User jack.slocum's Avatar
    Join Date
    Mar 2007
    Location
    New York, NY
    Posts
    6,956

    Default

    That's awesome. Thanks for packaging it into a class too, that will make it much easier to test and include in the distribution. I am going to do some testing with it on IE and Opera and I'll let you know how it goess (I can't test Safari).

  3. #3
    Ext JS Premium Member
    Join Date
    Mar 2007
    Posts
    122

    Default

    Oops!

    Guess I should have read through the forums before posting my FastGridView just now (http://www.jackslocum.com/forum/viewtopic.php?t=430) . Glad to see someone else had the same thoughts as I did! Nice job -

  4. #4
    Sencha User
    Join Date
    Mar 2007
    Location
    Boulder, CO
    Posts
    69

    Default

    Found a bug.
    I was trying to set the span's rowIndex while building the html string.
    Code:
         for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
             var alt = stripe && stripeRows ? ' ygrid-row-alt' : '';
             s += "<span class='ygrid-row"+ alt +"' style='top:"
                + (rowIndex * this.rowHeight) + "px' rowIndex:'"+rowIndex
                +"'></span>";
    This evidently does not work.
    So I pulled the "rowIndex:..." code and added a loop after setting innerHTML:
    Code:
    		bt.innerHTML = s;
    		var rows = bt.childNodes;
    		for (var rowIndex = 0; rowIndex < rows.length; rowIndex++)
    		{
    			rows[rowIndex].rowIndex = rowIndex;
    		}
    Is there a way to do this without the additional loop?

    Thanks.
    Eric

  5. #5
    Sencha User
    Join Date
    Mar 2007
    Posts
    7,854

    Default

    Given the amount of string concatenation that's going on here, you might try something else to get more speed.
    I don't have any numbers on relative speed of tons of string concat vs arrays, but I think it will be faster.

    Replace
    s = '';
    for ... {
    s = s + 'abc' + 'def';
    s = s + 'xyz'...
    }
    ...innerHTML = s;


    with
    s = [];
    for .. {
    s[s.length] = 'abc';
    s[s.length] = 'def';
    s[s.length] = 'xyz';
    }
    ...innerHTML = s.join('');

  6. #6
    Sencha User
    Join Date
    Mar 2007
    Location
    Boulder, CO
    Posts
    69

  7. #7
    Sencha User
    Join Date
    Mar 2007
    Location
    Boulder, CO
    Posts
    69

    Default

    (redundant. removed.)

  8. #8
    Sencha User
    Join Date
    Mar 2007
    Location
    Boulder, CO
    Posts
    69

    Default

    updateRows needs to be modified as well since it assumes all rows are already rendered.

    the latest:

    Code:
    YAHOO.ext.grid.FastGridView = function(){
    	YAHOO.ext.grid.FastGridView.superclass.constructor.call(this);
    	this.rendered = [];
    };
    YAHOO.extendX(YAHOO.ext.grid.FastGridView, YAHOO.ext.grid.GridView);
    
    YAHOO.ext.grid.FastGridView.prototype.init = function(grid){
    	YAHOO.ext.grid.FastGridView.superclass.init.call(this, grid);
    	this.grid.maxRowsToMeasure = 1; // hack
    	this.grid.addListener('bodyscroll', this.lazyRender, this, true);
    }
    
    YAHOO.ext.grid.FastGridView.prototype.insertRows
    	= function(dataModel, firstRow, lastRow)
    {
    	this.updateBodyHeight();
    	this.adjustForScroll(true);
    	var renderers = this.getColumnRenderers();
    	var dindexes = this.getDataIndexes();
    	var colCount = this.grid.colModel.getColumnCount();
    	var beforeRow = null;
    	var bt = this.getBodyTable();
    	if(firstRow < bt.childNodes.length){
    		beforeRow = bt.childNodes[firstRow];
    	}
    	if (firstRow == 0 && lastRow == dataModel.getRowCount() - 1)
    	{
    		var s = [];
    		var stripeRows = this.grid.stripeRows;
    		var stripe =  false;
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			s[s.length] = "<span class='ygrid-row";
    			s[s.length] = stripe && stripeRows ? ' ygrid-row-alt' : '';
    			s[s.length] = "' style='top:";
    			s[s.length] = rowIndex * this.rowHeight;
    			s[s.length] = "px'></span>";
    			this.rendered[rowIndex] = false;
    			stripe = !stripe;
    		}
    		bt.innerHTML = s.join("");
    		var rows = bt.childNodes;
    		for (var rowIndex = 0; rowIndex < rows.length; rowIndex++)
    		{
    			rows[rowIndex].rowIndex = rowIndex;
    		}
    	}
    	else
    	{
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			var row = document.createElement('span');
    			row.className = 'ygrid-row';
    			row.style.top = (rowIndex * this.rowHeight) + 'px';
    			this.rendered[rowIndex] = false;
    			if(beforeRow){
    				bt.insertBefore(row, beforeRow);
    			}else{
    				bt.appendChild(row);
    			}
    		}
    		this.updateRowIndexes(firstRow);
    	}
    	this.adjustForScroll();
    	this.lazyRender();
    };
    
    YAHOO.ext.grid.FastGridView.prototype.lazyRender = function()
    {
    	var indexes = this.getRowsToRender();
    	var rowIndex = indexes[0];
    	var lastRowIndex = indexes[1];
    	var grid = this.grid;
    	var dataModel = grid.getDataModel();
    	var colCount = grid.colModel.getColumnCount();
    	var renderers = this.getColumnRenderers();
    	var dindexes = this.getDataIndexes();
    	while (rowIndex <= lastRowIndex)
    	{
    		if (!this.rendered[rowIndex])
    		{
    			var row = grid.getRow(rowIndex);
    			this.renderRow(dataModel, row, rowIndex, colCount, renderers, dindexes);
    			this.rendered[rowIndex] = true;
    		}
    		++rowIndex;
    	}
    }
    
    YAHOO.ext.grid.FastGridView.prototype.getRowsToRender = function()
    {
    	var y = this.wrap.scrollTop;
    	var rowHeight = this.getRowHeight();
    	var rowIndex = (y == 0 ? 0 : Math.floor(y / rowHeight));
    	var grid = this.grid;
    	var dataModel = grid.getDataModel();
    	var rowCount = dataModel.getRowCount();
    	if (rowIndex >= rowCount) { return [0, -1]; }
    	var numDisplayedRows = Math.ceil(this.wrap.clientHeight / rowHeight);
    	var lastRowIndex = Math.min(rowCount - 1, rowIndex + numDisplayedRows * 2);
    	return [rowIndex, lastRowIndex];
    }
    
    
    YAHOO.ext.grid.FastGridView.prototype.deleteRows
    	= function(dataModel, firstRow, lastRow)
    {
    	this.updateBodyHeight();
    	// first make sure they are deselected
    	var bt = this.getBodyTable();
    
    	if (!this.grid.dataModel.getRowCount())
    	{
    		this.grid.selModel.clearSelections();
    		bt.innerHTML = "";
    	}
    	else
    	{
    		this.grid.selModel.deselectRange(firstRow, lastRow);
    		var rows = []; // get references because the rowIndex will change
    		for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    			rows.push(bt.childNodes[rowIndex]);
    		}
    		for(var i = 0; i < rows.length; i++){
    			bt.removeChild(rows[i]);
    			rows[i] = null;
    		}
    		rows = null;
    		this.updateRowIndexes(firstRow);
    		this.adjustForScroll();
    	}
    };
    
    YAHOO.ext.grid.FastGridView.prototype.updateRows
    	= function(dataModel, firstRow, lastRow)
    {
    	var bt = this.getBodyTable();
    	var dindexes = this.getDataIndexes();
    	var renderers = this.getColumnRenderers();
    	var grid = this.grid;
    	var colCount = grid.colModel.getColumnCount();
    	var indexes = this.getRowsToRender();
    	var firstRenderedRow = indexes[0];
    	var lastRenderedRow = indexes[1];
    	for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
    		var row = bt.rows[rowIndex];
    		if (rowIndex >= firstRenderedRow && rowIndex <= lastRenderedRow)
    		{
    			if (!this.rendered[rowIndex])
    			{
    				var row = grid.getRow(rowIndex);
    				this.renderRow(dataModel, row, rowIndex, colCount, renderers,
    									dindexes);
    				this.rendered[rowIndex] = true;
    			}
    			else
    			{
    				var cells = row.childNodes;
    				for(var colIndex = 0; colIndex < colCount; colIndex++){
    					var td = cells[colIndex];
    					var val = renderers[colIndex](dataModel.getValueAt(rowIndex, dindexes[colIndex]), rowIndex, colIndex);
    					if(typeof val == 'undefined' || val === '') val = '';
    					td.firstChild.innerHTML = val;
    				}
    			}
    		}
    		else
    		{
    			row.innerHTML = "";
    			this.rendered[rowIndex] = false;
    		}
    	}
    };

Similar Threads

  1. FastGridView
    By jack.slocum in forum Community Discussion
    Replies: 14
    Last Post: 18 Jun 2007, 9:52 PM
  2. Grid performance profile using Firebug
    By Animal in forum Ext 2.x: Help & Discussion
    Replies: 15
    Last Post: 27 Apr 2007, 10:30 AM
  3. Performance A2Rev5: Tab Switching with a tab being a Grid
    By Domitian in forum Ext 2.x: Help & Discussion
    Replies: 11
    Last Post: 8 Mar 2007, 7:52 AM
  4. Grid & Slow performance?
    By [email protected] in forum Ext 1.x: Bugs
    Replies: 2
    Last Post: 24 Oct 2006, 9:35 AM
  5. grid addRows performance
    By ericwaldheim in forum Ext 1.x: Help & Discussion
    Replies: 4
    Last Post: 19 Oct 2006, 10:40 AM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •