出品 | CSDN(ID:)
誠然,編譯器可以為你生成高性能的代碼,但是你真的需要編譯器嗎?另一種方法是用 編寫程序,雖然有點夸大,但這種方法有兩個主要缺陷:
1. 匯編代碼不可移植;
2. 雖然在現代工具的輔助下變得容易了些,但 編程仍然需要大量繁瑣的工作。
值得慶幸的是,我們都生活在二十一世紀,這兩個問題都已得到解決。第一個解決方案是LLVM,最初,它意味著“低級虛擬機”,這正是我們可以確保可移植性的原因。簡而言之,它需要用一些非常低級別的與硬件無關語言編寫的代碼,并為特定的硬件平臺返回一些高度優化的原生代碼。使用 LLVM,我們既具有低級編程的強大功能,又具有面向硬件微優化的自動化功能。
第二個問題的解決方法是使用“腳本”語言,、、Perl,甚至 bash 或 AWK 都可以消除繁瑣的工作。
實驗計劃
首先,讓我們生成一個完全內聯展開的解決方案,并將其嵌入到基準測試代碼中。該計劃如下:
1. 使用 Clang 為基準生成 LLVM 中間代碼,該基準用于測量 ,一個不存在的函數;
2. 使 在 LLVM 中生成線性求解器( )代碼;
3. 使用 腳本測試基準,用生成求解器替換 調用;
4. 使用 LLVM 靜態編譯器將中間代碼轉換為機器代碼;
5. 使用 GNU 匯編器和 Clang 的鏈接器將機器代碼轉換為可執行的二進制文件。
這就是它在 中的樣子:
部分
我們需要 中的線性求解器( ),就像我們使用 C 和 C ++ 一樣,此處代碼為:
#?this?generates?n-solver?in?LLVM?code?with?LLVMCode?objects.
#?No?LLVM?stuff?yet,?just?completely?Pythonic?solution
def?solve_linear_system(a_array,?b_array,?x_array,?n_value):
??def?a(i,?j,?n):
????if?n?==?n_value:
??????return?a_array[i?*?n_value?+?j]
????return?a(i,?j,?n+1)*a(n,?n,?n+1)?-?a(i,?n,?n+1)*a(n,?j,?n+1)
??def?b(i,?n):
????if?n?==?n_value:
??????return?b_array[i]
????return?a(n,?n,?n+1)*b(i,?n+1)?-?a(i,?n,?n+1)*b(n,?n+1)
??def?x(i):
????d?=?b(i,i+1)
????for?j?in?range(i):
??????d?-=?a(i,?j,?i+1)?*?x_array[j]
????return?d?/?a(i,?i,?i+1)
??for?k?in?range(n_value):
????x_array[k]?=?x(k)
return?x_array
當我們用數字運行時,我們可以得到數字。但我們想要代碼,因此,我們需要制作一個假裝成數字的對象()來探測算法。該對象記錄下算法想要執行的每一個操作,并準備好集成 LLVM 中間語言。
#?this?is?basically?the?whole?LLVM?layer
I?=?0
STACK?=?[]
class?LLVMCode:
??#?the?only?constructor?for?now?is?by?double*?instruction
??def?__init__(self,?io,?code?=?''):
????self.io?=?io
????self.code?=?code
??def?__getitem__(self,?i):
????global?I,?STACK
????copy_code?=?"%"?+?str(I+1)
????copy_code?+=?"?=?getelementptr?inbounds?double,?double*?"
????copy_code?+=?self.io?+",?i64?"?+?str(i)?+?"\n"
????copy_code?+=?"%"?+?str(I+2)
????copy_code?+=?"?=?load?double,?double*?%"?+?str(I+1)
????copy_code?+=?",?align?8\n"
????I?+=?2
????STACK?+=?[I]
????return?LLVMCode(self.io,?copy_code)
??def?__setitem__(self,?i,?other_llvcode):
????global?I,?STACK
????self.code?+=?other_llvcode.code
????self.code?+=?"%"?+?str(I+1)
????self.code?+=?"?=?getelementptr?inbounds?double,?double*?"
????self.code?+=?self.io?+",?i64?"?+?str(i)?+?"\n"
????self.code?+=?"store?double?%"?+?str(I)
????self.code?+=?",?double*?%"?+?str(I+1)?+?",?align?8\n"
????I?+=?1
????STACK?=?STACK[:-1]
????return?self
??def?general_arithmetics(self,?operator,?other_llvcode):
????global?I,?STACK
????self.code?+=?other_llvcode.code;
????self.code?+=?"%"?+?str(I+1)?+?"?=?f"?+?operator
????self.code?+=?"?double?%"?+?str(STACK[-2])?+?",?%"
????self.code?+=?str(STACK[-1])?+?"\n";
????I?+=?1
????STACK?=?STACK[:-2]?+?[I]
????return?self
??def?__add__(self,?other_llvcode):
????return?self.general_arithmetics('add',?other_llvcode)
??def?__sub__(self,?other_llvcode):
????return?self.general_arithmetics('sub',?other_llvcode)
??def?__mul__(self,?other_llvcode):
????return?self.general_arithmetics('mul',?other_llvcode)
??def?__div__(self,?other_llvcode):
????return?self.general_arithmetics('div',?other_llvcode)
接著,當我們使用這種對象運行求解器時,我們得到了一個用 LLVM 中間語言編寫的全功能線性求解器。然后我們將其放入基準代碼中進行速度測試(看它有多快)。
LLVM 中的指令有編號,我們希望保存枚舉,因此將代碼插入到基準測試中的函數很重要,但也不是很復雜。
#?this?replaces?the?function?call
#?and?updates?all?the?instructions'?indices
def?replace_call(text,?line,?params):
??global?I,?STACK
??#?'?'?->?12
??I?=?int(''.join(
????[xi?for?xi?in?params[2]?if?xi.isdigit()]
????))
??first_instruction_to_replace?=?I?+?1
??STACK?=?[]
??replacement?=?solve_linear_system(
????LLVMCode(params[0]),
????LLVMCode(params[1]),
????LLVMCode(params[2]),?5).code
??delta_instruction?=?I?-?first_instruction_to_replace?+?1
??for?i?in?xrange(first_instruction_to_replace,?sys.maxint):
????not_found?=?sum(
??????[text.find('%'?+?str(i)?+?c)?==?-1
????????for?c?in?POSSIBLE_CHARS_NUMBER_FOLLOWS_WITH]
??????)
????if?not_found?==?4:
??????#?the?last?instruction?has?already?been?substituted
??????break
????new_i?=?i?+?delta_instruction
????for?c?in?POSSIBLE_CHARS_NUMBER_FOLLOWS_WITH:
??????#?substitute?instruction?number
??????text?=?text.replace('%'?+?str(i)?+?c,?'%'?+?str(new_i)?+?c)
return?text.replace(line,?replacement)
實現解算器的整段代碼提供了 -to-LLVM 層,其中代碼插入只有 100 行!
另附 鏈接:
基準
基準測試本身在 C 中。當我們運行 時,它對 的調用被 生成的 LLVM 代碼所取代。
Step 1. C code
Step 2. LLVM 匯編語言
Step 3. 調用替換后的 LLVM
Step 4. 本地優化裝配
最值得注意的是 腳本生成的超冗長中間代碼如何變成一些非常緊湊且非常有效的硬件代碼。同時它也是高度標量化的,但它是否足以與 C 和 C++ 的解決方案競爭呢?
以下是三種情況的近似數字(帶有技巧的 C、C++ 與基于 LLVM 的 的性能對比):
1. C 的技巧對 Clang 來說并不適用,因此測量 GCC 版本,其平均運行大約 70 毫秒;
2. C++ 版本是用 Clang 構建的,運行時間為 60 毫秒;
3. 版本(此處描述的版本)僅運行 55 毫秒。
當然,這種加速并不是關鍵,但這表明你可以用 編寫出勝過用 C 或 C++ 編寫的程序。這也就暗示你不必學習一些特殊語言來創建高性能的應用程序或庫。
結論
快速編譯語言和慢速腳本語言之間的對立不過是虛張聲勢。原生代碼生成的可能不是核心功能,而是類似于可插拔選項。像是 編譯器 Numba或Lua 的 Terra,其優勢就在于你可以用一種語言進行研究和快速原型設計,然后使用相同的語言生成高性能的代碼。
高性能計算沒有理由保留編譯語言的特權,編譯器只是用于代碼生成的軟機器。你可以使用你想要的任何語言生成代碼python 保留有效數字,我相信如果你愿意,你可以教 生成超快的 LLVM 代碼。
本文涉及的所有測試均在 Intel(R)Core(TM)i7- CPU @ 2.80GHz 上進行python 保留有效數字,代碼使用 Clang 3.8.0- 和 g++5.4.0 編譯。
基準測試源代碼:
原文:
本文為 CSDN 翻譯,如需轉載,請注明來源出處。
熱 文推 薦
單身暴擊!程序員用 給女朋友寫了個翻譯軟件
?
?
?
?
?
print_r('點個好看吧!');
var_dump('點個好看吧!');
NSLog(@"點個好看吧!");
System.out.println("點個好看吧!");
console.log("點個好看吧!");
print("點個好看吧!");
printf("點個好看吧!\n");
cout?<"點個好看吧!"?<Console.WriteLine("點個好看吧!");
fmt.Println("點個好看吧!");
Response.Write("點個好看吧!");
alert("點個好看吧!")
echo "點個好看吧!"