Help with performance for 1Hz table refreshing
Help with performance for 1Hz table refreshing
mtmacdonald
Posts: 7Questions: 0Answers: 0
I'm developing a small web app to list the values of signals on an embedded controller. Here's the spec:
- the table can be moderately large (e.g. 4000 rows, 7 columns)
- a single column of the data (all rows) needs to be refreshed once per second (i.e. the signal values)
- data format is JSON (array of objects), and I'm currently loading the whole table and processing client-side
- preference is to use the scroller plugin over pagination but this isn't mandatory
Everything works nicely apart from the performance of refreshing the values (particularly on IE8). I have two questions:
1. how can I improve the 1Hz refresh performance? Any other design methods I can try?
2. how do I get the datatable to remember the pagination state after refreshing (ideally noting preference for scroller plugin)?
So far I've tried several methods.
Method 1 - recreate the entire table once per second (too slow to cope with 1Hz refresh)
[code]
var dataTable = $('#example').dataTable(dataTableOptions);
[/code]
Method 2 - use fnUpdate on every row once per second (much too slow to cope with 1Hz refresh)
[code]
$.get('./table.json', "getValues=true", function(result){
for(i = 0; i < result.aaData.length ; ++i)
{
dataTable.fnUpdate( result.aaData[i].Value, i, 4 );
}
}, "json");
}
[/code]
Method 3 - use the fnReloadAjax plugin once per second (closest so far but still CPU heavy, and barely useable in IE8)
[code]
dataTable.fnReloadAjax();
[/code]
Method 4 - attempt to refresh only visible rows once per second (so far not better than method 3)
[code]
var oSettings = dataTable.fnSettings();
for (var i = oSettings._iDisplayStart; i < oSettings._iDisplayEnd; ++i)
{
//either method 4.1 (just too slow):
dataTable.fnUpdate( result.aaData[i].Value, i, 4 );
//or method 4.2 (there's no fast method like fnClearTable() to remove blocks of rows? fnDeleteRow() is slow)
dataTable.fnDeleteRow(i);
dataTable.fnAddData(result.aaData[i]);
}
[/code]
My remaining ideas are:
- find other ways to refresh the values only for rows that are visible to the user
- cache the changes on the server and refresh only those rows which have changed (but I can't guarantee many rows won't change)
- switch to server side processing (but then I lose many of the gains of use Datatables, plus my server is an embedded controller)
- are there others?
Many thanks!
- the table can be moderately large (e.g. 4000 rows, 7 columns)
- a single column of the data (all rows) needs to be refreshed once per second (i.e. the signal values)
- data format is JSON (array of objects), and I'm currently loading the whole table and processing client-side
- preference is to use the scroller plugin over pagination but this isn't mandatory
Everything works nicely apart from the performance of refreshing the values (particularly on IE8). I have two questions:
1. how can I improve the 1Hz refresh performance? Any other design methods I can try?
2. how do I get the datatable to remember the pagination state after refreshing (ideally noting preference for scroller plugin)?
So far I've tried several methods.
Method 1 - recreate the entire table once per second (too slow to cope with 1Hz refresh)
[code]
var dataTable = $('#example').dataTable(dataTableOptions);
[/code]
Method 2 - use fnUpdate on every row once per second (much too slow to cope with 1Hz refresh)
[code]
$.get('./table.json', "getValues=true", function(result){
for(i = 0; i < result.aaData.length ; ++i)
{
dataTable.fnUpdate( result.aaData[i].Value, i, 4 );
}
}, "json");
}
[/code]
Method 3 - use the fnReloadAjax plugin once per second (closest so far but still CPU heavy, and barely useable in IE8)
[code]
dataTable.fnReloadAjax();
[/code]
Method 4 - attempt to refresh only visible rows once per second (so far not better than method 3)
[code]
var oSettings = dataTable.fnSettings();
for (var i = oSettings._iDisplayStart; i < oSettings._iDisplayEnd; ++i)
{
//either method 4.1 (just too slow):
dataTable.fnUpdate( result.aaData[i].Value, i, 4 );
//or method 4.2 (there's no fast method like fnClearTable() to remove blocks of rows? fnDeleteRow() is slow)
dataTable.fnDeleteRow(i);
dataTable.fnAddData(result.aaData[i]);
}
[/code]
My remaining ideas are:
- find other ways to refresh the values only for rows that are visible to the user
- cache the changes on the server and refresh only those rows which have changed (but I can't guarantee many rows won't change)
- switch to server side processing (but then I lose many of the gains of use Datatables, plus my server is an embedded controller)
- are there others?
Many thanks!
This discussion has been closed.
Replies
not a direct answer to your question nevertheless I noticed quite a difference of speed between the quirks mode and the normal mode of IE.
Some info here : http://msdn.microsoft.com/en-us/library/cc288325(v=vs.85).aspx
I'm using the meta:
when I'm displaying dataTables. it makes quite a difference.
HTH,
/tm
Excellent question this, thanks for posing it so clearly. It's an important topic as well as rapid updates are very useful for a number of different scenarios, although I think this is the first use case I've seen for hardware state display. Very cool :-)
So regarding your question, I think your method 2 is very close, but I would suggest a couple of things. Firstly make use of the fourth parameter for fnUpdate, which disables the table for redrawing on every call - that's really expensive and this little change should help a lot:
[code]
$.get('./table.json', "getValues=true", function(result){
for(i = 0; i < result.aaData.length ; ++i)
{
dataTable.fnUpdate( result.aaData[i].Value, i, 4, false );
}
dataTable.fnDraw();
}, "json");
}
[/code]
Another thing that will really help, if you haven't got it enabled already, is derferred rendering, particularly if you are using an Ajax source and Scroller: http://datatables.net/ref#bDeferRender .
Hopefully with these two things the speed increase will prove to be enough for the performance you need. I'll detail a few other options which are a little more complex if you like to make it even faster (mainly removing the TR find loop from DataTables).
Regards,
Allan
Thank you for such a helpful answer. So far I have:
- used your solution but with fnDraw(false), in order to preserve the scroller position
- checked bDeferRender (it was already enabled)
- disabled bSort and bSortClasses (I only need filtering)
This is great progress and close to usable. Testing with 2500 rows I get:
- excellent results for Google Chrome (responsive UI, short 15% CPU spikes)
- nearly passable results on IE8 (sluggish UI, CPU sits between 40-50%)
If further improvements are possible (either as a patch or a config option in future datatables versions) I would be most interested.
As such I think the next step will be for me to write a modification to fnUpdate which will cache the DOM elements for the cells you want to update, thus saving a DOM lookup on every call to fnUpdate, which will also be a real hit in IE. I'll post this tomorrow evening if that's okay :-). I'll also look to see if any other improvements can be made (I'm wondering if the building for the searh array can be optimised a bit for example).
Regards,
Allan
Regards,
Allan
[code]
$.fn.dataTableExt.oApi.fnFastUpdate = function( oSettings, sDisplay, iRow, iColumn )
{
var iVisibleColumn, i, iLen, sDisplay;
var oApi = oSettings.oApi;
/* Update the cell in the internal cache */
oApi._fnSetCellData( oSettings, iRow, iColumn, sDisplay );
/* Render if needed */
if ( oSettings.aoColumns[iColumn].fnRender !== null ) {
sDisplay = oSettings.aoColumns[iColumn].fnRender( {
"iDataRow": iRow,
"iDataColumn": iColumn,
"aData": oSettings.aoData[iRow]._aData,
"oSettings": oSettings
} );
if ( oSettings.aoColumns[iColumn].bUseRendered ) {
oApi._fnSetCellData( oSettings, iRow, iColumn, sDisplay );
}
}
/* Do the actual HTML update */
if ( oSettings.aoData[iRow].nTr !== null ) {
if ( typeof oSettings.aoData[iRow].__tds == 'undefined' ) {
oSettings.aoData[iRow].__tds = oApi._fnGetTdNodes( oSettings, iRow );
}
oSettings.aoData[iRow].__tds[iColumn].innerHTML = sDisplay;
}
/* Modify the search index for this row */
var iDisplayIndex = $.inArray( iRow, oSettings.aiDisplay );
oSettings.asDataSearch[iDisplayIndex] = oApi._fnBuildSearchRow( oSettings,
oApi._fnGetRowData( oSettings, iRow, 'filter' ) );
};
[/code]
And this is an example of how it might be used:
[code]
$(document).ready(function() {
var otable = $('#example').dataTable();
otable.fnFastUpdate( "hello!", 0, 0 );
} );
[/code]
Let me know how you get on with it :-)
Regards,
Allan
Unfortunately, this time neither seems to improve the performance (maybe a 2% difference when I use the IE8 profiler, but it's not perceptible).
So I spent some time with the profiler, and also commenting out code blocks. It seems that fnDraw (and perhaps _fnScrollDraw too?) might in fact be the bigger bottleneck (even at only one call per second).
My current test case has 600 rows. Below is the CPU situation when updating the table once per second, with different parts of the update function commented out (I'm using the method from your first suggestion on 25th Oct). Particularly when adding fnDraw, the interface becomes rather unresponsive:
Code block-------------------------CPU idles at
AJAX + FOR LOOP ONLY----------------------3%
WITH FNUPDATE / FNFASTUPDATE ALSO-----19-22%
WITH FNDRAW ALSO------------------------50%
WITH FNDRAW ONLY------------------------50%
Do you think there is anything else we could try? Would it help if I code and upload a test case?
Failing that, yes a test case would be extremely useful, but I wonder if we might be hitting a performance boundary now.
Thanks,
Allan
The file can be downloaded here (let me know if there's a better place to post this):
http://www.box.net/shared/p13eu5xribrnpeoqu8c4
Any further suggestions are much appreciated, and if we've got as far as we can, thanks again for all the help.
Since you're monitoring a controller and not accessing an SQL database, it might be an awful suggestion; maybe the controller would be even slower...?
@mtmacdonald - I think your updateDataTable method could be optimised in two areas:
1. Its calling fnGetData() on every loop, since its re-evaluating that every time.
2. Try passing parameter five as false to fnUpdate
[code]
function updateDataTable(){
var rows = dataTable.fnGetData().length;
for(i = 0; i < rows ; ++i)
{
dataTable.fnUpdate( "Sample Data", i, 3, false, false );
}
dataTable.fnDraw(false);
}
[/code]
Does that help much? :-)
Allan
Greg: server side processing would resolve the browser performance problems (with the added bonus of possibly reducing data transfer rates). It would also mean I'd have to implement the filtering and pagination / scroll handling myself (not needing to do this was a big reason for wanting to use DataTables). Seems like there are tradeoffs that I'll have to look into at a later date (a performance hit just in IE8 would seem preferable to shifting more processing load onto the embedded controller, but I'd have to examine the design).
Allan: I had already tried these suggestions. I've appreciated your help - it seems we've taken this as far as we can.
For now I'm tolerating the IE8 performance issues and sticking with client-side processing. I'll come back to server-side processing, or possibly alternative designs, if I have time in future.
Why don't you just put this function as a plugin on your website - someone else might also find it very useful
Allan