都有Python了,還要什麼編譯器!
編譯的目的是將原始碼轉化為機器可識別的可執行程式,在早期,每次編譯都需要重新構建所有東西,後來人們意識到可以讓編譯器自動完成一些工作,從而提升編譯效率。
但“編譯器不過是用於程式碼生成的軟機器,你可以使用你想要的任何語言來生成程式碼”,真的是必要的嗎?
作者 | Oleksandr Kaleniuk
譯者 | 虎說
責編 | 仲培藝
來源 | CSDN(ID:CSDNnews)
誠然,編譯器可以為你生成高效能的程式碼,但是你真的需要編譯器嗎?另一種方法是用 Assembly 編寫程式,雖然有點誇大,但這種方法有兩個主要缺陷:
1. 彙編程式碼不可移植;
2. 雖然在現代工具的輔助下變得容易了些,但 Assembly 程式設計仍然需要大量繁瑣的工作。
值得慶幸的是,我們都生活在二十一世紀,這兩個問題都已得到解決。第一個解決方案是 LLVM,最初,它意味著“低階虛擬機器”,這正是我們可以確保可移植性的原因。簡而言之,它需要用一些非常低階別的與硬體無關語言編寫的程式碼,併為特定的硬體平臺返回一些高度優化的原生程式碼。使用 LLVM,我們既具有低階程式設計的強大功能,又具有面向硬體微優化的自動化功能。
第二個問題的解決方法是使用“指令碼”語言,Scheme、Python、Perl,甚至 bash 或 AWK 都可以消除繁瑣的工作。
實驗計劃
首先,讓我們生成一個完全內聯展開的解決方案,並將其嵌入到基準測試程式碼中。該計劃如下:
1. 使用 Clang 為基準生成 LLVM 中間程式碼,該基準用於測量 solve_5,一個不存在的函式;
2. 使 Python 在 LLVM 中生成線性求解器(linear solver)程式碼;
3. 使用 Python 指令碼測試基準,用生成求解器替換 solve_5 呼叫;
4. 使用 LLVM 靜態編譯器將中間程式碼轉換為機器程式碼;
5. 使用 GNU 彙編器和 Clang 的連結器將機器程式碼轉換為可執行的二進位制檔案。
這就是它在 Makefile 中的樣子:
Python 部分
我們需要 Python 中的線性求解器(linear solver),就像我們使用 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
當我們用數字執行時,我們可以得到數字。但我們想要程式碼,因此,我們需要製作一個假裝成數字的物件(Object)來探測演算法。該物件記錄下演算法想要執行的每一個操作,並準備好整合 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 ' -> 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)
實現解算器的整段程式碼提供了 Python-to-LLVM 層,其中程式碼插入只有 100 行!
另附 GitHub 連結:
https://github.com/akalenuk/wordsandbuttons/blob/master/exp/python_to_llvm/exp_embed_on_call/substitute_solver_call.py
基準
基準測試本身在 C 中。當我們執行 Makefile 時,它對 solve_5 的呼叫被 Python 生成的 LLVM 程式碼所取代。
Step 1. Benchmark C source code
Step 2. LLVM 組合語言
Step 3. 呼叫替換後的 LLVM
Step 4. 本地優化裝配
最值得注意的是 Python 指令碼生成的超冗長中間程式碼如何變成一些非常緊湊且非常有效的硬體程式碼。同時它也是高度標量化的,但它是否足以與 C 和 C++ 的解決方案競爭呢?
以下是三種情況的近似數字(帶有技巧的 C、C++ 與基於 LLVM 的 Python 的效能對比):
1. C 的技巧對 Clang 來說並不適用,因此測量 GCC 版本,其平均執行大約 70 毫秒;
2. C++ 版本是用 Clang 構建的,執行時間為 60 毫秒;
3. Python 版本(此處描述的版本)僅執行 55 毫秒。
當然,這種加速並不是關鍵,但這表明你可以用 Python 編寫出勝過用 C 或 C++ 編寫的程式。這也就暗示你不必學習一些特殊語言來建立高效能的應用程式或庫。
結論
快速編譯語言和慢速指令碼語言之間的對立不過是虛張聲勢。原生程式碼生成的可能不是核心功能,而是類似於可插拔選項。像是 Python 編譯器 Numba 或 Lua 的 Terra,其優勢就在於你可以用一種語言進行研究和快速原型設計,然後使用相同的語言生成高效能的程式碼。
高效能運算沒有理由保留編譯語言的特權,編譯器只是用於程式碼生成的軟機器。你可以使用你想要的任何語言生成程式碼,我相信如果你願意,你可以教 Matlab 生成超快的 LLVM 程式碼。
本文涉及的所有測試均在 Intel(R)Core(TM)i7-7700HQ CPU @ 2.80GHz 上進行,程式碼使用 Clang 3.8.0-2ubuntu4 和 g++5.4.0 編譯。
基準測試原始碼:
https://github.com/akalenuk/wordsandbuttons/tree/master/exp/python_to_llvm
原文連結:
https://wordsandbuttons.online/outperforming_everything_with_anything.html
本文為 CSDN 翻譯,如需轉載,請註明來源出處。
(本文為 AI科技大本營轉載文章,轉載請聯絡原作者)
4 月13日-4 月14日,CSDN 將在北京主辦“Python 開發者日( 2019 )”,匯聚十餘位來自阿里巴巴、IBM、英偉達等國內外一線科技公司的Python技術專家,還有數百位來自各行業領域的Python開發者。目前購票通道已開啟,早鳥票限量發售中,3 月15日之前可享受優惠價 299 元(售完即止)。
推薦閱讀:
❤點選“閱讀原文”,檢視歷史精彩文章。
相關文章
- 為什麼有了 HTTP 還要 RPCHTTPRPC
- python都用什麼編輯器Python
- [譯] 2019 年了,為什麼我還在用 jQuery?jQuery
- 有了 Spring 為什麼還要弄個 Spring BootSpring Boot
- python程式碼是解釋型語言,為什麼還有編譯過程?Python編譯
- 什麼是Python直譯器?和Python IDE有什麼區別?PythonIDE
- Python是什麼?為什麼要掌握python?Python
- 什麼是程式語言,什麼是Python直譯器Python
- linux用什麼編輯器寫python?LinuxPython
- 為什麼還要記密碼密碼
- NEO Python編譯器介紹Python編譯
- Java要學哪些IDE?這些IDE都有什麼功能?JavaIDE
- [譯]為什麼要寫 super(props)
- Python到底是什麼?為什麼要學Python?Python
- 記憶體分頁不就夠了?為什麼還要分段?還有段頁式?記憶體
- 為什麼要學習Python?Python可以做什麼事情?Python
- 為什麼要學習Python?學習Python可以做什麼?Python
- 什麼是技術債,為什麼要還技術債?
- 有了uWGSI伺服器,Django 為什麼還需要 Nginx?伺服器DjangoNginx
- Java條件編譯是什麼?Java編譯
- 為什麼 2020 還要學 Node.jsNode.js
- [譯]我們為什麼要寫 super(props)?
- Python IDE和直譯器有什麼區別?Python入門教程PythonIDE
- Python培訓教程:什麼是Python全域性直譯器鎖(GIL)?Python
- Python優勢是什麼?為什麼要學習?Python
- 微控制器中為什麼有了Flash還有EEPROM?
- 淺談彙編器、編譯器和直譯器編譯
- 智慧斷路器安全用電都有什麼功能
- 除了防火牆,還要部署什麼裝置才能保證伺服器安全?防火牆伺服器
- 什麼是Python全域性直譯器鎖(GIL)?全域性直譯器鎖的好處!Python
- python如何編譯Python編譯
- 為什麼要學Python?Python可做哪些事情?Python
- 初學Python什麼編輯器比較適合新手?Python
- Python文字編輯器是什麼?推薦這兩款Python
- Java虛擬機器(JVM)和Python直譯器有什麼區別?Java虛擬機JVMPython
- 【高併發】面試官:Java中提供了synchronized,為什麼還要提供Lock呢?面試Javasynchronized
- 為什麼學習python要掌握Linux?PythonLinux
- 普通人為什麼要學python?Python