2013-02-11 65 views
5

我寫了一個虛擬機語言給我正在使用的計算機系統課程(使用nand2trtris課程)的程序集翻譯器。我最初是用Python編寫的,但是因爲我在學習D,所以我想我會翻譯它。 D在語法上與Python非常接近,所以它不是太難。我認爲D作爲一種性能語言並進行了編譯,至少和Python一樣快,並且在大文件上速度會更快。但事實恰恰相反!儘管算法相同,但是當我構建一個非常大的文件進行編譯時,D的執行速度比python稍慢。在一個大約50萬行的文件中,python一直花費大約2.6秒完成,而D一直花費大約3次。這並不是一個巨大的差距,但值得注意的是,python會更快。Python比D快嗎? IO操作似乎減慢了很多D ...發生了什麼?

我不想暗示我太天真了,認爲python實際上比D總體上快;然而,在這種情況下,似乎至少D不直觀地更快。我會很感激我的D代碼中可能的性能下降源的一些輸入。我認爲瓶頸可能在IO操作上,但我不確定。

源代碼如下。細節並不重要;分配一些彙編語言模板,然後通過虛擬機語言進行線性傳遞,將每條指令翻譯爲等效的彙編代碼塊。

編輯:在使用dmd -O -release -inline -m64重新編譯D代碼後,D在輸入端輸出時間爲2.20s。然而,問題仍然是爲什麼幾乎相同的代碼,D似乎比python執行速度慢。

編輯2:使用從下面的建議,我使用的字符串的簡單列表,到使用appender!string()切換,並且通過顯着的改善量的時間。值得然而提的是,如果你有一大堆的字符串在appender其寫入文件,像這樣的命令:

auto outputfile = File("foo.txt","w"); 
foreach(str; my_appender.data) 
    outputfile.write(str); 

而是相反,寫類似:

auto outputfile = File("foo.txt","w"); 
outputfile.write(my_appender.data); 

第二個例子會比使用簡單的string[]稍微提高性能。但使用第一個給我一個巨大的性能打擊,執行時間加倍。

更改爲appender!string(),上述大文件的編譯花費了大約2.75秒(對於Python的2.8),其中原始大約需要大約3次。這樣做並且還使用dmd中的優化標誌給出了總編譯時間爲1.98秒! :)

的Python:

#!/usr/bin/python 

import sys 

operations_dict = {"add":"+", "sub":"-", 
        "and":"&", "or":"|", 
        "not":"!", "neg":"-", 
        "lt":"JLT", "gt":"JGT", 
        "eq":"JEQ", "leq":"JLE", 
        "geq":"JGE"} 

vars_dict = {"this":("THIS","M"), 
      "that":("THAT","M"), 
      "argument":("ARG","M",), 
      "local":("LCL","M",), 
      "static":("f.%d","M",), 
      "temp":("TEMP","A",)} 

start = "@SP\nAM=M-1\n" 
end = "@SP\nM=M+1\n" 

binary_template = start + "D=M\n\ 
@SP\n\ 
AM=M-1\n\ 
M=M%sD\n" + end 

unary_template = start + "M=%sM\n" + end 

comp_template = start + "D=M\n\ 
@SP\n\ 
AM=M-1\n\ 
D=M-D\n\ 
@COMP.%d.TRUE\n\ 
D;%s\n\ 
@COMP.%d.FALSE\n\ 
0;JMP\n\ 
(COMP.%d.TRUE)\n\ 
@SP\n\ 
A=M\n\ 
M=-1\n\ 
@SP\n\ 
M=M+1\n\ 
@COMP.%d.END\n\ 
0;JMP\n\ 
(COMP.%d.FALSE)\n\ 
@SP\n\ 
A=M\n\ 
M=0\n" + end + "(COMP.%d.END)\n" 

push_tail_template = "@SP\n\ 
A=M\n\ 
M=D\n\ 
@SP\n\ 
M=M+1\n" 

push_const_template = "@%d\nD=A\n" + push_tail_template 

push_var_template = "@%d\n\ 
D=A\n\ 
@%s\n\ 
A=%s+D\n\ 
D=M\n" + push_tail_template 

push_staticpointer_template = "@%s\nD=M\n" + push_tail_template 

pop_template = "@%d\n\ 
D=A\n\ 
@%s\n\ 
D=%s+D\n\ 
@R13\n\ 
M=D\n\ 
@SP\n\ 
AM=M-1\n\ 
D=M\n\ 
@R13\n\ 
A=M\n\ 
M=D\n" 

pop_staticpointer_template = "@SP\n\ 
AM=M-1\n\ 
D=M\n\ 
@%s\n\ 
M=D" 


type_dict = {"add":"arithmetic", "sub":"arithmetic", 
        "and":"arithmetic", "or":"arithmetic", 
        "not":"arithmetic", "neg":"arithmetic", 
        "lt":"arithmetic", "gt":"arithmetic", 
        "eq":"arithmetic", "leq":"arithmetic", 
        "geq":"arithmetic", 
        "push":"memory", "pop":"memory"} 

binary_ops = ["add", "sub", "and", "or"] 
unary_ops = ["not", "neg"] 
comp_ops = ["lt", "gt", "eq", "leq", "geq"] 


op_count = 0 
line_count = 0 
output = ["// Assembly file generated by my awesome VM compiler\n"] 

def compile_operation(op): 
    global line_count 

    if (op[0:2] == "//") or (len(op.split()) == 0): 
     return "" 

    # print "input: " + op 
    operation = op.split()[0] 
    header = "// '" + op + "' (line " + str(line_count) + ")\n" 
    line_count += 1 

    if type_dict[operation] == "arithmetic": 
     return header + compile_arithmetic(op) 
    elif type_dict[operation] == "memory": 
     return header + compile_memory(op) 

def compile_arithmetic(op): 
    global op_count 
    out_string = "" 
    if op in comp_ops: 
     out_string += comp_template % (op_count, operations_dict[op], op_count, \ 
      op_count, op_count, op_count, op_count) 
     op_count += 1 
    elif op in unary_ops: 
     out_string += unary_template % operations_dict[op] 
    else: 
     out_string += binary_template % operations_dict[op] 
    return out_string 

def compile_memory(op): 
    global output 
    instructions = op.split() 
    inst = instructions[0] 
    argtype = instructions[1] 
    val = int(instructions[2]) 
    if inst == "push": 
     if argtype == "constant": 
      return push_const_template % val 
     elif argtype == "static": 
      return push_staticpointer_template % ("f." + str(val)) 
     elif argtype == "pointer": 
      if val == 0: 
       return push_staticpointer_template % ("THIS") 
      else: 
       return push_staticpointer_template % ("THAT") 
     else: 
      return push_var_template % (val, vars_dict[argtype][0], vars_dict[argtype][1]) 
    elif inst == "pop": 
     if argtype != "constant": 
      if argtype == "static": 
       return pop_staticpointer_template % ("f." + str(val)) 
      elif argtype == "pointer": 
       if val == 0: 
        return pop_staticpointer_template % "THIS" 
       else: 
        return pop_staticpointer_template % "THAT" 
      else: 
       return pop_template % (val, vars_dict[argtype][0], vars_dict[argtype][1]) 

def main(): 
    global output 

    if len(sys.argv) == 1: 
     inputfname = "test.txt" 
    else: 
     inputfname = sys.argv[1] 
    outputfname = inputfname.split('.')[0] + ".asm" 

    inputf = open(inputfname) 
    output += ["// Input filename: %s\n" % inputfname] 
    for line in inputf.readlines(): 
     output += [compile_operation(line.strip())] 

    outputf = open(outputfname, 'w') 
    for outl in output: 
     outputf.write(outl) 

    outputf.write("(END)\[email protected]\n0;JMP"); 
    inputf.close() 
    outputf.close() 
    print "Output written to " + outputfname 


if __name__ == "__main__": 
    main() 

d:

import std.stdio, std.string, std.conv, std.format, std.c.stdlib; 

string[string] operations_dict, type_dict; 
string[][string] vars_dict; 
string[] arithmetic, memory, comp_ops, unary_ops, binary_ops, lines, output; 
string start, end, binary_template, unary_template, 
     comp_template, push_tail_template, push_const_template, 
     push_var_template, push_staticpointer_template, 
     pop_template, pop_staticpointer_template; 
int op_count, line_count; 

void build_dictionaries() { 
    vars_dict = ["this":["THIS","M"], 
       "that":["THAT","M"], 
       "argument":["ARG","M"], 
       "local":["LCL","M"], 
       "static":["f.%d","M"], 
       "temp":["TEMP","A"]]; 

    operations_dict = ["add":"+", "sub":"-", 
        "and":"&", "or":"|", 
        "not":"!", "neg":"-", 
        "lt":"JLT", "gt":"JGT", 
        "eq":"JEQ", "leq":"JLE", 
        "geq":"JGE"]; 

    type_dict = ["add":"arithmetic", "sub":"arithmetic", 
        "and":"arithmetic", "or":"arithmetic", 
        "not":"arithmetic", "neg":"arithmetic", 
        "lt":"arithmetic", "gt":"arithmetic", 
        "eq":"arithmetic", "leq":"arithmetic", 
        "geq":"arithmetic", 
        "push":"memory", "pop":"memory"]; 

    binary_ops = ["add", "sub", "and", "or"]; 
    unary_ops = ["not", "neg"]; 
    comp_ops = ["lt", "gt", "eq", "leq", "geq"]; 
} 

bool is_in(string s, string[] list) { 
    foreach (str; list) 
     if (str==s) return true; 
    return false; 
} 

void build_strings() { 
    start = "@SP\nAM=M-1\n"; 
    end = "@SP\nM=M+1\n"; 

    binary_template = start ~ "D=M\n" 
    "@SP\n" 
    "AM=M-1\n" 
    "M=M%sD\n" ~ end; 

    unary_template = start ~ "M=%sM\n" ~ end; 

    comp_template = start ~ "D=M\n" 
    "@SP\n" 
    "AM=M-1\n" 
    "D=M-D\n" 
    "@COMP.%s.TRUE\n" 
    "D;%s\n" 
    "@COMP.%s.FALSE\n" 
    "0;JMP\n" 
    "(COMP.%s.TRUE)\n" 
    "@SP\n" 
    "A=M\n" 
    "M=-1\n" 
    "@SP\n" 
    "M=M+1\n" 
    "@COMP.%s.END\n" 
    "0;JMP\n" 
    "(COMP.%s.FALSE)\n" 
    "@SP\n" 
    "A=M\n" 
    "M=0\n" ~ end ~ "(COMP.%s.END)\n"; 

    push_tail_template = "@SP\n" 
    "A=M\n" 
    "M=D\n" 
    "@SP\n" 
    "M=M+1\n"; 

    push_const_template = "@%s\nD=A\n" ~ push_tail_template; 

    push_var_template = "@%s\n" 
    "D=A\n" 
    "@%s\n" 
    "A=%s+D\n" 
    "D=M\n" ~ push_tail_template; 

    push_staticpointer_template = "@%s\nD=M\n" ~ push_tail_template; 

    pop_template = "@%s\n" 
    "D=A\n" 
    "@%s\n" 
    "D=%s+D\n" 
    "@R13\n" 
    "M=D\n" 
    "@SP\n" 
    "AM=M-1\n" 
    "D=M\n" 
    "@R13\n" 
    "A=M\n" 
    "M=D\n"; 

    pop_staticpointer_template = "@SP\n" 
    "AM=M-1\n" 
    "D=M\n" 
    "@%s\n" 
    "M=D"; 
} 

void init() { 
    op_count = 0; 
    line_count = 0; 
    output = ["// Assembly file generated by my awesome VM compiler\n"]; 
    build_strings(); 
    build_dictionaries(); 
} 

string compile_operation(string op) { 
    if (op.length == 0 || op[0..2] == "//") 
     return ""; 
    string operation = op.split()[0]; 
    string header = "// '" ~ op ~ "' (line " ~ to!string(line_count) ~ ")\n"; 
    ++line_count; 

    if (type_dict[operation] == "arithmetic") 
     return header ~ compile_arithmetic(op); 
    else 
     return header ~ compile_memory(op); 
} 

string compile_arithmetic(string op) { 
    if (is_in(op, comp_ops)) { 
     string out_string = format(comp_template, op_count, operations_dict[op], op_count, 
      op_count, op_count, op_count, op_count); 
     op_count += 1; 
     return out_string; 
    } else if (is_in(op, unary_ops)) 
     return format(unary_template, operations_dict[op]); 
    else 
     return format(binary_template, operations_dict[op]); 
} 

string compile_memory(string op) { 
    string[] instructions = op.split(); 
    string inst = instructions[0]; 
    string argtype = instructions[1]; 
    int val = to!int(instructions[2]); 
    if (inst == "push") { 
     if (argtype == "constant") { 
      return format(push_const_template, val); 
     } else if (argtype == "static") 
      return format(push_staticpointer_template, ("f." ~ to!string(val))); 
     else if (argtype == "pointer") 
      if (val == 0) 
       return format(push_staticpointer_template, "THIS"); 
      else 
       return format(push_staticpointer_template, "THAT"); 
     else 
      return format(push_var_template, val, vars_dict[argtype][0], vars_dict[argtype][1]); 
    } else { 
     if (argtype != "constant") { 
      if (argtype == "static") 
       return format(pop_staticpointer_template, ("f." ~ to!string(val))); 
      else if (argtype == "pointer") { 
       if (val == 0) 
        return format(pop_staticpointer_template, "THIS"); 
       else 
        return format(pop_staticpointer_template, "THAT"); 
      } 
      else 
       return format(pop_template, val, vars_dict[argtype][0], vars_dict[argtype][1]); 
     } else { 
      return ""; 
     } 
    } 
} 

void main(string args[]) { 
    init(); 
    if (args.length < 2) { 
     writefln("usage: %s <filename>", args[0]); 
     exit(0); 
    } 
    string inputfname = args[1]; 
    string outputfname = args[1].split(".")[0] ~ ".asm"; 

    auto inputf = File(inputfname, "r"); 
    output ~= format("// Input filename: %s\n", inputfname); 
    foreach (line; inputf.byLine) { 
     output ~= compile_operation(to!string(line).strip); 
    } 
    inputf.close(); 

    auto outputf = File(outputfname, "w"); 
    foreach (outl; output) 
     outputf.write(outl); 

    outputf.write("(END)\[email protected]\n0;JMP"); 
    outputf.close(); 
    writeln("Compilation successful. Output written to " ~ outputfname); 
} 
+3

你有沒有試過分析看看發生了什麼? 不要假設。使用'-profile'編譯器標誌。 – dcousens 2013-02-11 10:18:04

+0

您可能還想在編譯D代碼時添加'-noboundscheck'。我不確定這會在這種情況下造成任何差異,但通常情況下,您可能需要在發行版中使用。看到gdc或ldc的表現也會很有趣。 – yaz 2013-02-11 15:58:50

+0

@Daniel你能解釋一下-profile是做什麼的?我運行了'dmd -profile vmt.d'(vmt.d是我的文件名),當我在一個大的輸入文件上運行程序時,它只是掛起(或者我沒有等待足夠長的時間,但是無論如何) 。 – 2013-02-11 18:05:53

回答

6

output變量使用Appenderdocs):

import std.array : appender; 

void main() { 
    auto output = appender!string("// Assembly file generated by my awesome VM compiler\n"); 
    //... 
    output.put(format("// Input filename: %s\n", inputfname)); 
    foreach (line; inputf.byLine) { 
     output.put(compile_operation(line.to!string().strip())); 
    } 
    //... 
    outputf.write(output.data()); 
    //... 
} 

另外,我建議你應該將type_dict更改爲類似int[string]與整型常量使用它。

int[string] type_dict; 

const TYPE_ARITHMETIC = 0, 
    TYPE_MEMORY = 1; 

//... 
type_dict = ["add": TYPE_ARITHMETIC, "push": TYPE_MEMORY]; // etc 
//... 

//... 
if (type_dict[operation] == TYPE_ARITHMETIC) { 
    //... 
} 
//... 

使用canFind方法(docs),而不是定製is_in。或者甚至嘗試SortedRangedocs)。

0

您可能需要使用std.array.appender輸出,而不是在你的主要功能appender陣列串聯,試圖最大限度地減少分配的數量,是通常針對追加進行優化。您也可以嘗試使用reserve

//Note: untested code 
import std.array; 
auto output = appender!string(); 

void init() { 
    ... 
    output.put("// Assembly file generated by my awesome VM compiler\n"); 
    ... 
} 

void main() { 
    ... 
    output.put(format("// Input filename: %s\n", inputfname)); 
    foreach (line; inputf.byLine) { 
     output.put(compile_operation(to!string(line).strip)); 
    } 
    ... 
    foreach (outl; output.data) 
     outputf.write(outl); 
    ... 
} 
+0

我把它放進去了,但奇怪的是,它使程序的運行時間加倍(!)。哇,那裏發生了什麼。順便說一句,顯然在全局範圍內聲明'auto output = appender!string();'是非法的。我嘗試將它聲明爲'auto output;'並在我的'init()'函數中初始化它,但它不喜歡不明確的類型。因爲我不確定它是什麼類型(我對D的幾個抱怨之一是這些看似匿名的類型),並且將它聲明爲Appender也不起作用,所以我改變了'init'來返回'auto'並聲明'輸出'在'init'中。任何更平滑的方式來做到這一點? – 2013-02-11 18:01:03

+0

string是'(immutable char)[]'的別名,appender需要更改數組的元素,將其更改爲'appender!char'將解決該問題。並且在lib的某個地方有一個模板,用'assumeUnique'正確地改變結果的字符串(假設該數組只在本地引用) – 2013-02-11 19:23:27

+1

「並且appender需要改變數組的元素」 - 什麼? – 2013-02-16 15:05:33

相關問題