一次WSL上使用clangd编写cuda的踩坑

起因

最近在WSL上学习CUDA,不假思索地选择了clangd作为lsp,使用的软件如下:

  • Arch WSL2
  • CMake 3.28.1
  • Cuda compilation tools, release 12.3, V12.3.103
  • LLVM 16.0.6
  • VSCode 1.85.1

但是使用的过程中,遇到了一个非常神秘的问题,对于C++的源代码*.h*.cpp等,clangd的代码补全一切正常,但是一遇到.cu文件,completion的延迟会长达几秒钟:

可以看到连copilot都已经给我写好了,但是clangd还在犹豫,这是为什么呢?我看了一眼compile_commands.json,发现并没有异样。遂在互联网上搜索了一番,最终在某群中,一个群友给出建议:

先在sudo模式下把 /etc/wsl.conf 文件权限+w,然后在该文件下新建节 [interop] enabled = false appendWindowsPath = false 最后重启wsl

看来果然是WSL的锅,经过一番控制变量,发现问题出在了$PATH的环境变量上,WSL的$PATH环境变量会自动添加Windows的环境变量,众所周知垮WSL的Host IO非常拉胯,在一堆/mnt路径中搜来搜去肯定是很慢的。

But How?

但是很明显,这是不应该出现的情况,而且我也不想把interop直接禁用掉。

再次检查compile_commands.json,一切正常:

1
2
3
4
5
6
{
"directory": "/home/carbene/Projects/HiCuda/build",
"command": "/opt/cuda/bin/nvcc -forward-unknown-to-host-compiler -DCMAKE_INTDIR=\\\"RelWithDebInfo\\\" -I/home/carbene/Projects/HiCuda/common/includes -I/home/carbene/Projects/HiCuda/build/_deps/fmt-src/include -O2 -g -DNDEBUG -std=c++20 \"--generate-code=arch=compute_75,code=[compute_75]\" -x cu -rdc=true -c /home/carbene/Projects/HiCuda/course3/memory_accessing.cu -o course3/CMakeFiles/course3_memory.dir/RelWithDebInfo/memory_accessing.cu.o",
"file": "/home/carbene/Projects/HiCuda/course3/memory_accessing.cu",
"output": "course3/CMakeFiles/course3_memory.dir/RelWithDebInfo/memory_accessing.cu.o"
}

根本没有任何/mnt路径出现,不见棺材不掉泪,我把LLVM的源码拉了下来,对着$PATH一顿搜索,毫无头绪,用到了$PATH的地方根本就没有被触发。幸运的是,发现vscode-clangd有一个trace的配置,可以保存trace到一个可以被Chrome trace viewer打开的文件中,very well,对比有无清理$PATH的trace结果:

image.png

瓶颈在CreatePreamblePatch

image.png

一开始以为是clangd的include search问题,但是经过二分排查法,瓶颈调用链竟然出现在clangd对于nvcc的调用上,但是我的compile_commands.json中的nvcc就是在WSL中的。由于后续debug涉及到了LLVM核心模块,因此不再深究。但是综合上述的情况,答案很明显了:

瓶颈并非clangd,而是nvccnvcc会在被调用时对于$PATH进行搜索,而$PATH中包含了/mnt路径,导致了严重的IO延迟(这也是为什么只有.cu文件会有问题)。

So **** u, NVIDIA.

解决方案

真麻烦,我们肯定不能指望哪一天NVIDIA大发慈悲来修修这个问题,而我目前也还没有找到能限制nvcc搜索路径的方法,因此只能从vscode-clangd插件入手。解决办法也很直接:在其启动clangd language server时清理一下$PATH即可:

vscode-clangdsrc/clangd-context.ts中:

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
diff --git a/src/clangd-context.ts b/src/clangd-context.ts
index 0fef3f5..9e1ceb2 100644
--- a/src/clangd-context.ts
+++ b/src/clangd-context.ts
@@ -71,10 +71,20 @@ export class ClangdContext implements vscode.Disposable {
args: await config.get<string[]>('arguments'),
options: {cwd: vscode.workspace.rootPath || process.cwd()}
};
+
+ let processEnv = process.env;
+ let envPath = processEnv.PATH;
+ if (envPath) {
+ let pathArray = envPath.split(':');
+ pathArray = pathArray.filter(path => !path.startsWith('/mnt'));
+ processEnv.PATH = pathArray.join(':');
+ }
const traceFile = config.get<string>('trace');
if (!!traceFile) {
- const trace = {CLANGD_TRACE: traceFile};
- clangd.options = {env: {...process.env, ...trace}};
+ const trace = { CLANGD_TRACE: traceFile };
+ clangd.options = { env: { ...processEnv, ...trace } };
+ } else {
+ clangd.options = { env: processEnv };
}
const serverOptions: vscodelc.ServerOptions = clangd;

总而言之,虽然最终没能完美解决这个问题,但是能发现问题的根源然后用一个比较tricky的方法解决掉,也是一件值得庆幸的事情。

以及,again,**** u, NVIDIA.

作者

Carbene Hu

发布于

2023-12-17

更新于

2024-02-14

许可协议

评论