# 1.15。故障排除和提示
> 原文: [http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html](http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html)
## 1.15.1。編譯什么
一般建議您應該只嘗試編譯代碼中的關鍵路徑。如果在某些更高級別的代碼中有一段性能關鍵的計算代碼,則可以在單獨的函數中分解性能關鍵代碼,并使用 Numba 編譯單獨的函數。讓 Numba 專注于那一小部分性能關鍵代碼有幾個優點:
* 它降低了擊中不支持功能的風險;
* 它減少了編譯時間;
* 它允許您更容易地演化編譯函數之外的更高級代碼。
## 1.15.2。我的代碼沒有編譯
Numba 無法編譯代碼可能有多種原因,并引發錯誤。一個常見原因是您的代碼依賴于不受支持的 Python 功能,尤其是在 [nopython 模式](../glossary.html#term-nopython-mode)中。請參閱[支持的 Python 功能列表](../reference/pysupported.html#pysupported)。如果您發現其中列出的內容仍然無法編譯,請[報告錯誤](../developer/contributing.html#report-numba-bugs)。
當 Numba 嘗試編譯代碼時,它首先嘗試計算出所有正在使用的變量的類型,這樣就可以生成代碼的特定類型實現,可以編譯成機器代碼。 Numba 無法編譯的常見原因(特別是在 [nopython 模式](../glossary.html#term-nopython-mode)中)是類型推斷失敗,本質上 Numba 無法確定代碼中所有變量的類型應該是什么。
例如,讓我們考慮一下這個簡單的函數:
```py
@jit(nopython=True)
def f(x, y):
return x + y
```
如果你用兩個數字來稱呼它,Numba 能夠正確地推斷出這些類型:
```py
>>> f(1, 2)
3
```
但是如果用一個元組和一個數字來調用它,Numba 無法說出添加元組和數字的結果是什么,因此編譯錯誤:
```py
>>> f(1, (2,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<path>/numba/numba/dispatcher.py", line 339, in _compile_for_args
reraise(type(e), e, None)
File "<path>/numba/numba/six.py", line 658, in reraise
raise value.with_traceback(tb)
numba.errors.TypingError: Failed at nopython (nopython frontend)
Invalid use of + with parameters (int64, tuple(int64 x 1))
Known signatures:
* (int64, int64) -> int64
* (int64, uint64) -> int64
* (uint64, int64) -> int64
* (uint64, uint64) -> uint64
* (float32, float32) -> float32
* (float64, float64) -> float64
* (complex64, complex64) -> complex64
* (complex128, complex128) -> complex128
* (uint16,) -> uint64
* (uint8,) -> uint64
* (uint64,) -> uint64
* (uint32,) -> uint64
* (int16,) -> int64
* (int64,) -> int64
* (int8,) -> int64
* (int32,) -> int64
* (float32,) -> float32
* (float64,) -> float64
* (complex64,) -> complex64
* (complex128,) -> complex128
* parameterized
[1] During: typing of intrinsic-call at <stdin> (3)
File "<stdin>", line 3:
```
錯誤消息可以幫助您找出出錯的地方:“無效使用+與參數(int64,tuple(int64 x 1))”將被解釋為“Numba 遇到了一個類型為整數和 1 元組整數的變量的添加分別,并且不知道任何此類操作“。
請注意,如果您允許對象模式:
```py
@jit
def g(x, y):
return x + y
```
編譯將成功,編譯的函數將在 Python 運行時提升:
```py
>>> g(1, (2,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'tuple'
```
## 1.15.3。我的代碼有一個類型統一問題
Numba 無法編譯代碼的另一個常見原因是它無法靜態地確定函數的返回類型。最可能的原因是返回類型取決于僅在運行時可用的值。同樣,當使用 [nopython 模式](../glossary.html#term-nopython-mode)時,這通常是有問題的。類型統一的概念只是試圖找到一種可以安全地表示兩個變量的類型。例如,64 位浮點數和 64 位復數可以用 128 位復數表示。
作為類型統一失敗的示例,此函數具有在運行時根據 <cite>x</cite> 的值確定的返回類型:
```py
In [1]: from numba import jit
In [2]: @jit(nopython=True)
...: def f(x):
...: if x > 10:
...: return (1,)
...: else:
...: return 1
...:
In [3]: f(10)
```
嘗試執行此功能,錯誤如下:
```py
TypingError: Failed at nopython (nopython frontend)
Can't unify return type from the following types: tuple(int64 x 1), int64
Return of: IR name '$8.2', type '(int64 x 1)', location:
File "<ipython-input-2-51ef1cc64bea>", line 4:
def f(x):
<source elided>
if x > 10:
return (1,)
^
Return of: IR name '$12.2', type 'int64', location:
File "<ipython-input-2-51ef1cc64bea>", line 6:
def f(x):
<source elided>
else:
return 1
```
錯誤消息“無法統一以下類型的返回類型:tuple(int64 x 1),int64”應該讀作“Numba 找不到可以安全地表示整數和整數的 1 元組的類型”。
## 1.15.4。我的代碼有一個無類型列表問題
正如[之前提到的](#code-doesnt-compile),Numba 編譯代碼的第一部分涉及計算所有變量的類型。對于列表,列表必須包含相同類型的項目,或者如果可以從稍后的某個操作推斷出類型,則列表可以為空。不可能的是有一個被定義為空并且沒有可推斷類型的列表(即無類型列表)。
例如,這是使用已知類型的列表:
```py
from numba import jit
@jit(nopython=True)
def f():
return [1, 2, 3] # this list is defined on construction with `int` type
```
這是使用空列表,但可以推斷出類型:
```py
from numba import jit
@jit(nopython=True)
def f(x):
tmp = [] # defined empty
for i in range(x):
tmp.append(i) # list type can be inferred from the type of `i`
return tmp
```
這是使用空列表,無法推斷類型:
```py
from numba import jit
@jit(nopython=True)
def f(x):
tmp = [] # defined empty
return (tmp, x) # ERROR: the type of `tmp` is unknown
```
雖然有點做作,如果你需要一個空列表并且無法推斷出類型,但是你知道列表是什么類型,這個“技巧”可以用來指示打字機制:
```py
from numba import jit
import numpy as np
@jit(nopython=True)
def f(x):
# define empty list, but instruct that the type is np.complex64
tmp = [np.complex64(x) for x in range(0)]
return (tmp, x) # the type of `tmp` is known, but it is still empty
```
## 1.15.5。編譯的代碼太慢
編譯 JIT 函數緩慢的最常見原因是 [nopython 模式](../glossary.html#term-nopython-mode)中的編譯失敗并且 Numba 編譯器已回落到[對象模式](../glossary.html#term-object-mode)。與常規 Python 解釋相比,[對象模式](../glossary.html#term-object-mode)目前幾乎沒有提供加速,其主要觀點是允許內部優化稱為[循環提升](../glossary.html#term-loop-lifting):此優化將允許編譯內部循環在 [nopython 模式](../glossary.html#term-nopython-mode)中,無論這些內部循環包圍什么代碼。
要確定函數的類型推斷是否成功,可以在編譯函數上使用 [`inspect_types()`](../reference/jit-compilation.html#Dispatcher.inspect_types "Dispatcher.inspect_types") 方法。
例如,讓我們采取以下功能:
```py
@jit
def f(a, b):
s = a + float(b)
return s
```
當使用數字調用時,此函數應該很快,因為 Numba 能夠將數字類型轉換為浮點數。讓我們來看看:
```py
>>> f(1, 2)
3.0
>>> f.inspect_types()
f (int64, int64)
--------------------------------------------------------------------------------
# --- LINE 7 ---
@jit
# --- LINE 8 ---
def f(a, b):
# --- LINE 9 ---
# label 0
# a.1 = a :: int64
# del a
# b.1 = b :: int64
# del b
# $0.2 = global(float: <class 'float'>) :: Function(<class 'float'>)
# $0.4 = call $0.2(b.1, ) :: (int64,) -> float64
# del b.1
# del $0.2
# $0.5 = a.1 + $0.4 :: float64
# del a.1
# del $0.4
# s = $0.5 :: float64
# del $0.5
s = a + float(b)
# --- LINE 10 ---
# $0.7 = cast(value=s) :: float64
# del s
# return $0.7
return s
```
在沒有嘗試理解太多 Numba 中間表示的情況下,仍然可以看到所有變量和臨時值都已正確推斷其類型:例如 _a_ 具有`int64`類型, _$ 0.5_ 的類型有`float64`等。
但是,如果 _b_ 作為字符串傳遞,編譯將回退到對象模式,因為 Numba 當前不支持帶有字符串的 float()構造函數:
```py
>>> f(1, "2")
3.0
>>> f.inspect_types()
[... snip annotations for other signatures, see above ...]
================================================================================
f (int64, str)
--------------------------------------------------------------------------------
# --- LINE 7 ---
@jit
# --- LINE 8 ---
def f(a, b):
# --- LINE 9 ---
# label 0
# a.1 = a :: pyobject
# del a
# b.1 = b :: pyobject
# del b
# $0.2 = global(float: <class 'float'>) :: pyobject
# $0.4 = call $0.2(b.1, ) :: pyobject
# del b.1
# del $0.2
# $0.5 = a.1 + $0.4 :: pyobject
# del a.1
# del $0.4
# s = $0.5 :: pyobject
# del $0.5
s = a + float(b)
# --- LINE 10 ---
# $0.7 = cast(value=s) :: pyobject
# del s
# return $0.7
return s
```
在這里,我們看到所有變量最終都被輸入為`pyobject`。這意味著該函數是在對象模式下編譯的,并且值作為通用 Python 對象傳遞,而 Numba 沒有嘗試查看它們以推斷其原始值。這是您在關心代碼速度時要避免的情況。
有幾種方法可以理解函數在 nopython 模式下無法編譯的原因:
* 傳遞 _nopython = True_ ,這將引發一個錯誤,指出出現了什么問題(見上文[我的代碼不編譯](#code-doesnt-compile));
* 通過設置 [`NUMBA_WARNINGS`](../reference/envvars.html#envvar-NUMBA_WARNINGS) 環境變量啟用警告;例如,上面的`f()`功能:
```py
>>> f(1, 2)
3.0
>>> f(1, "2")
example.py:7: NumbaWarning: Function "f" failed type inference: Internal error at <numba.typeinfer.CallConstrain object at 0x7f6b8dd24550>:
float() only support for numbers
File "example.py", line 9
@jit
example.py:7: NumbaWarning: Function "f" was compiled in object mode without forceobj=True.
@jit
3.0
```
## 1.15.6。禁用 JIT 編譯
為了調試代碼,可以禁用 JIT 編譯,這使得`jit`裝飾器(以及裝飾器`njit`和`autojit`)就像它們不執行任何操作一樣,并且裝飾函數的調用調用原始 Python 函數而不是編譯版本。可以通過將 [`NUMBA_DISABLE_JIT`](../reference/envvars.html#envvar-NUMBA_DISABLE_JIT) 環境變量設置為`1`來切換。
啟用此模式時,`vectorize`和`guvectorize`裝飾器仍將導致 ufunc 的編譯,因為這些函數沒有直接的純 Python 實現。
## 1.15.7。使用 GDB 調試 JIT 編譯代碼
在`jit`裝飾器中設置`debug`關鍵字參數(例如`@jit(debug=True)`)可以在 jitted 代碼中發出調試信息。要進行調試,需要 GDB 7.0 或更高版本。目前,可以使用以下調試信息:
* 函數名稱將顯示在回溯中。但是,沒有類型信息。
* 源位置(文件名和行號)可用。例如,用戶可以通過絕對文件名和行號設置斷點;例如`break /path/to/myfile.py:6`。
* 當前函數中的局部變量可以用`info locals`顯示。
* 帶`whatis myvar`的變量類型。
* 變量值`print myvar`或`display myvar`。
* 簡單的數字類型,即 int,float 和 double,以其原生表示形式顯示。但是,假設整數被簽名。
* 其他類型顯示為字節序列。
已知的問題:
* 步進很大程度上取決于優化級別。
* 在完全優化(相當于 O3)時,大多數變量都被優化掉了。
* 沒有優化(例如`NUMBA_OPT=0`),當單步執行代碼時,源位置會跳轉。
* 在 O1 優化(例如`NUMBA_OPT=1`),步進穩定但一些變量被優化。
* 啟用調試信息后,內存消耗會顯著增加。編譯器發出額外信息( [DWARF](http://www.dwarfstd.org/) )以及指令。使用調試信息,發出的目標代碼可以大 2 倍。
內部細節:
* 由于 Python 語義允許變量綁定到不同類型的值,因此 Numba 在內部為每種類型創建變量的多個版本。所以代碼如下:
```py
x = 1 # type int
x = 2.3 # type float
x = (1, 2, 3) # type 3-tuple of int
```
每個分配將存儲到不同的變量名稱。在調試器中,變量將是`x`,`x$1`和`x$2`。 (在 Numba IR 中,它們是`x`,`x.1`和`x.2`。)
* 啟用調試時,將禁用內聯函數。
### 1.15.7.1。調試用法示例
python 源碼:
|
```py
1
2
3
4
5
6
7
8
9
10
11
```
|
```py
from numba import njit
@njit(debug=True)
def foo(a):
b = a + 1
c = a * 2.34
d = (a, b, c)
print(a, b, c, d)
r= foo(123)
print(r)
```
|
在終端:
```py
$ NUMBA_OPT=1 gdb -q python
Reading symbols from python...done.
(gdb) break /home/user/chk_debug.py:5
No source file named /home/user/chk_debug.py.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (/home/user/chk_debug.py:5) pending.
(gdb) run chk_debug.py
Starting program: /home/user/miniconda/bin/python chk_debug.py
...
Breakpoint 1, __main__::foo$241(long long) () at chk_debug.py:5
5 b = a + 1
(gdb) n
6 c = a * 2.34
(gdb) bt
#0 __main__::foo$241(long long) () at chk_debug.py:6
#1 0x00007ffff7fec47c in cpython::__main__::foo$241(long long) ()
#2 0x00007fffeb7976e2 in call_cfunc (locals=0x0, kws=0x0, args=0x7fffeb486198,
...
(gdb) info locals
a = 0
d = <error reading variable d (DWARF-2 expression error: `DW_OP_stack_value' operations must be used either alone or in conjunction with DW_OP_piece or DW_OP_bit_piece.)>
c = 0
b = 124
(gdb) whatis b
type = i64
(gdb) whatis d
type = {i64, i64, double}
(gdb) print b
$2 = 124
```
### 1.15.7.2。全局覆蓋調試設置
通過設置環境變量`NUMBA_DEBUGINFO=1`,可以為整個應用程序啟用調試。這將設置`jit`中`debug`選項的默認值。通過設置`debug=False`可以關閉各個功能的調試。
請注意,啟用調試信息會顯著增加每個已編譯函數的內存消耗。對于大型應用程序,這可能會導致內存不足錯誤。
## 1.15.8。在`nopython`模式 中使用 Numba 的直接`gdb`綁定
Numba(版本 0.42.0 及更高版本)具有一些與`gdb`支持 CPU 相關的附加功能,可以更輕松地調試程序。以下描述的所有`gdb`相關函數以相同的方式工作,而不管它們是從標準 CPython 解釋器調用還是以 [nopython 模式](../glossary.html#term-nopython-mode)或[對象模式](../glossary.html#term-object-mode)編譯的代碼。
注意
這個功能是實驗性的!
警告
如果從 Jupyter 或`pdb`模塊旁邊使用此功能會發生意外情況。它的行為是無害的,很難預測!
### 1.15.8.1。設置
Numba 的`gdb`相關函數使用`gdb`二進制,如果需要,可以通過 [`NUMBA_GDB_BINARY`](../reference/envvars.html#envvar-NUMBA_GDB_BINARY) 環境變量配置該二進制文件的位置和名稱。
注意
Numba 的`gdb`支持要求`gdb`能夠附加到另一個進程。在某些系統(特別是 Ubuntu Linux)上,`ptrace`上的默認安全限制阻止了這種情況。 Linux 安全模塊 <cite>Yama</cite> 在系統級強制執行此限制。有關此模塊的文檔以及更改其行為的安全隱患,請參見 [Linux 內核文檔](https://www.kernel.org/doc/Documentation/admin-guide/LSM/Yama.rst)。 [Ubuntu Linux 安全文檔](https://wiki.ubuntu.com/Security/Features#ptrace)討論了如何根據`ptrace_scope`調整 <cite>Yama</cite> 的行為,以便允許所需的行為。
### 1.15.8.2。基本`gdb`支持
警告
不建議在同一程序中多次調用`numba.gdb()`和/或`numba.gdb_init()`,可能會發生意外情況。如果程序中需要多個斷點,則通過`numba.gdb()`或`numba.gdb_init()`啟動`gdb`一次,然后使用`numba.gdb_breakpoint()`注冊其他斷點位置。
添加`gdb`支持的最簡單功能是`numba.gdb()`,它在呼叫位置將:
* 啟動`gdb`并將其附加到正在運行的進程。
* 在`numba.gdb()`函數調用的站點創建斷點,附加的`gdb`將在此處暫停執行,等待用戶輸入。
使用此功能是最好的例子,繼續上面使用的示例:
|
```py
1
2
3
4
5
6
7
8
9
10
11
12
```
|
```py
from numba import njit, gdb
@njit(debug=True)
def foo(a):
b = a + 1
gdb() # instruct Numba to attach gdb at this location and pause execution
c = a * 2.34
d = (a, b, c)
print(a, b, c, d)
r= foo(123)
print(r)
```
|
在終端中(線路上的`...`本身表示為簡潔起見未顯示的輸出):
```py
$ NUMBA_OPT=0 python demo_gdb.py
Attaching to PID: 27157
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-36.el7
...
Attaching to process 27157
...
Reading symbols from <elided for brevity> ...done.
0x00007f0380c31550 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Breakpoint 1 at 0x7f036ac388f0: file numba/_helperlib.c, line 1090.
Continuing.
Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) s
Single stepping until exit from function _ZN5numba7targets8gdb_hook8hook_gdb12$3clocals$3e8impl$242E5Tuple,
which has no line number information.
__main__::foo$241(long long) () at demo_gdb.py:7
7 c = a * 2.34
(gdb) l
2
3 @njit(debug=True)
4 def foo(a):
5 b = a + 1
6 gdb() # instruct Numba to attach gdb at this location and pause execution
7 c = a * 2.34
8 d = (a, b, c)
9 print(a, b, c, d)
10
11 r= foo(123)
(gdb) p a
$1 = 123
(gdb) p b
$2 = 124
(gdb) p c
$3 = 0
(gdb) n
8 d = (a, b, c)
(gdb) p c
$4 = 287.81999999999999
```
在上面的例子中可以看出,代碼的執行在`numba_gdb_breakpoint`函數結束時`gdb()`函數調用的位置暫停(這是用`gdb`注冊為斷點的 Numba 內部符號)。此時發出`step`會移動到已編譯的 Python 源的堆棧幀。從那里可以看出,已經評估了變量`a`和`b`,但`c`沒有,正如通過打印它們的值所證明的那樣,這正如`gdb()`調用的位置所預期的那樣。發出`next`然后評估線`7`和`c`被賦予一個值,如最終印刷品所示。
### 1.15.8.3。運行`gdb`
`numba.gdb()`提供的功能(啟動并將`gdb`連接到執行進程并在斷點處暫停)也可作為兩個單獨的功能使用:
* `numba.gdb_init()`此函數在調用站點注入代碼以啟動并將`gdb`附加到執行進程,但不會暫停執行。
* `numba.gdb_breakpoint()`此函數在調用站點注入代碼,該代碼將調用在 Numba `gdb`支持中注冊為斷點的特殊`numba_gdb_breakpoint`函數。這將在下一節中演示。
此功能可實現更復雜的調試功能。再次,以示例為動機,調試'segfault'(內存訪問違規信令`SIGSEGV`):
|
```py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
```
|
```py
from numba import njit, gdb_init
import numpy as np
@njit(debug=True)
def foo(a, index):
gdb_init() # instruct Numba to attach gdb at this location, but not to pause execution
b = a + 1
c = a * 2.34
d = c[index] # access an address that is a) invalid b) out of the page
print(a, b, c, d)
bad_index = int(1e9) # this index is invalid
z = np.arange(10)
r = foo(z, bad_index)
print(r)
```
|
在終端中(線路上的`...`本身表示為簡潔起見未顯示的輸出):
```py
$ python demo_gdb_segfault.py
Attaching to PID: 5444
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-36.el7
...
Attaching to process 5444
...
Reading symbols from <elided for brevity> ...done.
0x00007f8d8010a550 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Breakpoint 1 at 0x7f8d6a1118f0: file numba/_helperlib.c, line 1090.
Continuing.
0x00007fa7b810a41f in __main__::foo$241(Array<long long, 1, C, mutable, aligned>, long long) () at demo_gdb_segfault.py:9
9 d = c[index] # access an address that is a) invalid b) out of the page
(gdb) p index
$1 = 1000000000
(gdb) p c
$2 = "p\202\017\364\371U\000\000\000\000\000\000\000\000\000\000\n\000\000\000\000\000\000\000\b\000\000\000\000\000\000\000\240\202\017\364\371U\000\000\n\000\000\000\000\000\000\000\b\000\000\000\000\000\000"
(gdb) whatis c
type = {i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64]}
(gdb) x /32xb c
0x7ffd56195068: 0x70 0x82 0x0f 0xf4 0xf9 0x55 0x00 0x00
0x7ffd56195070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffd56195078: 0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffd56195080: 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00
```
在`gdb`輸出中,可以注意到`numba_gdb_breakpoint`功能被注冊為斷點(其符號在`numba/_helperlib.c`中),捕獲了`SIGSEGV`信號,并且發生訪問沖突的行是打印。
繼續作為調試會話演示的示例,可以打印第一個`index`,顯然是 1e9。打印`c`會產生大量字節,因此需要查找類型。 `c`的類型根據其`DataModel`顯示數組`c`的布局(在 Numba 源`numba.datamodel.models`中查看布局,`ArrayModel`在下面顯示為了方便)。
```py
class ArrayModel(StructModel):
def __init__(self, dmm, fe_type):
ndim = fe_type.ndim
members = [
('meminfo', types.MemInfoPointer(fe_type.dtype)),
('parent', types.pyobject),
('nitems', types.intp),
('itemsize', types.intp),
('data', types.CPointer(fe_type.dtype)),
('shape', types.UniTuple(types.intp, ndim)),
('strides', types.UniTuple(types.intp, ndim)),
]
```
從`gdb`(`type = {i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64]}`)檢查的類型直接對應于`ArrayModel`的成員。鑒于 segfault 來自無效訪問,檢查數組中的項目數并將其與請求的索引進行比較將是有益的。
檢查`c`,(`x /32xb c`)的存儲器,前 16 個字節是`meminfo`指針和`parent` `pyobject`對應的兩個`i8*`。接下來的兩組 8 字節是分別對應于`nitems`和`itemsize`的`i64` / `intp`類型。顯然它們的值是`0x0a`和`0x08`,這是有意義的,因為輸入數組`a`有 10 個元素,類型為`int64`,寬度為 8 個字節。因此很明顯,segfault 來自包含`10`項的數組中索引`1000000000`的無效訪問。
### 1.15.8.4。向代碼 添加斷點
下一個示例演示了如何使用通過調用`numba.gdb_breakpoint()`函數定義的多個斷點:
|
```py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
```
|
```py
from numba import njit, gdb_init, gdb_breakpoint
@njit(debug=True)
def foo(a):
gdb_init() # instruct Numba to attach gdb at this location
b = a + 1
gdb_breakpoint() # instruct gdb to break at this location
c = a * 2.34
d = (a, b, c)
gdb_breakpoint() # and to break again at this location
print(a, b, c, d)
r= foo(123)
print(r)
```
|
在終端中(線路上的`...`本身表示為簡潔起見未顯示的輸出):
```py
$ NUMBA_OPT=0 python demo_gdb_breakpoints.py
Attaching to PID: 20366
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-36.el7
...
Attaching to process 20366
Reading symbols from <elided for brevity> ...done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.17.so.debug...done.
0x00007f631db5e550 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Breakpoint 1 at 0x7f6307b658f0: file numba/_helperlib.c, line 1090.
Continuing.
Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) step
__main__::foo$241(long long) () at demo_gdb_breakpoints.py:8
8 c = a * 2.34
(gdb) l
3 @njit(debug=True)
4 def foo(a):
5 gdb_init() # instruct Numba to attach gdb at this location
6 b = a + 1
7 gdb_breakpoint() # instruct gdb to break at this location
8 c = a * 2.34
9 d = (a, b, c)
10 gdb_breakpoint() # and to break again at this location
11 print(a, b, c, d)
12
(gdb) p b
$1 = 124
(gdb) p c
$2 = 0
(gdb) continue
Continuing.
Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) step
11 print(a, b, c, d)
(gdb) p c
$3 = 287.81999999999999
```
從`gdb`輸出可以看出,執行在第 8 行暫停作為斷點被擊中,并且在發出`continue`之后,它再次在第 11 行處破壞,其中下一個斷點被擊中。
### 1.15.8.5。并行區域調試
以下示例非常復雜,它按照上面的示例從一開始就使用`gdb`檢測執行,但它也使用線程并利用斷點功能。此外,并行部分的最后一次迭代調用函數`work`,在這種情況下實際上只是對`glibc`的`free(3)`的綁定,但同樣可能是一些因未知原因呈現段錯誤的函數。
|
```py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
```
|
```py
from numba import njit, prange, gdb_init, gdb_breakpoint
import ctypes
def get_free():
lib = ctypes.cdll.LoadLibrary('libc.so.6')
free_binding = lib.free
free_binding.argtypes = [ctypes.c_void_p,]
free_binding.restype = None
return free_binding
work = get_free()
@njit(debug=True, parallel=True)
def foo():
gdb_init() # instruct Numba to attach gdb at this location, but not to pause execution
counter = 0
n = 9
for i in prange(n):
if i > 3 and i < 8: # iterations 4, 5, 6, 7 will break here
gdb_breakpoint()
if i == 8: # last iteration segfaults
work(0xBADADD)
counter += 1
return counter
r = foo()
print(r)
```
|
在終端中(線路上的`...`本身表示為簡潔起見未顯示的輸出),請注意`NUMBA_NUM_THREADS`的設置為 4,以確保并行部分中有 4 個線程運行:
```py
$ NUMBA_NUM_THREADS=4 NUMBA_OPT=0 python demo_gdb_threads.py
Attaching to PID: 21462
...
Attaching to process 21462
[New LWP 21467]
[New LWP 21468]
[New LWP 21469]
[New LWP 21470]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f59ec31756d in nanosleep () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Breakpoint 1 at 0x7f59d631e8f0: file numba/_helperlib.c, line 1090.
Continuing.
[Switching to Thread 0x7f59d1fd1700 (LWP 21470)]
Thread 5 "python" hit Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) info threads
Id Target Id Frame
1 Thread 0x7f59eca2f740 (LWP 21462) "python" [email?protected]@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
2 Thread 0x7f59d37d4700 (LWP 21467) "python" [email?protected]@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
3 Thread 0x7f59d2fd3700 (LWP 21468) "python" [email?protected]@GLIBC_2.3.2 ()
at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
4 Thread 0x7f59d27d2700 (LWP 21469) "python" numba_gdb_breakpoint () at numba/_helperlib.c:1090
* 5 Thread 0x7f59d1fd1700 (LWP 21470) "python" numba_gdb_breakpoint () at numba/_helperlib.c:1090
(gdb) thread apply 2-5 info locals
Thread 2 (Thread 0x7f59d37d4700 (LWP 21467)):
No locals.
Thread 3 (Thread 0x7f59d2fd3700 (LWP 21468)):
No locals.
Thread 4 (Thread 0x7f59d27d2700 (LWP 21469)):
No locals.
Thread 5 (Thread 0x7f59d1fd1700 (LWP 21470)):
sched$35 = '\000' <repeats 55 times>
counter__arr = '\000' <repeats 16 times>, "\001\000\000\000\000\000\000\000\b\000\000\000\000\000\000\000\370B]\"hU\000\000\001", '\000' <repeats 14 times>
counter = 0
(gdb) continue
Continuing.
[Switching to Thread 0x7f59d27d2700 (LWP 21469)]
Thread 4 "python" hit Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) continue
Continuing.
[Switching to Thread 0x7f59d1fd1700 (LWP 21470)]
Thread 5 "python" hit Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) continue
Continuing.
[Switching to Thread 0x7f59d27d2700 (LWP 21469)]
Thread 4 "python" hit Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
(gdb) continue
Continuing.
Thread 5 "python" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7f59d1fd1700 (LWP 21470)]
__GI___libc_free (mem=0xbadadd) at malloc.c:2935
2935 if (chunk_is_mmapped(p)) /* release mmapped memory. */
(gdb) bt
#0 __GI___libc_free (mem=0xbadadd) at malloc.c:2935
#1 0x00007f59d37ded84 in $3cdynamic$3e::__numba_parfor_gufunc__0x7ffff80a61ae3e31$244(Array<unsigned long long, 1, C, mutable, aligned>, Array<long long, 1, C, mutable, aligned>) () at <string>:24
#2 0x00007f59d17ce326 in __gufunc__._ZN13$3cdynamic$3e45__numba_parfor_gufunc__0x7ffff80a61ae3e31$244E5ArrayIyLi1E1C7mutable7alignedE5ArrayIxLi1E1C7mutable7alignedE ()
#3 0x00007f59d37d7320 in thread_worker ()
from <path>/numba/numba/npyufunc/workqueue.cpython-37m-x86_64-linux-gnu.so
#4 0x00007f59ec626e25 in start_thread (arg=0x7f59d1fd1700) at pthread_create.c:308
#5 0x00007f59ec350bad in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
```
在輸出中可以看到有 4 個線程被啟動并且它們都在斷點處中斷,進一步`Thread 5`接收到信號`SIGSEGV`并且后向跟蹤顯示它來自`__GI___libc_free`,其中包含無效地址`mem`,如預期的那樣。
### 1.15.8.6。使用`gdb`命令語言
`numba.gdb()`和`numba.gdb_init()`函數都接受無限的字符串參數,這些參數在初始化時將作為命令行參數直接傳遞給`gdb`,這樣可以輕松地在其他函數上設置斷點并執行重復的調試任務,而無需手動每次都輸入它們。例如,此代碼在附加`gdb`的情況下運行并在`_dgesdd`上設置斷點(例如,傳遞給 LAPACK 的雙精度除法和征服者 SVD 函數的參數需要調試)。
|
```py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
```
|
```py
from numba import njit, gdb
import numpy as np
@njit(debug=True)
def foo(a):
# instruct Numba to attach gdb at this location and on launch, switch
# breakpoint pending on , and then set a breakpoint on the function
# _dgesdd, continue execution, and once the breakpoint is hit, backtrace
gdb('-ex', 'set breakpoint pending on',
'-ex', 'b dgesdd_',
'-ex','c',
'-ex','bt')
b = a + 10
u, s, vh = np.linalg.svd(b)
return s # just return singular values
z = np.arange(70.).reshape(10, 7)
r = foo(z)
print(r)
```
|
在終端中(一行上的`...`本身表示出于簡潔而未顯示的輸出),請注意不需要交互來中斷和回溯:
```py
$ NUMBA_OPT=0 python demo_gdb_args.py
Attaching to PID: 22300
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-36.el7
...
Attaching to process 22300
Reading symbols from <py_env>/bin/python3.7...done.
0x00007f652305a550 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Breakpoint 1 at 0x7f650d0618f0: file numba/_helperlib.c, line 1090.
Continuing.
Breakpoint 1, numba_gdb_breakpoint () at numba/_helperlib.c:1090
1090 }
Breakpoint 2 at 0x7f65102322e0 (2 locations)
Continuing.
Breakpoint 2, 0x00007f65182be5f0 in mkl_lapack.dgesdd_ ()
from <py_env>/lib/python3.7/site-packages/numpy/core/../../../../libmkl_rt.so
#0 0x00007f65182be5f0 in mkl_lapack.dgesdd_ ()
from <py_env>/lib/python3.7/site-packages/numpy/core/../../../../libmkl_rt.so
#1 0x00007f650d065b71 in numba_raw_rgesdd ([email?protected]=100 'd', jobz=<optimized out>, [email?protected]=65 'A', [email?protected]=10,
[email?protected]=7, [email?protected]=0x561c6fbb20c0, [email?protected]=10, s=0x561c6facf3a0, u=0x561c6fb680e0, ldu=10, vt=0x561c6fd375c0,
ldvt=7, work=0x7fff4c926c30, lwork=-1, iwork=0x7fff4c926c40, info=0x7fff4c926c20) at numba/_lapack.c:1277
#2 0x00007f650d06768f in numba_ez_rgesdd (ldvt=7, vt=0x561c6fd375c0, ldu=10, u=0x561c6fb680e0, s=0x561c6facf3a0, lda=10,
a=0x561c6fbb20c0, n=7, m=10, jobz=65 'A', kind=<optimized out>) at numba/_lapack.c:1307
#3 numba_ez_gesdd (kind=<optimized out>, jobz=<optimized out>, m=10, n=7, a=0x561c6fbb20c0, lda=10, s=0x561c6facf3a0,
u=0x561c6fb680e0, ldu=10, vt=0x561c6fd375c0, ldvt=7) at numba/_lapack.c:1477
#4 0x00007f650a3147a3 in numba::targets::linalg::svd_impl::$3clocals$3e::svd_impl$243(Array<double, 2, C, mutable, aligned>, omitted$28default$3d1$29) ()
#5 0x00007f650a1c0489 in __main__::foo$241(Array<double, 2, C, mutable, aligned>) () at demo_gdb_args.py:15
#6 0x00007f650a1c2110 in cpython::__main__::foo$241(Array<double, 2, C, mutable, aligned>) ()
#7 0x00007f650cd096a4 in call_cfunc ()
from <path>/numba/numba/_dispatcher.cpython-37m-x86_64-linux-gnu.so
...
```
### 1.15.8.7。 `gdb`綁定如何工作?
對于 Numba 應用程序的高級用戶和調試程序,了解概述的`gdb`綁定的一些內部實現細節非常重要。 `numba.gdb()`和`numba.gdb_init()`功能通過將以下內容注入函數的 LLVM IR 來工作:
* 在函數的調用站點首先注入`getpid(3)`的調用以獲取執行進程的 PID 并將其存儲以供以后使用,然后注入`fork(3)`調用:
* 在父母:
* 注入一個呼叫`sleep(3)`(因此在`gdb`加載時暫停)。
* 注入`numba_gdb_breakpoint`功能(僅`numba.gdb()`執行此操作)。
* 在孩子:
* 使用參數`numba.config.GDB_BINARY`,`attach`命令和先前記錄的 PID 注入`execl(3)`。 Numba 有一個特殊的`gdb`命令文件,其中包含斷開符號`numba_gdb_breakpoint`然后`finish`的指令,這是為了確保程序在斷點處停止,但它停止的幀是編譯的 Python 幀(或者一個`step`遠離,取決于優化)。此命令文件也會添加到參數中,最后添加任何用戶指定的參數。
在`numba.gdb_breakpoint()`的呼叫站點,呼叫被注入特殊的`numba_gdb_breakpoint`符號,該符號已經注冊并被立即作為中斷位置和`finish`進行檢測。
結果,例如, `numba.gdb()`調用將導致程序中的 fork,父進程將在子進程啟動`gdb`時暫停,并將其附加到父進程并告訴父進程繼續。啟動的`gdb`將`numba_gdb_breakpoint`符號注冊為斷點,當父母繼續并停止睡眠時,它將立即調用孩子將要打破的`numba_gdb_breakpoint`。額外的`numba.gdb_breakpoint()`調用會創建對已注冊斷點的調用,因此程序也會在這些位置中斷。
## 1.15.9。調試 CUDA Python 代碼
### 1.15.9.1。使用模擬器
CUDA Python 代碼可以使用 CUDA Simulator 在 Python 解釋器中運行,允許使用 Python 調試器或 print 語句進行調試。要啟用 CUDA 仿真器,請將環境變量 [`NUMBA_ENABLE_CUDASIM`](../reference/envvars.html#envvar-NUMBA_ENABLE_CUDASIM) 設置為 1.有關 CUDA 仿真器的更多信息,請參閱 [CUDA 仿真器文檔](../cuda/simulator.html#simulator)。
### 1.15.9.2。調試信息
通過將`debug`參數設置為`cuda.jit`到`True`(`@cuda.jit(debug=True)`),Numba 將在編譯的 CUDA 代碼中發出源位置。與 CPU 目標不同,只有文件名和行信息可用,但不會發出變量類型信息。該信息足以使用 [cuda-memcheck](http://docs.nvidia.com/cuda/cuda-memcheck/index.html) 調試內存錯誤。
例如,給出以下 cuda python 代碼:
|
```py
1
2
3
4
5
6
7
8
9
```
|
```py
import numpy as np
from numba import cuda
@cuda.jit(debug=True)
def foo(arr):
arr[cuda.threadIdx.x] = 1
arr = np.arange(30)
foo[1, 32](arr) # more threads than array elements
```
|
我們可以使用`cuda-memcheck`來查找內存錯誤:
```py
$ cuda-memcheck python chk_cuda_debug.py
========= CUDA-MEMCHECK
========= Invalid __global__ write of size 8
========= at 0x00000148 in /home/user/chk_cuda_debug.py:6:cudapy::__main__::foo$241(Array<__int64, int=1, C, mutable, aligned>)
========= by thread (31,0,0) in block (0,0,0)
========= Address 0x500a600f8 is out of bounds
...
=========
========= Invalid __global__ write of size 8
========= at 0x00000148 in /home/user/chk_cuda_debug.py:6:cudapy::__main__::foo$241(Array<__int64, int=1, C, mutable, aligned>)
========= by thread (30,0,0) in block (0,0,0)
========= Address 0x500a600f0 is out of bounds
...
```
- 1. 用戶手冊
- 1.1。 Numba 的約 5 分鐘指南
- 1.2。概述
- 1.3。安裝
- 1.4。使用@jit 編譯 Python 代碼
- 1.5。使用@generated_jit 進行靈活的專業化
- 1.6。創建 Numpy 通用函數
- 1.7。用@jitclass 編譯 python 類
- 1.8。使用@cfunc 創建 C 回調
- 1.9。提前編譯代碼
- 1.10。使用@jit 自動并行化
- 1.11。使用@stencil裝飾器
- 1.12。從 JIT 代碼 中回調到 Python 解釋器
- 1.13。性能提示
- 1.14。線程層
- 1.15。故障排除和提示
- 1.16。常見問題
- 1.17。示例
- 1.18。會談和教程
- 2. 參考手冊
- 2.1。類型和簽名
- 2.2。即時編譯
- 2.3。提前編譯
- 2.4。公用事業
- 2.5。環境變量
- 2.6。支持的 Python 功能
- 2.7。支持的 NumPy 功能
- 2.8。與 Python 語義的偏差
- 2.9。浮點陷阱
- 2.10。 Python 2.7 壽命終止計劃
- 3. 用于 CUDA GPU 的 Numba
- 3.1。概述
- 3.2。編寫 CUDA 內核
- 3.3。內存管理
- 3.4。編寫設備功能
- 3.5。 CUDA Python 中支持的 Python 功能
- 3.6。支持的原子操作
- 3.7。隨機數生成
- 3.8。設備管理
- 3.10。示例
- 3.11。使用 CUDA 模擬器 調試 CUDA Python
- 3.12。 GPU 減少
- 3.13。 CUDA Ufuncs 和廣義 Ufuncs
- 3.14。共享 CUDA 內存
- 3.15。 CUDA 陣列接口
- 3.16。 CUDA 常見問題
- 4. CUDA Python 參考
- 4.1。 CUDA 主機 API
- 4.2。 CUDA 內核 API
- 4.3。內存管理
- 5. 用于 AMD ROC GPU 的 Numba
- 5.1。概述
- 5.2。編寫 HSA 內核
- 5.3。內存管理
- 5.4。編寫設備功能
- 5.5。支持的原子操作
- 5.6。代理商
- 5.7。 ROC Ufuncs 和廣義 Ufuncs
- 5.8。示例
- 6. 擴展 Numba
- 6.1。高級擴展 API
- 6.2。低級擴展 API
- 6.3。示例:間隔類型
- 7. 開發者手冊
- 7.1。貢獻給 Numba
- 7.2。 Numba 建筑
- 7.3。多態調度
- 7.4。關于發電機的注意事項
- 7.5。關于 Numba Runtime 的注意事項
- 7.6。使用 Numba Rewrite Pass 獲得樂趣和優化
- 7.7。實時變量分析
- 7.8。上市
- 7.9。模板注釋
- 7.10。關于自定義管道的注意事項
- 7.11。環境對象
- 7.12。哈希 的注意事項
- 7.13。 Numba 項目路線圖
- 8. Numba 增強建議
- 9. 術語表