V8 vs JS 1.7(firefox2) vs JS 1.8(firefox3)陣列排序速度比較
Mon, 08 Sep 2008 17:06:07 +0800前幾天試著練習把SpiderMonkey整合進程式中,寫了一個簡單的程式來執行javascript檔案,global物件除了標準以外提供了print函數來列印訊息。
今天下載了V8,編譯了他的shell sample來試試看。我電腦裡還留著幾年前編譯的JS 1.7,也同樣有一個jsshell,所以就把之前做的陣列排序測試用這三個來測試一下。結果大致符合預期,JS 1.8還是贏過V8。
先看看測試的javascript:
var query;
var getquery;
try {
(function(){
query = function(_cm) {
var order=[];
var selection=[];
var from=[];
var criteria=[];
var limit=[];
var result=[];
var cm = _cm
/**
// valid format for parameter a is
// [int,int,int...]
// each number for zero based index number to select from the from array
*/
this.select = function(a) {
if (a instanceof Array) {
selection = a;
} else {
print("The argument for 'select' function must be an Array");
}
return this;
}
/**
// valid format for parameter a is Array
*/
this.from = function(a) {
if (a instanceof Array) {
from = a;
} else {
print("The argument for 'select' function must be an Array");
}
return this;
}
/**
// valid format for parameter a is
// [[[and1],[and2],...], [[or1],[or2],...]]
// each criteria is in the following format:
// ["index", "compare method","value"]
// index is the zero based number of the column index of from array
// compare methods maybe one of the following:
// "eq", "gt", "lt", "gteq", "lteq"....
*/
this.where = function(a) {
if (a instanceof Array) {
criteria = a;
} else {
print("The argument for 'select' function must be an Array");
}
return this;
}
/**
// valid format for parameter a is
// ["index","sort method"]
// index is the zero based number of the column index of the from array which the sort action depends on
// sort method may be one of the following:
// "desc", "asc"
*/
this.orderby = function(a) {
if (a instanceof Array) {
order = a;
} else {
print("The argument for 'select' function must be an Array");
}
return this;
}
/**
// valid format for paramater a is
// ["start", "limit"]
// where start is the zero based index of the from array the result array start
// and the limit is the length of the result array
*/
this.limit = function(a) {
if (a instanceof Array) {
limit = a;
} else {
print("The argument for 'select' function must be an Array");
}
return this;
}
function comp(a,b) {
var col = 0;
for(var i=0; i<selection.length; i++) {
if(selection[i] == order[0]) col = i;
}
if(!isNaN(a[col]) || !isNaN(b[col])) {
return (order[1]=="asc")? a[col]-b[col]:0-a[col]+b[col];
}else{
var factor = 10;
var tmp1 = a[col].toString();
var tmp2 = b[col].toString();
var r1 = 0;
var r2 = 0;
var n1 = (tmp1.length>factor)? factor:tmp1.length;
var n2 = (tmp2.length>factor)? factor:tmp2.length;
for(var m=0; m<n1; m++) {
r1 = r1*32 + tmp1.charCodeAt(m);
}
for(var m=0; m<n2; m++) {
r2 = r2*32 + tmp2.charCodeAt(m);
}
return (order[1]=="asc")? r1-r2:0-r1+r2;
}
}
/**
// the final treatment function
// the flow is as the following:
// 1. prepare to treat each item of the from array
// 2. if no criteria, then put the "selection" fields into ret array
// 3. if there're criterias, then do the following treatment:
// 3.1. sum up the "AND" criteria and the result in selornot1
// 3.2. sum up the "OR" criteria and the result in selornot2
// 3.3. determin the result by combine the two result using "OR" logic
// 4. sort the ret array according to the "order" condition
// 5. return the ret array as result
*/
this.exec = function() {
result = [];
if(!selection.length>0) {
return [];
}
if(!from.length>0) {
return [];
}
var ret = [];
// step 1.
for(var i=0; i<from.length; i++) {
// step 2.
if(criteria.length==0) {
var tmp = [];
if(selection.length==1 || selection[0] == "*") {
tmp = from[i];
} else {
for(var j in selection) {
tmp.push(from[i][selection[j]]);
}
}
ret.push(tmp);
// step 3.
} else {
// if there're existing criterias...
for(var i=0; i<from.length; i++) {
//tranversing all AND criteria to determin which row to add to the result set
//to speed up the query, any "FALSE" condition will force the loop to stop and give the result to "FALSE"
var selornot1 = false;
// 3.1
if(criteria.length > 0) {
for(var m=0; m<criteria[0].length; m++) {
if(cm[criteria[0][m][1]]!==undefined) {
selornot1 = cm[criteria[0][m][1]](from[i][criteria[0][m][0]], criteria[0][m][2]);
if(!selornot1) break;
}
}
}
//tranversing all AND criteria to determin which row to add to the result set
//to speed up the query, any "TRUE" condition will force the loop to stop and give the result to "TRUE"
var selornot2 = false;
// 3.2
if(criteria.length > 1) {
for(var m=0; m<criteria[1].length; m++){
if(cm[criteria[1][m][1]]!==undefined) {
selornot2 = cm[criteria[1][m][1]](from[i][criteria[1][m][0]], criteria[1][m][2]);
if(selornot2) break;
}
}
}
// 3.3
if(selornot1 || selornot2) {
var tmp = [];
if(selection.length==1 || selection[0] == "*") {
tmp = from[i];
} else {
for(var j in selection) {
tmp.push(from[i][selection[j]]);
}
}
ret.push(tmp);
}
}
}
}
// step 4.
if(order.length > 0) {
ret.sort(comp);
}
// step 5.
if(limit.length>0) {
result = ret.slice(limit[0],limit[0]+limit[1]);
return ret.slice(limit[0],limit[0]+limit[1]);
} else {
result = ret;
return ret;
}
}
this.result = function() {
return result;
}
this.count = function() {
return result.length;
}
this.test = function() {
print(selection);
print(from);
print(criteria);
print(order);
print(limit);
}
}
var cm = {
"eq": function(a,b) {
if(a==b) return true;
return false;
},
"gt": function(a,b) {
if(a>b) return true;
return false;
},
"lt": function(a,b) {
if(a<b) return true;
return false;
},
"gteq": function(a,b) {
if(a>=b) return true;
return false;
},
"lteq": function(a,b) {
if(a<=b) return true;
return false;
}
};
getquery = function() {
return new query(cm);
}
})();
}catch(e){print(e);}
try{
(function(){
var b = [];
for (var i=0; i<10000; i++) {
var j=10000+i;
var tmp = [];
tmp.push(i);
tmp.push(i+1);
tmp.push(i+2);
tmp.push(String.fromCharCode(j,j+1,j+2));
b.push(tmp);
}
function test002() {
var a = getquery();
a.select(['*']).from(b).orderby([3,"asc"]);
var t1 = new Date().getTime();
var c = a.exec();
print("asc "+a.count()+" rows: "+(new Date().getTime() - t1)+"ms\n");
a.orderby([3,"desc"]);
var t2 = new Date().getTime();
c = a.exec();
print("desc "+a.count()+" rows: "+(new Date().getTime() - t2)+"ms\n");
a.orderby([]);
var t3 = new Date().getTime();
c = a.exec();
print("no sort "+a.count()+" rows: "+(new Date().getTime() - t3)+"ms\n");
}
test002();
})();
}catch(e){print(e);}
接下來用三支程式來做測試。js001.exe是我練習整合SpiderMonkey時做的,使用動態連結方式呼叫在js32.dll等裡面的函數來執行javascript(這些dll是我直接從Firefox3目錄裡面拷貝過來的)。jsshell.exe是從JS 1.7原始碼編譯的。shell.exe則是V8提供的sample,在編譯時下sample=shell參數就可以編譯出來(靜態編譯)。執行程式以後,再利用他提供的load函數來載入測試程式。以下是簡單的測試結果畫面:
首先是jsshell.exe(js 1.7, firefox2):
其次是js001.exe(js 1.8, firefox3):
最後是shell.exe(V8, google chrome):
照測試結果,一般javascript速度是V8(Chrome) > JS 1.8(Firefox3) > JS 1.7(Firefox2),但是陣列排序速度是JS 1.8(Firefox3) > V8(Chrome) > JS 1.7(Firefox2)。因為Array.sort會呼叫我自己寫的排序函數,所以強烈懷疑這個結果是因為JS 1.8的context switching速度比較快的關係。
有空時把V8整合到程式裡面以後,再做一次測試吧。
阿,文貼上來才發現,其實shell這一支只要傳檔名當做參數就可以了,我進去shell再用load跑有一點多此一舉。但是速度差不多。
剛剛發現為什麼陣列排序速度會比較慢了。我剛剛在V8的Issue List上看到:Issue 5: Poor Performance in Benchmark,進去V8的src目錄下,會看到array.js,打開看看,嗯......看起來是真的。所以原因就是:在V8中,陣列是用Javascript來實作的喔!!!(其實不只啦,可以看到其他的js檔案,所以像Math也是...其他還沒看。)JS 理所當然是用C,編譯過後比較快是理所當然。V8也很厲害,他可以把javascript編譯到速度那麼快。JS 1.7的陣列當然還是用C實作的,但是速度比javascript編譯的還差一截呢。