精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

用GDB演示Python和C調(diào)試

jf_pJlTbmA9 ? 來(lái)源:NVIDIA ? 作者:NVIDIA ? 2023-07-05 16:30 ? 次閱讀

調(diào)試很困難??缍喾N語(yǔ)言調(diào)試尤其具有挑戰(zhàn)性,跨設(shè)備調(diào)試通常需要一個(gè)具有不同技能和專(zhuān)業(yè)知識(shí)的團(tuán)隊(duì)來(lái)揭示潛在問(wèn)題

然而,項(xiàng)目通常需要使用多種語(yǔ)言,以確保必要時(shí)的高性能、用戶(hù)友好的體驗(yàn)以及可能的兼容性。不幸的是,沒(méi)有一種編程語(yǔ)言能夠提供上述所有功能,這就要求開(kāi)發(fā)人員變得多才多藝。

這篇文章展示了RAPIDS該團(tuán)隊(duì)著手調(diào)試多種編程語(yǔ)言,包括使用GDB以識(shí)別和解決死鎖。該團(tuán)隊(duì)致力于設(shè)計(jì)加速和擴(kuò)展數(shù)據(jù)科學(xué)解決方案的軟件。

這篇文章中的 bug 是RAPIDS 項(xiàng)目這一問(wèn)題在 2019 年夏天得到了確認(rèn)和解決。它涉及到一個(gè)包含多種編程語(yǔ)言的復(fù)雜堆棧,主要是 C 、 C ++和 Python ,以及CUDA對(duì)于 GPU 加速度

記錄這個(gè)歷史錯(cuò)誤及其解決方案有幾個(gè)目的,包括:

用 GDB 演示 Python 和 C 調(diào)試

提出關(guān)于如何診斷死鎖的想法

更好地理解混合 Python 和 CUDA

這篇文章中的內(nèi)容應(yīng)該有助于你理解這些錯(cuò)誤是如何表現(xiàn)出來(lái)的,以及如何在你自己的工作中解決類(lèi)似的問(wèn)題。

Bug 描述

為了高效和高性能, RAPIDS 依賴(lài)于各種庫(kù)進(jìn)行多種不同的操作。舉幾個(gè)例子, RAPIDS 使用CuPy和cuDF以分別計(jì)算 GPU 上的數(shù)組和數(shù)據(jù)幀。Numba是一個(gè)即時(shí)編譯器,可用于加速 GPU 上用戶(hù)定義的 Python 操作

此外Dask用于將計(jì)算擴(kuò)展到多個(gè) GPU 和多個(gè)節(jié)點(diǎn)。手頭的 bug 中的最后一塊拼圖是UCX, a communication framework used to leverage a variety of interconnects , such as InfiniBand and NVLink .

這種僵局首次出現(xiàn)在 2019 年 8 月,也就是 UCX 引入堆棧后不久。事實(shí)證明,死鎖以前在沒(méi)有 UCX 的情況下表現(xiàn)出來(lái)(使用 Dask 默認(rèn)的 TCP 通信器),只是偶爾出現(xiàn)。

死鎖發(fā)生時(shí),我們花了很多時(shí)間探索這個(gè)空間。盡管當(dāng)時(shí)未知,但該錯(cuò)誤可能發(fā)生在特定的操作中,例如group by aggregation,merge/joins,repartitioning,或在任何庫(kù)的特定版本中,包括 cuDF 、 CuPy 、 Dask 、 UCX 等。因此,有許多方面需要探索。

準(zhǔn)備調(diào)試

接下來(lái)的部分將向您介紹如何為調(diào)試做準(zhǔn)備。

設(shè)置最小復(fù)制機(jī)

找到一個(gè)最小的復(fù)制器是調(diào)試任何東西的關(guān)鍵。這個(gè)問(wèn)題最初是在運(yùn)行 8 GPU 的工作流中發(fā)現(xiàn)的。隨著時(shí)間的推移,我們將其減少到兩個(gè) GPU 。擁有一個(gè)最小的復(fù)制器對(duì)于輕松地與他人共享錯(cuò)誤并獲得更廣泛團(tuán)隊(duì)的時(shí)間和關(guān)注至關(guān)重要。

設(shè)置您的環(huán)境

在深入研究這個(gè)問(wèn)題之前,先設(shè)置好你的環(huán)境。 0 . 10 版本的 RAPIDS (于 2019 年 10 月發(fā)布)可以最低限度地再現(xiàn)該漏洞??梢允褂?Conda 或 Docker 來(lái)設(shè)置環(huán)境(請(qǐng)參閱本文后面的相應(yīng)部分)。

整個(gè)過(guò)程假設(shè)使用 Linux 。由于 UCX 在 Windows 或 MacOS 上不受支持,因此在這些操作系統(tǒng)上無(wú)法復(fù)制。

Conda

首先,安裝Miniconda。初次安裝后,強(qiáng)烈建議您安裝mamba通過(guò)運(yùn)行以下腳本:

conda install mamba -n base -c conda-forge

然后運(yùn)行以下腳本創(chuàng)建并激活一個(gè) RAPIDS 0 . 10 的 conda 環(huán)境:

mamba create -n rapids-0.10 -c rapidsai -c nvidia -c conda-forge rapids=0.10 glog=0.4 cupy=6.7 numba=0.45.1 ucx-py=0.11 ucx=1.7 ucx-proc=*=gpu libnuma dask=2.30 dask-core=2.30 distributed=2.30 gdb
conda activate rapids-0.10

我們建議曼巴加快環(huán)境分辨率。跳過(guò)該步驟并替換mamba具有conda應(yīng)該也能工作,但可能會(huì)慢得多。

Docker

或者,您可以使用 Docker 重現(xiàn)該錯(cuò)誤。在你擁有NVIDIA Container Toolkit之后按照這些說(shuō)明進(jìn)行設(shè)置。

docker run -it --rm --cap-add sys_admin --cap-add sys_ptrace --ipc shareable --net host --gpus all rapidsai/rapidsai:0.10-cuda10.0-runtime-ubuntu18.04 /bin/bash

在容器中,安裝mamba以加快環(huán)境分辨率。

conda create -n mamba -c conda-forge mamba -y

然后,安裝 UCX / UCX-Py ,然后libnuma,這是一個(gè) UCX 依賴(lài)項(xiàng)。此外,將 Dask 升級(jí)到集成了 UCX 支持的版本。為了以后進(jìn)行調(diào)試,還可以安裝 GDB 。

/opt/conda/envs/mamba/bin/mamba install -y -c rapidsai -c nvidia -c conda-forge dask=2.30 dask-core=2.30 distributed=2.30 fsspec=2022.11.0 libnuma ucx-py=0.11 ucx=1.7 ucx-proc=*=gpu gdb -p /opt/conda/envs/rapids

調(diào)試

本節(jié)詳細(xì)介紹了這個(gè)特定問(wèn)題是如何遇到并最終解決的,并提供了詳細(xì)的分步概述。您還可以復(fù)制和練習(xí)一些所描述的概念。

正在運(yùn)行(或掛起)

有問(wèn)題的調(diào)試問(wèn)題肯定不僅限于單個(gè)計(jì)算問(wèn)題,但使用我們?cè)?2019 年使用的相同工作流更容易。可以通過(guò)運(yùn)行以下腳本將該腳本下載到本地環(huán)境:

wget 
https://gist.githubusercontent.com/pentschev/9ce97f8efe370552c7dd5e84b64d3c92/raw/424c9cf95f31c18d32a9481f78dd241e08a071a9/cudf-deadlock.py

要進(jìn)行復(fù)制,請(qǐng)執(zhí)行以下操作:

OPENBLAS_NUM_THREADS=1 UCX_RNDV_SCHEME=put_zcopy UCX_MEMTYPE_CACHE=n UCX_TLS=sockcm,tcp,cuda_copy,cuda_ipc python cudf-deadlock.py

在幾次迭代中(可能只有一兩次),您應(yīng)該會(huì)看到前面的程序掛起?,F(xiàn)在真正的工作開(kāi)始了。

僵局

死鎖的一個(gè)好特性是進(jìn)程和線(xiàn)程(如果你知道如何調(diào)查它們)可以顯示它們當(dāng)前正在嘗試做什么。你可以推斷出是什么導(dǎo)致了死鎖

關(guān)鍵工具是 GDB 。然而, PDB 最初花了很多時(shí)間來(lái)調(diào)查 Python 在每一步都在做什么。 GDB 可以連接到活動(dòng)進(jìn)程,因此您必須首先了解進(jìn)程及其關(guān)聯(lián) ID 是什么:

(rapids) root@dgx13:/rapids/notebooks# ps ax | grep python
   19 pts/0    S      0:01 /opt/conda/envs/rapids/bin/python /opt/conda/envs/rapids/bin/jupyter-lab --allow-root --ip=0.0.0.0 --no-browser --NotebookApp.token=
  865 pts/0    Sl+    0:03 python cudf-deadlock.py
  871 pts/0    S+     0:00 /opt/conda/envs/rapids/bin/python -c from multiprocessing.semaphore_tracker import main;main(69)
  873 pts/0    Sl+    0:08 /opt/conda/envs/rapids/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=70, pipe_handle=76) --multiprocessing-fork
  885 pts/0    Sl+    0:07 /opt/conda/envs/rapids/bin/python -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=70, pipe_handle=85) --multiprocessing-fork

四個(gè) Python 過(guò)程與此問(wèn)題相關(guān):

Dask 客戶(hù)端 (865)

Dask 調(diào)度程序 (871)

兩名 Dask 工人 (873和885)

有趣的是,自從最初調(diào)查這個(gè)錯(cuò)誤以來(lái),在調(diào)試 Python 方面已經(jīng)取得了重大進(jìn)展。 2019 年, RAPIDS 在 Python 3 . 6 上運(yùn)行,該版本已經(jīng)有了調(diào)試較低堆棧的工具,但只有當(dāng) Python 以調(diào)試模式構(gòu)建時(shí)。這可能需要重建整個(gè)軟件堆棧,這在像這樣的復(fù)雜情況下是令人望而卻步的

由于 Python 3 . 8debug builds use the same ABI as release builds,極大地簡(jiǎn)化了 C 和 Python 堆棧組合的調(diào)試。我們?cè)谶@篇文章中沒(méi)有涉及到這一點(diǎn)。

GDB 勘探

使用gdb連接到最后一個(gè)正在運(yùn)行的進(jìn)程( Dask 工人之一):

(rapids) root@dgx13:/rapids/notebooks# gdb -p 885
Attaching to process 885
[New LWP 889]
[New LWP 890]
[New LWP 891]
[New LWP 892]
[New LWP 893]
[New LWP 894]
[New LWP 898]
[New LWP 899]
[New LWP 902]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007f5494d48938 in pthread_rwlock_wrlock () from /lib/x86_64-linux-gnu/libpthread.so.0
(gdb)

每個(gè) Dask 工作程序都有幾個(gè)線(xiàn)程(通信、計(jì)算、管理等等)。使用gdb命令info threads檢查每個(gè)線(xiàn)程在做什么。

(gdb) info threads
  Id   Target Id                                        Frame
* 1    Thread 0x7f5495177740 (LWP 885) "python"         0x00007f5494d48938 in pthread_rwlock_wrlock () from /lib/x86_64-linux-gnu/libpthread.so.0
  2    Thread 0x7f5425b98700 (LWP 889) "python"         0x00007f5494d4d384 in read () from /lib/x86_64-linux-gnu/libpthread.so.0
  3    Thread 0x7f5425357700 (LWP 890) "python"         0x00007f5494d49f85 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/x86_64-linux-gnu/libpthread.so.0
  4    Thread 0x7f5424b16700 (LWP 891) "python"         0x00007f5494d49f85 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/x86_64-linux-gnu/libpthread.so.0
  5    Thread 0x7f5411fff700 (LWP 892) "cuda-EvtHandlr" 0x00007f5494a5fbf9 in poll () from /lib/x86_64-linux-gnu/libc.so.6
  6    Thread 0x7f54117fe700 (LWP 893) "python"         0x00007f5494a6cbb7 in epoll_wait () from /lib/x86_64-linux-gnu/libc.so.6
  7    Thread 0x7f5410d3c700 (LWP 894) "python"         0x00007f5494d4c6d6 in do_futex_wait.constprop () from /lib/x86_64-linux-gnu/libpthread.so.0
  8    Thread 0x7f53f6048700 (LWP 898) "python"         0x00007f5494d49f85 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/x86_64-linux-gnu/libpthread.so.0
  9    Thread 0x7f53f5847700 (LWP 899) "cuda-EvtHandlr" 0x00007f5494a5fbf9 in poll () from /lib/x86_64-linux-gnu/libc.so.6
  10   Thread 0x7f53a39d9700 (LWP 902) "python"         0x00007f5494d4c6d6 in do_futex_wait.constprop () from /lib/x86_64-linux-gnu/libpthread.so.0

這個(gè) Dask 工作程序有 10 個(gè)線(xiàn)程,其中一半似乎在等待互斥/ futex 。另一半cuda-EvtHandlr正在輪詢(xún)。通過(guò)查看回溯,觀(guān)察當(dāng)前線(xiàn)程(由左側(cè)的*表示)線(xiàn)程 1 正在做什么:

(gdb) bt
#0  0x00007f5494d48938 in pthread_rwlock_wrlock () from /lib/x86_64-linux-gnu/libpthread.so.0
#1  0x00007f548bc770a8 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#2  0x00007f548ba3d87c in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#3  0x00007f548bac6dfa in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#4  0x00007f54240ba372 in uct_cuda_ipc_iface_event_fd_arm (tl_iface=0x562398656990, events=) at cuda_ipc/cuda_ipc_iface.c:271
#5  0x00007f54241d4fc2 in ucp_worker_arm (worker=0x5623987839e0) at core/ucp_worker.c:1990
#6  0x00007f5424259b76 in __pyx_pw_3ucp_5_libs_4core_18ApplicationContext_23_blocking_progress_mode_1_fd_reader_callback ()
   from /opt/conda/envs/rapids/lib/python3.6/site-packages/ucp/_libs/core.cpython-36m-x86_64-linux-gnu.so
#7  0x000056239601d5ae in PyObject_Call (func=, args=, kwargs=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Objects/abstract.c:2261
#8  0x00005623960d13a2 in do_call_core (kwdict=0x0, callargs=(), func=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:5120
#9  _PyEval_EvalFrameDefault (f=, throwflag=) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:3404
#10 0x00005623960924b5 in PyEval_EvalFrameEx (throwflag=0, f=Python Exception  Type does not have a target.:
) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:754
#11 _PyFunction_FastCall (globals=, nargs=, args=, co=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4933
#12 fast_function (func=, stack=, nargs=, kwnames=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4968
#13 0x00005623960a13af in call_function (pp_stack=0x7ffdfa2311e8, oparg=, kwnames=0x0)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4872
#14 0x00005623960cfcaa in _PyEval_EvalFrameDefault (f=, throwflag=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:3335
#15 0x00005623960924b5 in PyEval_EvalFrameEx (throwflag=0, Python Exception  Type does not have a target.:
f=) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:754
#16 _PyFunction_FastCall (globals=, nargs=, args=, co=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4933
#17 fast_function (func=, stack=, nargs=, kwnames=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4968
#18 0x00005623960a13af in call_function (pp_stack=0x7ffdfa2313f8, oparg=, kwnames=0x0)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4872
#19 0x00005623960cfcaa in _PyEval_EvalFrameDefault (f=, throwflag=)
    at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:3335
#20 0x00005623960924b5 in PyEval_EvalFrameEx (throwflag=0, Python Exception  Type does not have a target.:
f=) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:754

查看堆棧的前 20 幀(為了簡(jiǎn)潔起見(jiàn),后面的幀都是不相關(guān)的 Python 內(nèi)部調(diào)用,省略了),您可以看到一些內(nèi)部的 Python 調(diào)用:_PyEval_EvalFrameDefault,_PyFunction_FastCall和_PyEval_EvalCodeWithName。也有一些電話(huà)libcuda.so.

這一觀(guān)察結(jié)果暗示可能存在死鎖。它可以是 Python 、 CUDA ,也可能是兩者都有。這個(gè)Linux Wikibook on Deadlocks包含調(diào)試死鎖的方法,以幫助您向前邁進(jìn)

然而pthread_mutex_lock正如維基解密中所描述的,它在這里pthread_rwlock_wrlock.

(gdb) bt
#0  0x00007f8e94762938 in pthread_rwlock_wrlock () from /lib/x86_64-linux-gnu/libpthread.so.0
#1  0x00007f8e8b6910a8 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#2  0x00007f8e8b45787c in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
…

根據(jù)documentation for pthread_rwlock_wrlock,只需要一個(gè)參數(shù),rwlock,這是一個(gè)讀/寫(xiě)鎖?,F(xiàn)在,看看代碼在做什么,并列出源代碼:

(gdb) list
6       /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Programs/python.c: No such file or directory.

沒(méi)有調(diào)試符號(hào)。回到 Linux Wikibook ,您可以查看寄存器。您也可以在 GDB 中這樣做:

(gdb) info reg
rax            0xfffffffffffffe00  -512
rbx            0x5623984aa750      94710878873424
rcx            0x7f5494d48938      140001250937144
rdx            0x3                 3
rsi            0x189               393
rdi            0x5623984aa75c      94710878873436
rbp            0x0                 0x0
rsp            0x7ffdfa230be0      0x7ffdfa230be0
r8             0x0                 0
r9             0xffffffff          4294967295
r10            0x0                 0
r11            0x246               582
r12            0x5623984aa75c      94710878873436
r13            0xca                202
r14            0xffffffff          4294967295
r15            0x5623984aa754      94710878873428
rip            0x7f5494d48938      0x7f5494d48938 
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

問(wèn)題是不知道它們的意思。幸運(yùn)的是,文檔是存在的,例如Guide to x86-64 from Stanford CS107,解釋了前六個(gè)參數(shù)在寄存器中%rdi,%rsi,%rdx,%rcx,%r8和%r9.

如前所述,pthread_rwlock_wrlock只需要一個(gè)參數(shù),所以必須在%rdi剩下的可能會(huì)被用作通用寄存器pthread_rwlock_wrlock.

現(xiàn)在,您需要閱讀%rdi登記你已經(jīng)知道它有一個(gè)類(lèi)pthread_rwlock_t,因此必須可以取消引用:

(gdb) p *(pthread_rwlock_t*)$rdi
$2 = {__data = {__lock = 3, __nr_readers = 0, __readers_wakeup = 0, __writer_wakeup = 898, __nr_readers_queued = 0, __nr_writers_queued = 0, __writer = 0,
    __shared = 0, __pad1 = 0, __pad2 = 0, __flags = 0}, __size = "?03", '?00' , "202?03", '?00' , __align = 3}

顯示的是pthread_rwlock_t反對(duì)libcuda.so傳遞給pthread_rwlock_wrlock– 鎖本身。不幸的是,這些名字并沒(méi)有太大的相關(guān)性。你可以推斷__lock可能意味著同時(shí)嘗試獲取鎖的次數(shù),但這是推斷的范圍

唯一具有非零值的其他屬性是__write_wakeup。 Linux Wikibook 列出了一個(gè)有趣的值,稱(chēng)為_(kāi)_owner,它指向當(dāng)前擁有鎖所有權(quán)的進(jìn)程標(biāo)識(shí)符( PID )。鑒于此pthread_rwlock_t是讀/寫(xiě)鎖,假設(shè)__writer_wakeup指向擁有鎖的進(jìn)程可能是一個(gè)很好的下一步。

關(guān)于 Linux 的一個(gè)事實(shí)是,程序中的每個(gè)線(xiàn)程都像一個(gè)進(jìn)程一樣運(yùn)行。每個(gè)線(xiàn)程都應(yīng)該有一個(gè) PID (或 GDB 中的 LWP )

再次查看進(jìn)程中的所有線(xiàn)程,查找一個(gè) PID 與相同的線(xiàn)程__writer_wakeup。幸運(yùn)的是,有一個(gè)線(xiàn)程確實(shí)具有該 ID :

(gdb) info threads
  Id   Target Id                                        Frame
  8    Thread 0x7f53f6048700 (LWP 898) "python"         0x00007f5494d49f85 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/x86_64-linux-gnu/libpthread.so.0

到目前為止,線(xiàn)程 8 可能擁有線(xiàn)程 1 試圖獲取的鎖。線(xiàn)程 8 的堆??赡軙?huì)提供有關(guān)正在發(fā)生的事情的線(xiàn)索。接下來(lái)運(yùn)行:

(gdb) thread apply 8 bt
Thread 8 (Thread 0x7f53f6048700 (LWP 898) "python"):
#0  0x00007f5494d49f85 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/x86_64-linux-gnu/libpthread.so.0
#1  0x00005623960e59e0 in PyCOND_TIMEDWAIT (cond=0x562396232f40 , mut=0x562396232fc0 , us=5000) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/condvar.h:103
#2  take_gil (tstate=0x5623987ff240) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval_gil.h:224
#3  0x000056239601cf7e in PyEval_RestoreThread (tstate=0x5623987ff240) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:369
#4  0x00005623960e5cd4 in PyGILState_Ensure () at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/pystate.c:895
#5  0x00007f5493610aa7 in _CallPythonObject (pArgs=0x7f53f6042e80, flags=4353, converters=(<_ctypes.PyCSimpleType at remote 0x562396b4d588>,), callable=, setfunc=0x7f549360ba80 , restype=0x7f549369b9d8, mem=0x7f53f6043010) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callbacks.c:141
#6  closure_fcn (cif=, resp=0x7f53f6043010, args=0x7f53f6042e80, userdata=) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callbacks.c:296
#7  0x00007f54935fa3d0 in ffi_closure_unix64_inner () from /opt/conda/envs/rapids/lib/python3.6/lib-dynload/../../libffi.so.6
#8  0x00007f54935fa798 in ffi_closure_unix64 () from /opt/conda/envs/rapids/lib/python3.6/lib-dynload/../../libffi.so.6
#9  0x00007f548ba99dc6 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#10 0x00007f548badd4a5 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#11 0x00007f54935fa630 in ffi_call_unix64 () from /opt/conda/envs/rapids/lib/python3.6/lib-dynload/../../libffi.so.6
#12 0x00007f54935f9fed in ffi_call () from /opt/conda/envs/rapids/lib/python3.6/lib-dynload/../../libffi.so.6
#13 0x00007f549361109e in _call_function_pointer (argcount=6, resmem=0x7f53f6043400, restype=, atypes=0x7f53f6043380, avalues=0x7f53f60433c0, pProc=0x7f548bad61f0 , flags=4353) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callproc.c:831
#14 _ctypes_callproc (pProc=0x7f548bad61f0 , argtuple=, flags=4353, argtypes=, restype=<_ctypes.PyCSimpleType at remote 0x562396b4d588>, checker=0x0) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callproc.c:1195
#15 0x00007f5493611ad5 in PyCFuncPtr_call (self=self@entry=0x7f53ed534750, inargs=, kwds=) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/_ctypes.c:3970
#16 0x000056239601d5ae in PyObject_Call (func=Python Exception  Type does not have a target.:
, args=, kwargs=) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Objects/abstract.c:2261
#17 0x00005623960d13a2 in do_call_core (kwdict=0x0, callargs=(, , , , 0, 1024), func=Python Exception  Type does not have a target.:
) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:5120
#18 _PyEval_EvalFrameDefault (f=, throwflag=) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:3404
#19 0x0000562396017ea8 in PyEval_EvalFrameEx (throwflag=0, f=Python Exception  Type does not have a target.:
) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:754
#20 _PyEval_EvalCodeWithName (_co=, globals=, locals=, args=, argcount=, kwnames=0x0, kwargs=0x7f541805a390, kwcount=, kwstep=1, defs=0x0, defcount=0, kwdefs=0x0, closure=(, , ), name=Python Exception  Type does not have a target.:
, qualname=Python Exception  Type does not have a target.:
) at /home/conda/feedstock_root/build_artifacts/python_1596656032113/work/Python/ceval.c:4166

在堆棧的頂部,它看起來(lái)像是一個(gè)普通的 Python 線(xiàn)程在等待GIL。它看起來(lái)不起眼,所以你可以忽略它,在其他地方尋找線(xiàn)索。這正是我們?cè)?2019 年所做的

更全面地查看堆棧的其余部分,尤其是第 9 幀和第 10 幀:

#9  0x00007f548ba99dc6 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so
#10 0x00007f548badd4a5 in ?? () from /usr/lib/x86_64-linux-gnu/libcuda.so

在這一點(diǎn)上,事情可能看起來(lái)更加令人困惑。線(xiàn)程 1 正在鎖定libcuda.so內(nèi)部構(gòu)件。如果不能訪(fǎng)問(wèn) CUDA 源代碼,調(diào)試將很困難

進(jìn)一步檢查 Thread 8 的堆棧,可以看到兩個(gè)提供提示的幀:

#13 0x00007f549361109e in _call_function_pointer (argcount=6, resmem=0x7f53f6043400, restype=, atypes=0x7f53f6043380, avalues=0x7f53f60433c0, pProc=0x7f548bad61f0 , flags=4353) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callproc.c:831
#14 _ctypes_callproc (pProc=0x7f548bad61f0 , argtuple=, flags=4353, argtypes=, restype=<_ctypes.PyCSimpleType at remote 0x562396b4d588>, checker=0x0) at /usr/local/src/conda/python-3.6.11/Modules/_ctypes/callproc.c:1195

綜上所述,兩個(gè)線(xiàn)程共享一個(gè)鎖。線(xiàn)程 8 正在嘗試獲取 GIL ,并對(duì)進(jìn)行 CUDA 調(diào)用cuOccupancyMaxPotentialBlockSize.

然而libcuda.so對(duì) Python 一無(wú)所知,那么它為什么要試圖獲得 GIL 呢?

的文檔cuOccupancyMaxPotentialBlockSize顯示它需要回調(diào)?;卣{(diào)是可以向另一個(gè)函數(shù)注冊(cè)的函數(shù),以便在某個(gè)時(shí)間點(diǎn)執(zhí)行,從而在該預(yù)定義點(diǎn)有效地執(zhí)行用戶(hù)定義的操作。

這很有趣。接下來(lái),找出那個(gè)電話(huà)是從哪里打來(lái)的。通過(guò)一堆又一堆的代碼—— cuDF 、 Dask 、 RMM 、 CuPy 和 Numba ,可以顯式調(diào)用cuOccupancyMaxPotentialBlockSize在 0 . 45 版本的 Numba 中:

    def get_max_potential_block_size(self, func, b2d_func, memsize, blocksizelimit, flags=None):
        """Suggest a launch configuration with reasonable occupancy.
        :param func: kernel for which occupancy is calculated
        :param b2d_func: function that calculates how much per-block dynamic shared memory 'func' uses based on the block size.
        :param memsize: per-block dynamic shared memory usage intended, in bytes
        :param blocksizelimit: maximum block size the kernel is designed to handle"""


        gridsize = c_int()
        blocksize = c_int()
        b2d_cb = cu_occupancy_b2d_size(b2d_func)
        if not flags:
            driver.cuOccupancyMaxPotentialBlockSize(byref(gridsize), byref(blocksize),
                                                    func.handle,
                                                    b2d_cb,
                                                    memsize, blocksizelimit)
        else:
            driver.cuOccupancyMaxPotentialBlockSizeWithFlags(byref(gridsize), byref(blocksize),
                                                             func.handle, b2d_cb,
                                                             memsize, blocksizelimit, flags)
        return (gridsize.value, blocksize.value)

此函數(shù)在中調(diào)用numba/cuda/compiler:

    def _compute_thread_per_block(self, kernel):
        tpb = self.thread_per_block
        # Prefer user-specified config
        if tpb != 0:
            return tpb
        # Else, ask the driver to give a good cofnig
        else:
            ctx = get_context()
            kwargs = dict(
                func=kernel._func.get(),
                b2d_func=lambda tpb: 0,
                memsize=self.sharedmem,
                blocksizelimit=1024,
            )
            try:
                # Raises from the driver if the feature is unavailable
                _, tpb = ctx.get_max_potential_block_size(**kwargs)
            except AttributeError:
                # Fallback to table-based approach.
                tpb = self._fallback_autotune_best(kernel)
                raise
            return tpb

仔細(xì)查看的函數(shù)定義_compute_thread_per_block,您可以看到一個(gè)寫(xiě)為 Python lambda 的回調(diào):b2d_func=lambda tpb: 0.

啊哈!在這個(gè) CUDA 調(diào)用的中間,回調(diào)函數(shù)必須獲取 Python GIL 才能執(zhí)行只返回 0 的函數(shù)。這是因?yàn)閳?zhí)行任何 Python 代碼都需要 GIL ,并且在任何給定的時(shí)間點(diǎn)只能由單個(gè)線(xiàn)程擁有

用純 C 函數(shù)代替它就解決了這個(gè)問(wèn)題。你可以用 Numba 從 Python 中編寫(xiě)一個(gè)純 C 函數(shù)!

@cfunc("uint64(int32)")
def _b2d_func(tpb):
    return 0

b2d_func=_b2d_func.address

此修復(fù)程序已提交并最終合并到Numba PR #4581Numba 中的這五行代碼更改最終解決了幾個(gè)人在數(shù)周的調(diào)試中大量編寫(xiě)代碼的問(wèn)題。

調(diào)試經(jīng)驗(yàn)教訓(xùn)

在各種調(diào)試過(guò)程中,甚至在錯(cuò)誤最終解決后,我們都反思了這個(gè)問(wèn)題,并得出了以下教訓(xùn):

不要實(shí)現(xiàn)死鎖。真的,不要!

不要將 Python 函數(shù)作為回調(diào)傳遞給 C / C ++函數(shù),除非您絕對(duì)確定在執(zhí)行回調(diào)時(shí) GIL 不會(huì)被另一個(gè)線(xiàn)程占用。即使你絕對(duì)確定 GIL 沒(méi)有被拿走,也要進(jìn)行雙重和三次檢查。你不想在這里冒險(xiǎn)。

使用您所掌握的所有工具。盡管您主要編寫(xiě) Python 代碼,但在用其他語(yǔ)言(如 C 或 C ++)編寫(xiě)的庫(kù)中仍然可以發(fā)現(xiàn)錯(cuò)誤。 GDB 在調(diào)試 C 和 C ++以及 Python 方面功能強(qiáng)大。有關(guān)詳細(xì)信息,請(qǐng)參閱GDB 支持.

Bug 復(fù)雜性與代碼修復(fù)復(fù)雜性相比

結(jié)論

調(diào)試可能會(huì)讓人望而生畏,尤其是當(dāng)您無(wú)法訪(fǎng)問(wèn)所有源代碼或一個(gè)好的 IDE 時(shí)。盡管 GDB 看起來(lái)很可怕,但它也同樣強(qiáng)大。然而,隨著時(shí)間的推移,有了正確的工具、經(jīng)驗(yàn)和知識(shí),看似不可能理解的問(wèn)題可以被不同程度的細(xì)節(jié)看待,并得到真正的理解

這篇文章一步一步地概述了一個(gè) bug 是如何花了一個(gè)多方面的開(kāi)發(fā)團(tuán)隊(duì)幾十個(gè)工程小時(shí)來(lái)解決的。有了這個(gè)概述和對(duì) GDB 、多線(xiàn)程和死鎖的一些理解,您可以使用新獲得的技能來(lái)幫助解決中等復(fù)雜的問(wèn)題。

最后,永遠(yuǎn)不要局限于你已經(jīng)知道的工具。如果你知道 PDB ,接下來(lái)試試 GDB 。如果您對(duì)操作系統(tǒng)調(diào)用堆棧有足夠的了解,請(qǐng)嘗試探索寄存器和其他 CPU 屬性。這些技能當(dāng)然可以幫助所有領(lǐng)域和編程語(yǔ)言的開(kāi)發(fā)人員更加意識(shí)到潛在的陷阱,并提供獨(dú)特的機(jī)會(huì)來(lái)防止愚蠢的錯(cuò)誤成為噩夢(mèng)般的怪物。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • NVIDIA
    +關(guān)注

    關(guān)注

    14

    文章

    4940

    瀏覽量

    102815
  • 人工智能
    +關(guān)注

    關(guān)注

    1791

    文章

    46859

    瀏覽量

    237567
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4782

    瀏覽量

    84453
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    使用GDB調(diào)試Linux應(yīng)用程序

    本篇講解使用GDB調(diào)試Linux應(yīng)用程序,以下以 `hellowld.c` 為例介紹 GDB調(diào)試入門(mén)。
    發(fā)表于 06-27 15:48 ?449次閱讀

    GDB調(diào)試命令手冊(cè)

    GDB調(diào)試命令手冊(cè),感興趣的可以下載看看。
    發(fā)表于 11-05 16:36 ?9次下載

    Python調(diào)試器,快速定位各種疑難雜癥

    pdb 是 Python 自帶的庫(kù),為 Python 提供了一種交互式的源碼調(diào)試功能,包含當(dāng)前調(diào)試器應(yīng)有的功能,包括設(shè)置斷點(diǎn)、單步調(diào)試、查看
    的頭像 發(fā)表于 02-26 09:40 ?3097次閱讀

    如何在Linux中使用gdb調(diào)試C程序

    如果你是 C/C++ 程序員,或者使用 Fortran 和 Modula-2 編程語(yǔ)言開(kāi)發(fā)軟件,那么你將會(huì)很樂(lè)意知道有這么一款優(yōu)秀的調(diào)試器 - GDB - 可以幫你更輕松地
    發(fā)表于 05-16 14:45 ?780次閱讀
    如何在Linux中使用<b class='flag-5'>gdb</b><b class='flag-5'>調(diào)試</b><b class='flag-5'>C</b>程序

    Linux應(yīng)用的GDB調(diào)試的原理及過(guò)程分析

    GDB調(diào)試是應(yīng)用程序在開(kāi)發(fā)板上運(yùn)行,然后在PC機(jī)上對(duì)開(kāi)發(fā)板上得應(yīng)用程序進(jìn)行調(diào)試,PC機(jī)運(yùn)行GDB,開(kāi)發(fā)板上運(yùn)行GDBServer。在應(yīng)用程序調(diào)試
    發(fā)表于 03-05 09:44 ?3387次閱讀
    Linux應(yīng)用的<b class='flag-5'>GDB</b><b class='flag-5'>調(diào)試</b>的原理及過(guò)程分析

    linux編程GDB調(diào)試功能

    GDB是一套字符界面的程序集,可以用它在linux上調(diào)試CC++程序,它提供了以下的功能:
    的頭像 發(fā)表于 05-05 23:49 ?2235次閱讀
    linux編程<b class='flag-5'>GDB</b><b class='flag-5'>調(diào)試</b>功能

    實(shí)例演示GDB的使用

    可以直接使用gcc、gdb來(lái)做編譯調(diào)試我們的C程序,如MinGW( 一個(gè)可自由使用和自由發(fā)布的Windows特定頭文件和使用GNU工具集導(dǎo)入庫(kù)的集合 )中就同時(shí)包含有g(shù)cc與gdb工具
    的頭像 發(fā)表于 10-19 09:58 ?2653次閱讀
    實(shí)例<b class='flag-5'>演示</b><b class='flag-5'>GDB</b>的使用

    嵌入式Linux GDB調(diào)試環(huán)境搭建與使用

    這里寫(xiě)目錄標(biāo)題簡(jiǎn)介在Ubuntu下簡(jiǎn)單體驗(yàn)GDB嵌入式GDB移植GDB 常用命令參考網(wǎng)絡(luò)通信設(shè)置注意事項(xiàng)簡(jiǎn)介L(zhǎng)inux系統(tǒng)常用GDB調(diào)試
    發(fā)表于 11-01 17:59 ?8次下載
    嵌入式Linux <b class='flag-5'>GDB</b><b class='flag-5'>調(diào)試</b>環(huán)境搭建與使用

    ARM平臺(tái)如何玩轉(zhuǎn)GDB遠(yuǎn)程調(diào)試?

    強(qiáng)大的程序調(diào)試工具。GDB支持多種語(yǔ)言,包括Ada、匯編、C/C++、D、Fortran、GO、Objective-C、OpenCL、Mod
    的頭像 發(fā)表于 09-28 18:17 ?952次閱讀
    ARM平臺(tái)如何玩轉(zhuǎn)<b class='flag-5'>GDB</b>遠(yuǎn)程<b class='flag-5'>調(diào)試</b>?

    在ubuntu中調(diào)試GDB

    的 gcc 編譯器即可,注意需要加 -g 選項(xiàng),才能使用 gdb 調(diào)試 arm-linux-gnueabihf-gcc gdbtest .c -o gdbtest -g //編譯測(cè)試程序,注意-g 選項(xiàng)
    的頭像 發(fā)表于 07-27 16:31 ?1026次閱讀
    在ubuntu中<b class='flag-5'>調(diào)試</b><b class='flag-5'>GDB</b>

    gdb動(dòng)態(tài)調(diào)試演示

    今天要分享給大家的是gdb動(dòng)態(tài)調(diào)試( 動(dòng)態(tài)調(diào)試就是在不終止正在運(yùn)行的進(jìn)程的情況下,來(lái)對(duì)這個(gè)正在運(yùn)行的進(jìn)程進(jìn)行調(diào)試,我一般習(xí)慣稱(chēng)之為在線(xiàn)調(diào)試
    的頭像 發(fā)表于 11-01 15:28 ?724次閱讀
    <b class='flag-5'>gdb</b>動(dòng)態(tài)<b class='flag-5'>調(diào)試演示</b>

    GDB調(diào)試工具的原理

    一、GDB調(diào)試工具的原理 1.1 未執(zhí)行進(jìn)程調(diào)試 啟用gdb調(diào)試運(yùn)行gdb ./test的時(shí)候,
    的頭像 發(fā)表于 11-09 17:04 ?889次閱讀
    <b class='flag-5'>GDB</b><b class='flag-5'>調(diào)試</b>工具的原理

    如何使用GDB調(diào)試工具

    2.1 啟動(dòng)調(diào)試 在程序開(kāi)始調(diào)試之前,要確保程序在gcc、g++編譯時(shí),有如下-g的添加調(diào)試信息的選項(xiàng) gcc - g test .c -o test 1、啟動(dòng)未運(yùn)行的程序 啟動(dòng)未運(yùn)行
    的頭像 發(fā)表于 11-09 17:17 ?787次閱讀
    如何使用<b class='flag-5'>GDB</b><b class='flag-5'>調(diào)試</b>工具

    linuxgdb調(diào)試遇到函數(shù)調(diào)用怎么辦?

    linuxgdb調(diào)試遇到函數(shù)調(diào)用怎么辦? 在Linux上使用GDB調(diào)試時(shí),遇到函數(shù)調(diào)用是一個(gè)常見(jiàn)的情況。函數(shù)調(diào)用可能涉及到多個(gè)函數(shù)、多個(gè)文
    的頭像 發(fā)表于 01-31 10:33 ?688次閱讀

    如何使用linux下gdb來(lái)調(diào)試python程序

    如何使用linux下gdb來(lái)調(diào)試python程序? 在Linux下,可以使用GDB(GNU調(diào)試器)來(lái)調(diào)試
    的頭像 發(fā)表于 01-31 10:41 ?2422次閱讀