進階偵錯工具#
如果您來到這裡,您想要深入研究或使用更進階的工具。這通常對於首次貢獻者和大多數日常開發來說不是必要的。這些工具較少使用,例如在新的 NumPy 發行版即將發布時,或當進行了大型或特別複雜的變更時。
由於並非所有這些工具都定期使用,並且僅在某些系統上可用,請預期會有差異、問題或怪異之處;如果您遇到困難,我們很樂意提供協助,並感謝您對這些工作流程的任何改進或建議。
使用額外工具尋找 C 錯誤#
大多數開發工作不需要比 偵錯 中所示的典型偵錯工具鏈更多。但例如,記憶體洩漏可能特別隱晦或難以縮小範圍。
我們不期望大多數貢獻者運行這些工具中的任何一個。但是,您可以確保我們能夠更輕鬆地追蹤此類問題
測試應涵蓋所有程式碼路徑,包括錯誤路徑。
盡量編寫簡短而簡單的測試。如果您有一個非常複雜的測試,請考慮建立一個額外的更簡單的測試。這可能會有所幫助,因為通常只能輕鬆找到哪個測試觸發了問題,而不是測試的哪一行。
如果資料被讀取/使用,永遠不要使用
np.empty
。valgrind
會注意到這一點並報告錯誤。當您不關心值時,您可以改為產生隨機值。
這將有助於我們在您的變更發布之前發現任何疏忽,並且意味著您不必擔心犯引用計數錯誤,這可能會令人望而生畏。
Python 偵錯建置版本#
Python 的偵錯建置版本很容易取得,例如透過 Linux 系統上的系統套件管理器,但在其他平台上也可用,可能格式不太方便。如果您無法從系統套件管理器輕鬆安裝 Python 的偵錯建置版本,您可以使用 pyenv 自己建置一個。例如,要安裝並全域啟用 Python 3.10.8 的偵錯建置版本,可以執行
pyenv install -g 3.10.8
pyenv global 3.10.8
請注意,pyenv install
從原始碼建置 Python,因此您必須確保在建置之前安裝了 Python 的相依性,請參閱 pyenv 文件以獲取平台特定的安裝說明。您可以使用 pip
安裝偵錯階段可能需要的 Python 相依性。如果 pypi 上沒有可用的偵錯 wheel 檔案,您將需要從原始碼建置相依性,並確保您的相依性也編譯為偵錯建置版本。
通常,Python 的偵錯建置版本會將 Python 可執行檔命名為 pythond
而不是 python
。要檢查您是否安裝了 Python 的偵錯建置版本,您可以執行例如 pythond -m sysconfig
以取得 Python 可執行檔的建置配置。偵錯建置版本將使用 CFLAGS
中的偵錯編譯器選項(例如 -g -Og
)建置。
運行 Numpy 測試或互動式終端機通常非常簡單,只需
python3.8d runtests.py
# or
python3.8d runtests.py --ipython
並且已在 偵錯 中提及。
Python 偵錯建置版本將有助於
尋找可能導致隨機行為的錯誤。一個例子是物件在刪除後仍被使用。
Python 偵錯建置版本允許檢查正確的引用計數。這可以使用額外的命令
sys.gettotalrefcount() sys.getallocatedblocks()
Python 偵錯建置版本允許使用 gdb 和其他 C 偵錯器更輕鬆地進行偵錯。
與 pytest
一起使用#
僅使用偵錯 Python 建置版本運行測試套件本身不會發現許多錯誤。Python 偵錯建置版本的另一個優點是它可以偵測記憶體洩漏。
一個使這更容易的工具是 pytest-leaks,可以使用 pip
安裝。不幸的是,pytest
本身可能會洩漏記憶體,但通常(目前)可以透過移除
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace['np'] = numpy
@pytest.fixture(autouse=True)
def env_setup(monkeypatch):
monkeypatch.setenv('PYTHONHASHSEED', '0')
從 numpy/conftest.py
獲得良好的結果(這可能會隨著新的 pytest-leaks
版本或 pytest
更新而改變)。
這允許方便地運行測試套件或其中的一部分
python3.8d runtests.py -t numpy/_core/tests/test_multiarray.py -- -R2:3 -s
其中 -R2:3
是 pytest-leaks
命令(請參閱其文件),-s
導致輸出列印,並且可能是必要的(在某些版本中,捕獲的輸出被偵測為洩漏)。
請注意,某些測試已知(甚至設計為)洩漏引用,我們嘗試標記它們,但預期會有一些誤報。
valgrind
#
Valgrind 是一個強大的工具,用於尋找某些記憶體存取問題,應在複雜的 C 程式碼上運行。基本使用 valgrind
通常只需要
PYTHONMALLOC=malloc valgrind python runtests.py
其中 PYTHONMALLOC=malloc
是必要的,以避免來自 Python 本身的誤報。根據系統和 valgrind 版本,您可能會看到更多誤報。valgrind
支援「抑制」以忽略其中一些,而 Python 確實有一個抑制檔案(甚至是一個編譯時選項),如果您覺得必要,這可能會有所幫助。
Valgrind 有助於
尋找使用未初始化的變數/記憶體。
偵測記憶體存取違規(在已分配記憶體之外讀取或寫入)。
尋找許多記憶體洩漏。請注意,對於大多數洩漏,python 偵錯建置方法(和
pytest-leaks
)更為敏感。原因是valgrind
只能偵測記憶體是否確實遺失。如果dtype = np.dtype(np.int64) arr.astype(dtype=dtype)
對於
dtype
具有不正確的引用計數,這是一個錯誤,但 valgrind 無法看到它,因為np.dtype(np.int64)
始終返回相同的物件。但是,並非所有 dtype 都是單例,因此這可能會洩漏不同輸入的記憶體。在極少數情況下,NumPy 使用malloc
而不是 Python 記憶體分配器,這對於 Python 偵錯建置版本是不可見的。malloc
通常應避免使用,但有一些例外(例如,PyArray_Dims
結構是公共 API,不能使用 Python 分配器。)
即使使用 valgrind 進行記憶體洩漏偵測速度較慢且靈敏度較低,它也可能很方便:您可以使用 valgrind 運行大多數程式,而無需修改。
需要注意的事項
Valgrind 不支援 numpy
longdouble
,這意味著測試將失敗或被標記為完全正常的錯誤。預期在運行 NumPy 程式碼之前和之後會出現一些錯誤。
快取可能意味著錯誤(特別是記憶體洩漏)可能無法偵測到,或者僅在稍後不相關的時間偵測到。
valgrind 的一大優點是它除了 valgrind 本身之外沒有任何要求(儘管您可能希望使用偵錯建置版本以獲得更好的追蹤)。
與 pytest
一起使用#
您可以使用 valgrind 運行測試套件,如果您只對少數測試感興趣,這可能就足夠了
PYTHOMMALLOC=malloc valgrind python runtests.py \
-t numpy/_core/tests/test_multiarray.py -- --continue-on-collection-errors
請注意 --continue-on-collection-errors
,這目前是必要的,因為缺少 longdouble
支援導致失敗(如果您不運行完整的測試套件,這通常不是必要的)。
如果您希望偵測記憶體洩漏,您還需要 --show-leak-kinds=definite
以及可能更多的 valgrind 選項。就像 pytest-leaks
一樣,某些測試已知會洩漏導致 valgrind 中的錯誤,並且可能會或可能不會被標記為這樣。
我們開發了 pytest-valgrind,它可以
針對每個測試個別報告錯誤
將記憶體洩漏縮小到個別測試(預設情況下,valgrind 僅在程式停止後檢查記憶體洩漏,這非常麻煩)。
請參閱其 README
以獲取更多資訊(它包含 NumPy 的範例命令)。