Python 程序是如何運行的仁热?
- 將源碼
*.py
編譯成字節(jié)碼*.pyc
, - 再由 Python 虛擬機逐行解釋
*.pyc
成機器碼俺夕,通知 CPU 執(zhí)行
名詞解釋:
- 源碼 (Programing Code) - 我們用高級語言敲的代碼
- 字節(jié)碼 (Byte Code) - 將高級語言編譯成更低一級的中間代碼契耿,方便虛擬機(VM)執(zhí)行
- 機器碼 (Machine Code) - 用于操控 CPU 運行的二進制指令代碼
什么是 GIL?
Python 虛擬機默認使用的是 CPython 解釋器(C 語言實現)拣度,CPython 使用了 GIL (Golbal Iterpreter Lock - 全局解釋器鎖),來確保同一時間只有一個線程運行,所以即使再多的線程也只能有效的使用一個 CPU抗果。
為什么不刪除 GIL筋帖?
實驗證明,如果放棄 GIL冤馏,使用大量細粒度的鎖代替日麸,導致單線程性能下降至少 30%。所以說 GIL 在支持多線程的同時能把單線程的優(yōu)勢最大地發(fā)揮出來逮光。
GIL 的限制
由于 GIL 的存在代箭,即便是多線程的 Python 程序也無法利用多核 CPU 的優(yōu)勢。不過 I/O 密集型程序涕刚,依然適合使用多線程嗡综,因為它們大部分時間都在等待 I/O,對 CPU 依賴很低杜漠。
名詞解釋:
- CPU 密集型 - 數學計算极景、搜索、圖像處理等
- I/O 密集型 - 文件操作驾茴、網絡交互盼樟、數據庫操作等
怎么充分的利用多核?
使用多進程替代多線程沟涨,或者使用 C 擴展
有了 GIL 還需要線程鎖嗎恤批?
需要异吻。GIL 只是解釋器級別的鎖裹赴,它能保證當多個線程在修改一個變量時不會崩潰,但結果可能是錯亂的诀浪。
如多個線程修改全部變量i = i + 1
棋返,是先讀取 i,再加 1 后雷猪,最后寫回內存睛竣,這會把在讀與寫期間其他線程對 i 的所有修改覆都蓋掉。所以我們需要用線程鎖來對整個讀寫過程加鎖求摇。
PyPy 是什么射沟?
Python 語言實現的另一種解釋器,使用了 JIT 編譯方式与境,沒有 GIL验夯。運行 Pure Python (純 Python 代碼) 程序速度更快。如果依賴了 C 擴展摔刁,反而會慢挥转,如 MysqlDB、protobuf 庫。
JIT 與其他編譯方式的對比:
- Compilation: 先編譯成適用于本機識別的機器碼绑谣,再運行党窜,不同系統或 CPU 需要重新編譯 (如 C/C++)
- Interpretation: 解釋器在程序運行時把字節(jié)碼逐行解釋成機器碼,邊解釋借宵,邊執(zhí)行 (如 CPython/Ruby)
- JIT Compilation - 及時編譯 (Just-in-Time)幌衣,是對前兩張方法的整合,將多次調用的字節(jié)碼壤玫,解釋成機器碼泼掠,緩存起來,優(yōu)先讀緩存垦细,而非解釋择镇。支持跨平臺的同時,又降低了動態(tài)編譯對性能的影響括改。 (如 Java/C#/Pypy)
總結
根據應用場景選擇最佳的性能優(yōu)化方案
- I/O 密集型: 使用多線程
- I/O 密集型 & Pure Python: 使用 Pypy 解釋器
- CPU 密集型: 使用多進程或把復雜邏輯用 C 擴展實現