2010-08-27 20 views

回答

8

AST解釋與字節碼的主要勝利是操作調度成本,對於高度優化的解釋器,這開始成爲一個真正的問題。 「調度」是用於描述開始執行操作所需的開銷(例如算術,屬性訪問等)的術語。基於

還算正常AST解釋會是這個樣子:

class ASTNode { 
    virtual double execute() = 0; 
} 

class NumberNode { 
    virtual double execute() { return m_value; } 
    double m_value; 
} 

class AddNode { 
    virtual double execute() { return left->execute() + right->execute(); } 
} 

所以執行如1+1一樣簡單的代碼需要3個虛擬電話。虛擬呼叫是非常昂貴的(在事物的宏偉計劃中),這是由於呼叫的多重間接,以及首先撥打電話的總成本。

在一個字節碼解釋你有不同的調度模型 - 而不是虛擬的電話你有一個執行循環,類似於:

while (1) { 
    switch (op.type) { 
     case op_add: 
      // Efficient interpreters use "registers" rather than 
      // a stack these days, but the example code would be more 
      // complicated 
      push(pop() + pop()); 
      continue; 
     case op_end: 
      return pop(); 
    } 
} 

這仍然具有相當昂貴的成本派遣VS本地代碼,但比虛擬調度快得多。您可以使用名爲「computed goto」的gcc擴展來進一步提高性能,從而允許您刪除交換機調度,從而將總調度成本降低到基本上單個間接分支。

除了提高分派成本基於字節碼的解釋器還有一些優於AST解釋器的優點,主要是由於字節碼能夠像真機一樣「直接」跳轉到其他位置,例如想象一個片段像這樣的代碼:

while (1) { 
    ...statements... 
    if (a) 
     break; 
    else 
     continue; 
} 

爲了實現這個正確每次一個語句執行則需要指出的執行是否打算留在循環或停止,所以執行循環變得像:

while (condition->execute() == true) { 
    for (i = 0; i < statements->length(); i++) { 
     result = statements[i]->execute(); 
     if (result.type == BREAK) 
      break; 
     if (result.type == CONTINUE) 
      i = 0; 
    } 
} 

隨着您添加更多形式的流量控制,此信號變得越來越昂貴。一旦添加了異常(例如可以在任何地方發生的流量控制),您甚至需要在基本算術中檢查這些事情,從而導致不斷增加的開銷。如果你想在現實世界中看到這一點,我鼓勵你看看ECMAScript規範,他們在這裏用AST解釋器來描述執行模型。

在字節碼解釋器中,這些問題基本消失了,因爲字節碼能夠直接表示控制流而不是通過信號間接表達,例如。 continue只是簡單地轉換成跳轉指令,如果它實際被觸發,你只能得到這個成本。

最後定義的AST解釋是遞歸的,所以必須從溢出系統堆棧,這使多少你可以在你的代碼遞歸非常沉重的限制阻止,是這樣的:

1+(1+(1+(1+(1+(1+(1+(1+1))))))) 

在翻譯中有8個遞歸層次(至少) - 這可能是一個非常重要的成本;老版本的Safari(SquirrelFish之前版本)使用AST解釋器,爲此,JS僅允許在現代瀏覽器中允許數百個遞歸級別與1000個遞歸級別相比。

+0

謝謝,有道理:) – 2010-08-27 23:47:03

1

也許你可以看看llvm「opt」工具提供的各種方法。這些是字節碼到字節碼的優化,而工具本身將提供應用特定優化的好處的分析。