2012-08-24 38 views
2

在我的應用程序中,我生成的JavaScript代碼遵循CPS風格。我'不'使用任何'延續'。沒有異步行爲,沒有暫停和恢復,沒有回調。JavaScript:直接代碼與CPS風格的生成代碼性能比較

只是代碼是在編程的continuation passing style之後。

功能有很多階段,每個階段都會處理並將結果傳遞給它的延續。

我發現的是CPS樣式代碼的性能非常差。以直接樣式編寫的代碼比CPS樣式代碼快150倍。

請檢查以下代碼。
下面兩個碼是等同於

var res = data.store.bookshelf.book.author; 

直接樣式化代碼:

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}}; 
var t1 = new Date().getTime(); 
for(var i = 0; i < 1000*1000*100; i+=1){  
    var temp0 = data; 
    var temp1 = temp0.store; 
    var temp2 = temp1.bookshelf; 
    var temp3 = temp2.book; 
    var temp4 = temp3.author; 
    var res = temp4; 
} 
var t2 = new Date().getTime(); 
console.log(t2-t1); 

上面的代碼運行在近95毫秒。

CPS樂府代碼:

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}}; 

// return the variable to the continuation 
function cps_VARREF(x,f){ 
    return f(x); 
} 
// get the value of the property from the variable and pass it to the continuation 
function cps_CHILD(x,child,f){ 
    return f(x[child]); 
} 
// simply return the input value, essentially closing the continuation chain 
function ret_(x){ 
    return x; 
} 

var t1 = new Date().getTime(); 
for(var i = 0; i < 1000*1000*100; i+=1){ 
var res = function(c_){ 
    return cps_VARREF(data,function(x1){ 
    return cps_CHILD(x1,"store",function(x2){ 
    return cps_CHILD(x2,"bookshelf",function(x3){ 
    return cps_CHILD(x3,"book",function(x4){ 
    return cps_CHILD(x4,"author",c_);});});});});}(ret_); 
} 
var t2 = new Date().getTime(); 
console.log(t2-t1); 

以上CPS風格的代碼在15000毫秒

運行有什麼我可以做,以提高代碼風格的CPS?或者JavaScript本質上不適用於CPS風格的代碼?

上述試驗是在node.js的版本0.6.12

做某人能請扔在這個問題上有些輕?

感謝,

+0

瀏覽器測試一個http://jsperf.com/copast-vs-direct只顯示1:10運行時間比例 – Bergi

回答

1

至少有兩個潛在原因導致經濟大幅放緩。首先是你要用動態查找替換「本地」屬性查找。

V8會盡可能優化對象,以便訪問屬性更快。它並不使用散列表來查找名稱屬性,而是跟蹤內部「類」,以便可以從已知地址查找屬性。所以data.store只是一個快速指針比較,以確保對象是預期類型和索引指針加載。

但在cps_CHILD函數中,它不能進行這種優化,因爲它不知道哪些屬性會提前被訪問(並且每次調用它時都會發生更改)。動態查找強制V8回退到散列表查找,這比優化的靜態查找要慢。

另一個問題是函數調用的開銷。每次嵌套函數在每次傳遞到下一個函數時都必須重新創建。它們不需要每次編譯,但仍需要在新的上下文中創建。

+0

時減慢性能感謝@Matthew。關於「本地」與財產的動態查詢,我早先在SO中提出過問題,我得到了兩個答案都一樣的答案。我的測試結果也表明,兩者的表現相同。 http://stackoverflow.com/questions/11580448/javascript-property-access-speed-difference-var-property-vs-varproperty – weima

+0

@ weima如果你總是用字符串文字查找同一個屬性,那麼這是真的。編譯器足夠聰明,可以對其進行相同處理。問題是當你將屬性名稱傳遞給一個函數時,該函數並不會提前知道該字符串是什麼,所以它不能優化它。 –

+0

我創建了一個測試案例來證明不同之處。使用常量字符串的索引與「obj.property」語法幾乎相同,但兩者的速度比每次使用不同屬性名稱的動態查找的速度快兩倍。測試用例如下:http://jsperf.com/static-vs-dynamic –

0

你應該知道,有些事情是在運行時解析,就像你在循環進入那些匿名函數。在每個新的「我」再次創建新的「構造函數」和它的每個匿名函數的原型。這就是爲什麼它比較慢。這是新的測試。嘗試爲每個匿名函數定義實際函數,將它們嵌套在循環之外,它應該提高程序的性能。

下面是代碼,它深藏在你的CPS風格的代碼,但只有一次函數定義

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}}; 

function getValueFrom(data){ 
    return data; 
} 

function getAuthorFrom(data){ 
    return getValueFrom(data); 
} 

function getBookFrom(data){ 
    return getAuthorFrom(data["book"]); 
} 

function getBookShelfFrom(data){ 
    return getBookFrom(data["bookshelf"]); 
} 

function getStoreFrom(data){ 
    return getBookShelfFrom(data["store"]); 
} 

function getAuthor(data){ 
    return getAuthor(data); 
} 

var t1 = new Date().getTime(); 
for(var i = 0; i < 1000*1000*100; i+=1){ 
var res = getStoreFrom(data); 
} 
var t2 = new Date().getTime(); 
console.log(t2-t1); 

它的運行速度更快的4-5倍。由於JS引擎需要尋找函數原型,所以它仍然比較慢,所以它可以放在堆上執行。在你的第一種情況下(非CSP風格),當你使用點訪問屬性時,JS引擎只能通過鍵(JS對象屬性)來查詢散列以獲得它的值。它工作得更快,因爲它只需要處理內存引用。

+0

這不是延續傳遞樣式(帶回調),雖然我認爲你的解釋是相當的對。 – Bergi

+0

這是上面的代碼點,它應該解釋什麼是當您使用CPS –