0%

这被称为 Scatter-Gather I/O。

其中,struct iovec 的定义如下:

struct iovec {
  void  *iov_base; /* Start address of the buffer */
  size_t iov_len;  /* Number of bytes to transfer to/from buffer */
};

这样 readvwritev 就能一次完成多个缓冲区的读取/写入。最重要的特征是:和多次调用 read/write 相比, readv/writev 操作具有原子性!

对书的介绍

Linux/UNIX 系统编程手册原名 The Linux Programming Interface,副标题是 A Linux and UNIX System Programming Handbook,这也是其翻译名的由来。

这本书经常被简称为 TLPI。

笔记的编号方式

有些笔记的编号是在章内简单排序(在书上没有对应或者不能直接对应),有些笔记的编号是和书的章节对应的。这点确实做的不好,看书的时间太长了,写笔记的方式也发生了变化。

我的测试环境

有一部分是在本机的 wsl 测试的,有一部分是在服务器上测试的(会明确说明)。

2024 年 7 月 20 日:开始考虑使用虚拟机安装系统测试,对应内容是 41 共享库基础 的中间部分(及以后)。

#include <errno.h>
#include <fcntl.h>
#include <linux/kcmp.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>

int main() {
    pid_t pid = getpid(); /* always successful */
    int ret = syscall(SYS_kcmp, pid, pid, KCMP_FILE, 0, 1);
    int ec = errno;
    if (ec) {
        perror("kcmp");
        return 1;
    }
    printf("kcmp returns %d\n", ret);
}

其中,kcmp 系统调用可以用来比较两个进程的资源大小(0 等于,1 小于,2 等于,3 不相等但无法确定大小)。输出:

kcmp returns 0

这说明此文件在终端中运行时,标准输入流和输出流使用的是相同的打开文件项(事实上就连标准错误流都是用的同一个文件项)。一个程序的标准输入输出使用不会产生冲突吗?查看一下当前 shell 的信息:

(py310) xxx ~ $ ls -lh /proc/self/fd/0
lrwx------ 1 xxx xxx 64 May 16 16:49 /proc/self/fd/0 -> /dev/pts/0
(py310) xxx ~ $ ls -lh /proc/$$/fd/0
lrwx------ 1 xxx xxx 64 May 16 16:43 /proc/387/fd/0 -> /dev/pts/0
(py310) xxx ~ $ cat /proc/$$/fdinfo/0
pos:    0
flags:  0100002
mnt_id: 115
ino:    3
(py310) xxx ~ $ cat /proc/$$/fdinfo/1
pos:    0
flags:  0100002
mnt_id: 115
ino:    3
(py310) xxx ~ $ file /proc/$$/fd/0
/proc/387/fd/0: symbolic link to /dev/pts/0
(py310) xxx ~ $ file $(readlink -f /proc/$$/fd/0)
/dev/pts/0: character special (136/0)

书上的例子是:

# 在 bash 中
./myscript > results.log 2>&1

这样能够将标准输出流和错误流都重定向到日志文件中去。我们希望标准输出流和标准错误流能共享文件偏移量等信息,否则两者写入同一个文件中时就会相互覆盖。

注意,这里将输出流和错误流都重定向到了一个普通文件,因而 1 和 2 号文件描述符并不是像终端那样对应着字符设备,此时文件偏移量是非常重要的信息。

#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd;
    assert((fd = open("A.txt", O_WRONLY | O_TRUNC | O_CREAT,
                      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) != -1);
    assert(pwrite(fd, "pwrite", 6, 0) == 6);
    assert(write(fd, "WRITE", 5) == 5);
}

最后 A.txt 的文件内容为:

WRITEe

这说明第一次用 pwrite 读写完成之后,文件偏移量并没有被更新,还是在 0 的位置。

UNIX 中的 printf

Single UNIX Specification 中允许 printf 使用 %m$*m$ 的形式来绑定第 m 个参数,其中 m 从 1 开始计数。如果使用了这种表达方式,则所有参数都要指定序号,而且不能遗漏参数。这样做的好处是:可以多次使用同一个参数。

请注意,参数是 %m$*m$ 这两个结构索取的。这意味着以下两行的含义相同:

printf("%*d", width, num);
printf("%2$*1$d", width, num);

首先 %2$ 索要了参数 num 用来表示输出的数字,然后 *m$ 索要了参数 width 来表示输出的宽度。这和我们看到的 %*d 的参数顺序有差异:%*d 中就好像输出数字这个参数是 d 索取的一样。相关链接:StackOverflow 的提问

C99 中没有规定这种指定序号绑定参数的方式。

修改配置文件

网上一般推荐的做法是这种: https://linuxiac.com/how-to-change-docker-data-directory/

修改 /etc/docker/daemon.json,向其中加入:

{ "data-root": "/new/docker/root" }

或者在 systemd 的服务启动程序中指定数据的路径。

不修改配置文件,只转移数据

但是我们实验室的服务器没有采用以上方案,却通过其他方式完成了从标准位置 /var/lib/docker 向其他位置的数据转移,而且两个文件夹中的数据是一样的。可以想到的方法有三种:软链接、硬链接、挂载。一个一个分析。

事情起因

在第一次使用 nsys 的时候发现 cudaLaunchKernel 非常耗时。网友觉得 cudaLaunchKernel 超过了 50ms 就觉得很大了,但我在服务器上测是 400~500ms!

我想到的可能原因:

  • 服务器的 CPU 和 GPU 负载太高。用环境变量 CUDA_VISIBLE_DEVICES 换到同一台服务器上相对比较空闲的卡,发现 kernel 启动时间从平均 440 ms 减少到了 372 ms,因此可以排除 GPU 负载高的影响。
  • 启动 kernel 时申请的资源太多。我们自己写的 kernel 每个 block 都是固定 512 线程数,GPU 为 3090,每个 SM 上最大能容纳 1024 个线程;对 cudnn 的调用(比如卷积核)看了一下是用满了 1024 线程数。
  • GPU 在 warmup 阶段。

真正原因

GPU 在 warmup 阶段

安装

nsys 来自包 cuda-nsight-systems-11-7(和自己的 CUDA 版本对应一下)。

在网上一直都没有搜到安装方式,官网也说的不明不白。尝试过两个错误的包:

  • nsight-systems:这个是 Qt 写的图形界面程序,提供的二进制可执行程序是 nsight-sys
  • nvidia-nsight:这个是 Java 写的图形界面程序,提供的二进制可执行程序是 nsight

使用

nsys profile --stat=true xxx 运行完程序后得到 report1.nsys-rep,report 后面的编号是自动增长的,而 --stat=true 表示用文本形式在命令行输出信息。如果有条件可以把 report1.nsys-rep 下载下来然后用本地的图形化程序加载分析。

但是这样导出的程序看不到 CUDA 内存占用。为了看到 CUDA 内存占用情况,考虑:

起因:每日一题官解看不明白

今天(2024 年 5 月 4 日)做 Leetcode 每日一题又没有做出来,最后抄了答案。题目是这样的:1235. 规划兼职工作

思路是先按照 endTime 排序,然后再 dp,然后 dp 中用二分查找求满足“自己的 endTime 小于等于当前元素 startTime ” 的元素数量。但是官方解答的 std::upper_bound 传参实在是看迷糊了。

二分查找:AoS 和 SoA 的比较

一般数组(Array of Structures 或 AoS)排序好之后,直接用 std::lower_bound / std::upper_bound 做就好了。但如果数组里面存的不是可以直接用 operator< 或者自定义 comp 比较的值,写 comp 就比较伤脑筋。比如这道题里面为了减少数据拷贝(或者内存使用量),只对下标排序:

注:comp 指的是自定义的比较器。

int jobScheduling(vector<int> &startTime, vector<int> &endTime, vector<int> &profit) {
    vector<int> idx(n);
    iota(idx.begin(), idx.end(), 0);
    sort(idx.begin(), idx.end(), [&](int i, int j) -> bool { return endTime[i] < endTime[j]; });
    // ...
}