Permalink
Browse files

Update: Significant update to how sorting is applied internally in Da…

…taTables - there is no difference to how the sort is actually done, with the single exception that the -asc and -desc are not depricated in favour of the -pre method only.

- With the introduction of the -pre method in DataTables 1.9, the -asc and -desc sorting functions became more or less redundant since they are simple comparisons (all of the complexity is now in the -pre formatting function). As such the call to the -asc / -desc method is overhead that really isn't needed, and this commit introduces a sort function that doesn't call the -asc / -desc methods, instead just doing the comparison itself. In tests, this relatively simple change leads to a performance improvement of around 15% in all browsers (it also has the side benefit of less operations, so IE8- will be able to sort larger tables before flagging up a slow script warning).

- We can't just remove the sorting method which will call -asc / -desc though since not all sorting plug-ins will have a -pre method. Therefore, for backwards compatiblity the old sort function (albeit updated for the changed variables) is retained. The backwards compatibality code adds around 300 bytes to the library, but this is an unaccounced change, so backwards compatiblity must be retained.

- The old sort method will be removed in v1.11. The -asc and -desc methods are now fully depricated.

- Altered the sorting method to flatten the aaSorting array since the introduction of aDataSort in v1.9 required an extra loop in several locations. The functionality is very useful, but the extra loop can be a bit messy and slightly hit performance, so it is now flattened to be a single array (with object information so it makes sense, rather htan array indexes!).

- Altered the order of sorting when building _aSortData since it was looking up the same variable smultiple times which really wasn't needed.

This is part of a small incremental changes plan for DataTables! There are still a huge number of things to improve in this area, but this is a nice clean up and a nice 15% sorting performance improvement to get us started :-).
  • Loading branch information...
1 parent 6900a59 commit 6b605936f794c0ec83cd5a63a32c4f995f6254d0 @DataTables committed Sep 29, 2012
Showing with 245 additions and 171 deletions.
  1. +116 −82 media/js/jquery.dataTables.js
  2. +112 −48 media/src/core/core.sort.js
  3. +1 −30 media/src/ext/ext.sorting.js
  4. +16 −11 media/unit_testing/performance/large.php
View
198 media/js/jquery.dataTables.js
@@ -3838,34 +3838,65 @@
* @param {object} oSettings dataTables settings object
* @param {bool} bApplyClasses optional - should we apply classes or not
* @memberof DataTable#oApi
+ * @todo This really needs split up!
*/
function _fnSort ( oSettings, bApplyClasses )
{
var
i, iLen, j, jLen, k, kLen,
sDataType, nTh,
- aaSort = [],
+ aSort = [],
aiOrig = [],
- oSort = DataTable.ext.oSort,
+ oExtSort = DataTable.ext.oSort,
aoData = oSettings.aoData,
aoColumns = oSettings.aoColumns,
- oAria = oSettings.oLanguage.oAria;
-
- /* No sorting required if server-side or no sorting array */
- if ( !oSettings.oFeatures.bServerSide &&
- (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
- {
- aaSort = ( oSettings.aaSortingFixed !== null ) ?
+ oAria = oSettings.oLanguage.oAria,
+ fnFormatter, aDataSort, data, iCol, sType, oSort,
+ iFormatters = 0,
+ aaNestedSort = ( oSettings.aaSortingFixed !== null ) ?
oSettings.aaSortingFixed.concat( oSettings.aaSorting ) :
oSettings.aaSorting.slice();
-
+
+ /* Flatten the aDataSort inner arrays into a single array, otherwise we have nested
+ * loops in multiple locations
+ */
+ for ( i=0 ; i<aaNestedSort.length ; i++ )
+ {
+ aDataSort = aoColumns[ aaNestedSort[i][0] ].aDataSort;
+
+ for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
+ {
+ iCol = aDataSort[k];
+ sType = aoColumns[ iCol ].sType || 'string';
+ fnFormatter = oExtSort[ sType+"-pre" ];
+
+ aSort.push( {
+ col: iCol,
+ dir: aaNestedSort[i][1],
+ index: aaNestedSort[i][2],
+ type: sType,
+ format: fnFormatter
+ } );
+
+ // Track if we can use the formatter method
+ if ( fnFormatter )
+ {
+ iFormatters++;
+ }
+ }
+ }
+
+ /* No sorting required if server-side or no sorting array */
+ if ( !oSettings.oFeatures.bServerSide && aSort.length !== 0 )
+ {
/* If there is a sorting data type, and a function belonging to it, then we need to
* get the data from the developer's function and apply it for this column
*/
- for ( i=0 ; i<aaSort.length ; i++ )
+ for ( i=0 ; i<aSort.length ; i++ )
{
- var iColumn = aaSort[i][0];
+ var iColumn = aSort[i].col;
var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
+
sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
if ( DataTable.ext.afnSortData[sDataType] )
{
@@ -3885,6 +3916,7 @@
}
}
}
+
/* Create a value - key array of the current row positions such that we can use their
* current position during the sort, if values match, in order to perform stable sorting
@@ -3898,23 +3930,17 @@
* the data to be sorted only once, rather than needing to do it every time the sorting
* function runs. This make the sorting function a very simple comparison
*/
- var iSortLen = aaSort.length;
- var fnSortFormat, aDataSort;
- for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
+ for ( j=0 ; j<aSort.length ; j++ )
{
- for ( j=0 ; j<iSortLen ; j++ )
+ oSort = aSort[j];
+
+ for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
{
- aDataSort = aoColumns[ aaSort[j][0] ].aDataSort;
+ data = _fnGetCellData( oSettings, i, oSort.col, 'sort' );
- for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
- {
- sDataType = aoColumns[ aDataSort[k] ].sType;
- fnSortFormat = oSort[ (sDataType ? sDataType : 'string')+"-pre" ];
-
- aoData[i]._aSortData[ aDataSort[k] ] = fnSortFormat ?
- fnSortFormat( _fnGetCellData( oSettings, i, aDataSort[k], 'sort' ) ) :
- _fnGetCellData( oSettings, i, aDataSort[k], 'sort' );
- }
+ aoData[i]._aSortData[ oSort.col ] = oSort.format ?
+ oSort.format( data ) :
+ data;
}
}
@@ -3934,31 +3960,69 @@
* Basically we have a test for each sorting column, if the data in that column is equal,
* test the next column. If all columns match, then we use a numeric sort on the row
* positions in the original data array to provide a stable sort.
+ *
+ * Note - I know it seems excessive to have two sorting methods, but the first is around
+ * 15% faster, so the second is only maintained for backwards compatibility with sorting
+ * methods which do not have a pre-sort formatting function.
*/
- oSettings.aiDisplayMaster.sort( function ( a, b ) {
- var k, l, lLen, iTest, aDataSort, sDataType;
- for ( k=0 ; k<iSortLen ; k++ )
- {
- aDataSort = aoColumns[ aaSort[k][0] ].aDataSort;
-
- for ( l=0, lLen=aDataSort.length ; l<lLen ; l++ )
+ if ( iFormatters === aSort.length ) {
+ // All sort types have formatting functions
+ oSettings.aiDisplayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; k<len ; k++ )
{
- sDataType = aoColumns[ aDataSort[l] ].sType;
-
- iTest = oSort[ (sDataType ? sDataType : 'string')+"-"+aaSort[k][1] ](
- aoData[a]._aSortData[ aDataSort[l] ],
- aoData[b]._aSortData[ aDataSort[l] ]
- );
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ test = x<y ? -1 : x>y ? 1 : 0;
+ if ( test !== 0 )
+ {
+ return sort.dir === 'asc' ? test : -test;
+ }
+ }
- if ( iTest !== 0 )
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
+ else {
+ // Depreciated - remove in 1.11 (providing a plug-in option)
+ // Not all sort types have formatting methods, so we have to call their sorting
+ // methods.
+ oSettings.aiDisplayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, l, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; k<len ; k++ )
+ {
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ test = oExtSort[ sort.type+"-"+sort.dir ]( x, y );
+ if ( test !== 0 )
{
- return iTest;
+ return test;
}
}
- }
-
- return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
- } );
+
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
}
/* Alter the sorting classes to take account of the changes */
@@ -3977,12 +4041,12 @@
/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
if ( aoColumns[i].bSortable )
{
- if ( aaSort.length > 0 && aaSort[0][0] == i )
+ if ( aSort.length > 0 && aSort[0].col == i )
{
- nTh.setAttribute('aria-sort', aaSort[0][1]=="asc" ? "ascending" : "descending" );
+ nTh.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
- var nextSort = (aoColumns[i].asSorting[ aaSort[0][2]+1 ]) ?
- aoColumns[i].asSorting[ aaSort[0][2]+1 ] : aoColumns[i].asSorting[0];
+ var nextSort = (aoColumns[i].asSorting[ aSort[0].index+1 ]) ?
+ aoColumns[i].asSorting[ aSort[0].index+1 ] : aoColumns[i].asSorting[0];
nTh.setAttribute('aria-label', sTitle+
(nextSort=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
}
@@ -6221,7 +6285,6 @@
"_fnGetTdNodes": _fnGetTdNodes,
"_fnEscapeRegex": _fnEscapeRegex,
"_fnDeleteIndex": _fnDeleteIndex,
- "_fnReOrderIndex": _fnReOrderIndex,
"_fnColumnOrdering": _fnColumnOrdering,
"_fnLog": _fnLog,
"_fnClearTable": _fnClearTable,
@@ -8244,8 +8307,8 @@
/**
- * Enable or disable the addition of the classes 'sorting_1', 'sorting_2' and
- * 'sorting_3' to the columns which are currently being sorted on. This is
+ * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
+ * `sorting\_3` to the columns which are currently being sorted on. This is
* presented as a feature switch as it can increase processing time (while
* classes are removed and added) so for large data sets you might want to
* turn this off.
@@ -11640,6 +11703,7 @@
return a.toLowerCase();
},
+ // string-asc and -desc are retained only for compatibility with
"string-asc": function ( x, y )
{
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
@@ -11659,16 +11723,6 @@
return a.replace( /<.*?>/g, "" ).toLowerCase();
},
- "html-asc": function ( x, y )
- {
- return ((x < y) ? -1 : ((x > y) ? 1 : 0));
- },
-
- "html-desc": function ( x, y )
- {
- return ((x < y) ? 1 : ((x > y) ? -1 : 0));
- },
-
/*
* date sorting
@@ -11683,16 +11737,6 @@
}
return x;
},
-
- "date-asc": function ( x, y )
- {
- return x - y;
- },
-
- "date-desc": function ( x, y )
- {
- return y - x;
- },
/*
@@ -11702,16 +11746,6 @@
{
return (a=="-" || a==="") ? 0 : a*1;
},
-
- "numeric-asc": function ( x, y )
- {
- return x - y;
- },
-
- "numeric-desc": function ( x, y )
- {
- return y - x;
- }
} );
View
160 media/src/core/core.sort.js
@@ -3,34 +3,65 @@
* @param {object} oSettings dataTables settings object
* @param {bool} bApplyClasses optional - should we apply classes or not
* @memberof DataTable#oApi
+ * @todo This really needs split up!
*/
function _fnSort ( oSettings, bApplyClasses )
{
var
i, iLen, j, jLen, k, kLen,
sDataType, nTh,
- aaSort = [],
+ aSort = [],
aiOrig = [],
- oSort = DataTable.ext.oSort,
+ oExtSort = DataTable.ext.oSort,
aoData = oSettings.aoData,
aoColumns = oSettings.aoColumns,
- oAria = oSettings.oLanguage.oAria;
-
- /* No sorting required if server-side or no sorting array */
- if ( !oSettings.oFeatures.bServerSide &&
- (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
- {
- aaSort = ( oSettings.aaSortingFixed !== null ) ?
+ oAria = oSettings.oLanguage.oAria,
+ fnFormatter, aDataSort, data, iCol, sType, oSort,
+ iFormatters = 0,
+ aaNestedSort = ( oSettings.aaSortingFixed !== null ) ?
oSettings.aaSortingFixed.concat( oSettings.aaSorting ) :
oSettings.aaSorting.slice();
-
+
+ /* Flatten the aDataSort inner arrays into a single array, otherwise we have nested
+ * loops in multiple locations
+ */
+ for ( i=0 ; i<aaNestedSort.length ; i++ )
+ {
+ aDataSort = aoColumns[ aaNestedSort[i][0] ].aDataSort;
+
+ for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
+ {
+ iCol = aDataSort[k];
+ sType = aoColumns[ iCol ].sType || 'string';
+ fnFormatter = oExtSort[ sType+"-pre" ];
+
+ aSort.push( {
+ col: iCol,
+ dir: aaNestedSort[i][1],
+ index: aaNestedSort[i][2],
+ type: sType,
+ format: fnFormatter
+ } );
+
+ // Track if we can use the formatter method
+ if ( fnFormatter )
+ {
+ iFormatters++;
+ }
+ }
+ }
+
+ /* No sorting required if server-side or no sorting array */
+ if ( !oSettings.oFeatures.bServerSide && aSort.length !== 0 )
+ {
/* If there is a sorting data type, and a function belonging to it, then we need to
* get the data from the developer's function and apply it for this column
*/
- for ( i=0 ; i<aaSort.length ; i++ )
+ for ( i=0 ; i<aSort.length ; i++ )
{
- var iColumn = aaSort[i][0];
+ var iColumn = aSort[i].col;
var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
+
sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
if ( DataTable.ext.afnSortData[sDataType] )
{
@@ -50,6 +81,7 @@ function _fnSort ( oSettings, bApplyClasses )
}
}
}
+
/* Create a value - key array of the current row positions such that we can use their
* current position during the sort, if values match, in order to perform stable sorting
@@ -63,23 +95,17 @@ function _fnSort ( oSettings, bApplyClasses )
* the data to be sorted only once, rather than needing to do it every time the sorting
* function runs. This make the sorting function a very simple comparison
*/
- var iSortLen = aaSort.length;
- var fnSortFormat, aDataSort;
- for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
+ for ( j=0 ; j<aSort.length ; j++ )
{
- for ( j=0 ; j<iSortLen ; j++ )
+ oSort = aSort[j];
+
+ for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
{
- aDataSort = aoColumns[ aaSort[j][0] ].aDataSort;
+ data = _fnGetCellData( oSettings, i, oSort.col, 'sort' );
- for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
- {
- sDataType = aoColumns[ aDataSort[k] ].sType;
- fnSortFormat = oSort[ (sDataType ? sDataType : 'string')+"-pre" ];
-
- aoData[i]._aSortData[ aDataSort[k] ] = fnSortFormat ?
- fnSortFormat( _fnGetCellData( oSettings, i, aDataSort[k], 'sort' ) ) :
- _fnGetCellData( oSettings, i, aDataSort[k], 'sort' );
- }
+ aoData[i]._aSortData[ oSort.col ] = oSort.format ?
+ oSort.format( data ) :
+ data;
}
}
@@ -99,31 +125,69 @@ function _fnSort ( oSettings, bApplyClasses )
* Basically we have a test for each sorting column, if the data in that column is equal,
* test the next column. If all columns match, then we use a numeric sort on the row
* positions in the original data array to provide a stable sort.
+ *
+ * Note - I know it seems excessive to have two sorting methods, but the first is around
+ * 15% faster, so the second is only maintained for backwards compatibility with sorting
+ * methods which do not have a pre-sort formatting function.
*/
- oSettings.aiDisplayMaster.sort( function ( a, b ) {
- var k, l, lLen, iTest, aDataSort, sDataType;
- for ( k=0 ; k<iSortLen ; k++ )
- {
- aDataSort = aoColumns[ aaSort[k][0] ].aDataSort;
+ if ( iFormatters === aSort.length ) {
+ // All sort types have formatting functions
+ oSettings.aiDisplayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
- for ( l=0, lLen=aDataSort.length ; l<lLen ; l++ )
+ for ( k=0 ; k<len ; k++ )
{
- sDataType = aoColumns[ aDataSort[l] ].sType;
-
- iTest = oSort[ (sDataType ? sDataType : 'string')+"-"+aaSort[k][1] ](
- aoData[a]._aSortData[ aDataSort[l] ],
- aoData[b]._aSortData[ aDataSort[l] ]
- );
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ test = x<y ? -1 : x>y ? 1 : 0;
+ if ( test !== 0 )
+ {
+ return sort.dir === 'asc' ? test : -test;
+ }
+ }
- if ( iTest !== 0 )
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
+ else {
+ // Depreciated - remove in 1.11 (providing a plug-in option)
+ // Not all sort types have formatting methods, so we have to call their sorting
+ // methods.
+ oSettings.aiDisplayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, l, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; k<len ; k++ )
+ {
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ test = oExtSort[ sort.type+"-"+sort.dir ]( x, y );
+ if ( test !== 0 )
{
- return iTest;
+ return test;
}
}
- }
-
- return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
- } );
+
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
}
/* Alter the sorting classes to take account of the changes */
@@ -142,12 +206,12 @@ function _fnSort ( oSettings, bApplyClasses )
/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
if ( aoColumns[i].bSortable )
{
- if ( aaSort.length > 0 && aaSort[0][0] == i )
+ if ( aSort.length > 0 && aSort[0].col == i )
{
- nTh.setAttribute('aria-sort', aaSort[0][1]=="asc" ? "ascending" : "descending" );
+ nTh.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
- var nextSort = (aoColumns[i].asSorting[ aaSort[0][2]+1 ]) ?
- aoColumns[i].asSorting[ aaSort[0][2]+1 ] : aoColumns[i].asSorting[0];
+ var nextSort = (aoColumns[i].asSorting[ aSort[0].index+1 ]) ?
+ aoColumns[i].asSorting[ aSort[0].index+1 ] : aoColumns[i].asSorting[0];
nTh.setAttribute('aria-label', sTitle+
(nextSort=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
}
View
31 media/src/ext/ext.sorting.js
@@ -11,6 +11,7 @@ $.extend( DataTable.ext.oSort, {
return a.toLowerCase();
},
+ // string-asc and -desc are retained only for compatibility with
"string-asc": function ( x, y )
{
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
@@ -30,16 +31,6 @@ $.extend( DataTable.ext.oSort, {
return a.replace( /<.*?>/g, "" ).toLowerCase();
},
- "html-asc": function ( x, y )
- {
- return ((x < y) ? -1 : ((x > y) ? 1 : 0));
- },
-
- "html-desc": function ( x, y )
- {
- return ((x < y) ? 1 : ((x > y) ? -1 : 0));
- },
-
/*
* date sorting
@@ -54,16 +45,6 @@ $.extend( DataTable.ext.oSort, {
}
return x;
},
-
- "date-asc": function ( x, y )
- {
- return x - y;
- },
-
- "date-desc": function ( x, y )
- {
- return y - x;
- },
/*
@@ -73,14 +54,4 @@ $.extend( DataTable.ext.oSort, {
{
return (a=="-" || a==="") ? 0 : a*1;
},
-
- "numeric-asc": function ( x, y )
- {
- return x - y;
- },
-
- "numeric-desc": function ( x, y )
- {
- return y - x;
- }
} );
View
27 media/unit_testing/performance/large.php
@@ -23,25 +23,30 @@
<script type="text/javascript" language="javascript" src="../../js/jquery.dataTables.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
- var oTable = $('#example').dataTable();
- var iStart = new Date().getTime();
//if ( typeof console != 'undefined' ) {
// console.profile();
//}
- for ( var i=0 ; i<10 ; i++ )
- {
- var oTable = $('#example').dataTable({"bDestroy": true});
- }
+ var oTable = $('#example').dataTable();
//if ( typeof console != 'undefined' ) {
// console.profileEnd();
//}
- //oTable.fnSort( [[ 1, 'asc' ]] );
- //oTable.fnSort( [[ 1, 'asc' ]] );
- //oTable.fnSort( [[ 2, 'asc' ]] );
- //oTable.fnSort( [[ 1, 'asc' ]] );
- //oTable.fnSort( [[ 2, 'asc' ]] );
+ var iStart = new Date().getTime();
+
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
+ oTable.fnSort( [[ 1, 'asc' ]] );
+ oTable.fnSort( [[ 2, 'asc' ]] );
var iEnd = new Date().getTime();
document.getElementById('output').innerHTML = "Test took "+(iEnd-iStart)+" mS";

0 comments on commit 6b60593

Please sign in to comment.