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

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

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

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

io_uring內(nèi)核各個(gè)組件的性能

科技綠洲 ? 來(lái)源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-10 11:46 ? 次閱讀

先看看性能

io_uring 需要內(nèi)核版本在5.1 及以上才支持,liburing的編譯安裝 很簡(jiǎn)單,直接clone 官方的代碼,sudo make && sudo make install 就好了,本文是在內(nèi)核5.12版本 上測(cè)試的。

在描述io_uring 的性能之前,我們先直接看一組實(shí)測(cè)數(shù)據(jù):

這組數(shù)據(jù)是在3D XPoint 介質(zhì)的硬盤 :optane-5800上測(cè)試的,optane5800 能夠提供(randread100% 150w/s , randwrite 100% 150w/s)的性能。

圖片

進(jìn)行測(cè)試的fio 腳本如下:

# aio
[global]
ioengine=libaio
direct=0
randrepeat=1
threads=8
runtime=15
time_based
size=1G
directory=../test-data
group_reporting
[read256B-rand]
bs=4096
rw=randread
numjobs=1
iodepth=128

# io_uring
[global]
ioengine=io_uring
sqthread_poll=1 #開啟io_uring sq_poll模式
direct=1
randrepeat=1
threads=8
runtime=15
time_based
size=1G
directory=../test-data
group_reporting
[read256B-rand]
bs=4096
rw=randread
numjobs=1
iodepth=128

通過上面的測(cè)試,我們能夠得到如下幾個(gè)結(jié)論:

  1. 這種高隊(duì)列深度的測(cè)試下,可以看到io_uring 在開啟sq_poll之后的性能 相比于aio 的高隊(duì)列深度的處理能力好接近一倍;
  2. 在較低隊(duì)列深度 以及不開啟 sq_poll 模式的情況下,io_uring 整體沒有太大的優(yōu)勢(shì),或者說(shuō)一樣的性能。
  3. 在buffer I/O (direct=0) 下,io_uring 也不會(huì)有太大的優(yōu)勢(shì),因?yàn)槎嫉猛ㄟ^ os-cache 來(lái)操作。

需要注意的是,如果aio和io_uring 在高并發(fā)下(jobs 的數(shù)目不斷增加),都是可以達(dá)到當(dāng)前磁盤的性能瓶頸的。

AIO 的基本實(shí)現(xiàn)

那有這樣的測(cè)試現(xiàn)象,我們可能會(huì)有一些疑問,就這性能?我們?cè)趎vme上做軟件,希望發(fā)揮的是整個(gè)磁盤的性能,而不是比拼誰(shuí)的隊(duì)列深度大,誰(shuí)的優(yōu)勢(shì)更大。。。我用aio 做batch 也能達(dá)到磁盤的性能瓶頸,為什么要選擇 對(duì)于數(shù)據(jù)庫(kù)/存儲(chǔ) 領(lǐng)域來(lái)說(shuō) 好像“如日中天”的io_uring呢。

我們先來(lái)看看aio 的大體實(shí)現(xiàn),沒有涉及到源代碼。aio 主要提供了三個(gè)系統(tǒng)調(diào)用:

  • io_setup 初始化一些內(nèi)核態(tài)的數(shù)據(jù)結(jié)構(gòu)
  • io_submit 用于用戶態(tài)提交io 請(qǐng)求
  • io_getevents 用于io 請(qǐng)求處理完成之后的io 收割

圖片

大體的IO調(diào)度過程如下:

  1. io_setup 完成一些內(nèi)核數(shù)據(jù)結(jié)構(gòu)的初始化(包括內(nèi)核中的 aio 調(diào)度隊(duì)列,aio_ring_info 的ring-buffer緩沖區(qū))
  2. 用戶態(tài)構(gòu)造一批io請(qǐng)求,通過io_submit 拷貝請(qǐng)求到內(nèi)核態(tài)io 隊(duì)列(文件系統(tǒng)之上,上圖沒有體現(xiàn)出來(lái))之后返回到用戶態(tài)。
  3. 內(nèi)核態(tài)繼續(xù)通過內(nèi)核i/o 棧處理io請(qǐng)求,處理完成之后 通過 aio_complete 函數(shù)將處理完成的請(qǐng)求放入到 aio_ring_info,每一個(gè)io請(qǐng)求是一個(gè)io_event。
  4. 用戶態(tài)通過 io_getevents 系統(tǒng)調(diào)用 從 aio_ring_info(ring-buffer) 的head 拿處理完成的io_event,如果head==tail,則表示這個(gè)ring-buffer是空的。拿到之后,將拿到的io_event 一批從內(nèi)核態(tài)拷貝到用戶態(tài)。

如果單純看 誰(shuí)能將磁盤性能完整發(fā)揮出來(lái),那毋庸置疑,大家都可以;那為什么做存儲(chǔ)的對(duì)io_uring 的出現(xiàn)如此熱衷呢?我們就得結(jié)合實(shí)際的應(yīng)用場(chǎng)景來(lái)看看兩者之間的差異了:

  1. 使用AIO的話,請(qǐng)求調(diào)度都需要直接由通用塊層來(lái)調(diào)度處理,所以需要O_DIRECT標(biāo)記。這就意味著,使用AIO的應(yīng)用都無(wú)法享受os cache,這對(duì)與存儲(chǔ)應(yīng)用來(lái)說(shuō)并不友好,cache都得自己來(lái)維護(hù),而且顯然沒有os page-cache性能以及穩(wěn)定性有優(yōu)勢(shì)。而使用io_uring 則沒有這樣的限制,當(dāng)然,io_uring在 buffer I/O下顯然沒有太大的優(yōu)勢(shì)。
  2. 延時(shí)上的開銷。AIO 提交用戶請(qǐng)求的時(shí)候 通過io_submit調(diào)用,收割用戶請(qǐng)求的時(shí)候通過io_getevents,正常應(yīng)用的時(shí)候每一個(gè)請(qǐng)求都意味著至少兩次系統(tǒng)調(diào)用(I/O提交和I/O收割),而對(duì)于io_uring來(lái)說(shuō),I/O 提交和I/O收割都可以 offload 給內(nèi)核。這樣相比于AIO 來(lái)說(shuō),io_uring能夠極大得減少 系統(tǒng)調(diào)用引入的上下文切換。
  3. io_uring 能夠支持針對(duì)submit queue的polling,啟動(dòng)一個(gè)內(nèi)核線程進(jìn)行polling,加速請(qǐng)求的提交和收割;對(duì)于aio來(lái)說(shuō),這里就沒有這樣的機(jī)制。

總的來(lái)說(shuō),io_uring 能夠保證上層應(yīng)用 對(duì)系統(tǒng)資源(cache)正常使用的同時(shí) ,降低應(yīng)用 下發(fā)的請(qǐng)求延時(shí)和CPU的開銷,在單實(shí)例高隊(duì)深下,能夠顯著優(yōu)于同等隊(duì)深下的AIO性能。

io_ring 使用

io_uring 基本接口

io_uring的用戶態(tài)API 提供了三個(gè)系統(tǒng)調(diào)用,io_uring_setup,io_uring_enter,io_uring_register。

  • int io_uring_setup(u32 entries, struct io_uring_params *p); 這個(gè)接口 用于創(chuàng)建 擁有 entries 個(gè)請(qǐng)求的 提交隊(duì)列(SQ) 和 完成隊(duì)列(CQ),并且返回給用戶一個(gè)fd。這個(gè)fd可以用做在同一個(gè)uring實(shí)例上 用戶空間和內(nèi)核空間共享sq和cq 隊(duì)列,這樣能夠避免在請(qǐng)求完成時(shí)不需要從完成隊(duì)列拷貝數(shù)據(jù)到用戶態(tài)了。io_uring_params 主要是根據(jù)用戶的配置來(lái)設(shè)置uring 實(shí)例的創(chuàng)建行為。包括 單不限于開啟 IORING_SETUP_IOPOLL 和 IORING_SETUP_SQPOLL 兩種 poll 模式。后面會(huì)細(xì)說(shuō)。
  • int io_uring_register(unsigned int fd, unsigned int opcode, void *arg, unsigned int nr_args);這個(gè)接口主要用于注冊(cè)用戶態(tài)和內(nèi)核態(tài)共享的緩沖區(qū),即將 setup 返回的fd中的數(shù)據(jù)結(jié)構(gòu) 映射到共享內(nèi)存,從而進(jìn)一步減少用戶I/O 提交到uring 隊(duì)列中的開銷。
  • int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig);這個(gè)接口既能夠提交 新的I/O請(qǐng)求 ,又能夠支持I/O收割。

liburing 的使用

可以從上面的幾個(gè)系統(tǒng)調(diào)用能夠簡(jiǎn)單看到 用戶在自主使用這三個(gè)系統(tǒng)調(diào)用來(lái)調(diào)度 I/O請(qǐng)求時(shí) 還是比較麻煩的,像io_uring_setup 之后的fd,我們用戶層想要使用創(chuàng)建好的sq/cq ,則需要自主進(jìn)行mmap,并且維護(hù)用戶態(tài)的sq/cq 數(shù)據(jù)結(jié)構(gòu),并在后續(xù)的 enter 中自主進(jìn)行用戶態(tài)的sq 的填充。這個(gè)過程相對(duì)來(lái)說(shuō)還是比較麻煩的。更不要說(shuō)用三個(gè)系統(tǒng)調(diào)用中數(shù)十個(gè)的flags的靈活配置,如果全部結(jié)合起來(lái),對(duì)于剛接觸io_uring的用戶來(lái)說(shuō)還是需要較大的學(xué)習(xí)成本。

比如,我想啟動(dòng)io_uring,并初始化好用戶態(tài)的sq/cq 數(shù)據(jù)結(jié)構(gòu),就需要寫下面這一些代碼:

int app_setup_uring(struct submitter *s) {
struct app_io_sq_ring *sring = &s->sq_ring;
struct app_io_cq_ring *cring = &s->cq_ring;
struct io_uring_params p;
void *sq_ptr, *cq_ptr;

/*
* We need to pass in the io_uring_params structure to the io_uring_setup()
* call zeroed out. We could set any flags if we need to, but for this
* example, we don't.
* */
memset(&p, 0, sizeof(p));
s->ring_fd = io_uring_setup(QUEUE_DEPTH, &p);
if (s->ring_fd < 0) {
perror("io_uring_setup");
return 1;
}

/*
* io_uring communication happens via 2 shared kernel-user space ring buffers,
* which can be jointly mapped with a single mmap() call in recent kernels.
* While the completion queue is directly manipulated, the submission queue
* has an indirection array in between. We map that in as well.
* */

int sring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned);
int cring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);

/* In kernel version 5.4 and above, it is possible to map the submission and
* completion buffers with a single mmap() call. Rather than check for kernel
* versions, the recommended way is to just check the features field of the
* io_uring_params structure, which is a bit mask. If the
* IORING_FEAT_SINGLE_MMAP is set, then we can do away with the second mmap()
* call to map the completion ring.
* */
if (p.features & IORING_FEAT_SINGLE_MMAP) {
if (cring_sz > sring_sz) {
sring_sz = cring_sz;
}
cring_sz = sring_sz;
}

/* Map in the submission and completion queue ring buffers.
* Older kernels only map in the submission queue, though.
* */
sq_ptr = mmap(0, sring_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_SQ_RING);
if (sq_ptr == MAP_FAILED) {
perror("mmap");
return 1;
}

if (p.features & IORING_FEAT_SINGLE_MMAP) {
cq_ptr = sq_ptr;
} else {
/* Map in the completion queue ring buffer in older kernels separately */
// 放置內(nèi)存被page fault
cq_ptr = mmap(0, cring_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_CQ_RING);
if (cq_ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
}
/* Save useful fields in a global app_io_sq_ring struct for later
* easy reference */
sring->head = sq_ptr + p.sq_off.head;
sring->tail = sq_ptr + p.sq_off.tail;
sring->ring_mask = sq_ptr + p.sq_off.ring_mask;
sring->ring_entries = sq_ptr + p.sq_off.ring_entries;
sring->flags = sq_ptr + p.sq_off.flags;
sring->array = sq_ptr + p.sq_off.array;

/* Map in the submission queue entries array */
s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_SQES);
if (s->sqes == MAP_FAILED) {
perror("mmap");
return 1;
}

/* Save useful fields in a global app_io_cq_ring struct for later
* easy reference */
cring->head = cq_ptr + p.cq_off.head;
cring->tail = cq_ptr + p.cq_off.tail;
cring->ring_mask = cq_ptr + p.cq_off.ring_mask;
cring->ring_entries = cq_ptr + p.cq_off.ring_entries;
cring->cqes = cq_ptr + p.cq_off.cqes;

return 0;
}

所以Jens Axboe將三個(gè)系統(tǒng)調(diào)用做了一個(gè)封裝,形成了liburing,在這里面我想要初始化一個(gè)uring實(shí)例,并完成用戶態(tài)的數(shù)據(jù)結(jié)構(gòu)的映射,只需要調(diào)用下面io_uring_queue_init這個(gè)接口:

struct io_uring ring;
struct io_uring_params p = { };
int ret;
ret = io_uring_queue_init(IORING_MAX_ENTRIES, &ring, IORING_SETUP_IOPOLL);

關(guān)于liburing的使用,可以看下面這個(gè)100行的小案例:

大體的功能就是利用io_uring 去讀一個(gè)用戶輸入的文件,每次讀請(qǐng)求的大小是4K,讀完整個(gè)文件結(jié)束。

#include
#include
#include
#include
#include
#include
#include
#include "liburing.h"

#define QD 4

int main(int argc, char *argv[])
{
struct io_uring ring;
int i, fd, ret, pending, done;
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct iovec *iovecs;
struct stat sb;
ssize_t fsize;
off_t offset;
void *buf;

if (argc < 2) {
printf("%s: filen", argv[0]);
return 1;
}
// 初始化io_uring,并拿到初始化的結(jié)果,0是成功的,小于0 是失敗的
ret = io_uring_queue_init(QD, &ring, 0);
if (ret < 0) {
fprintf(stderr, "queue_init: %sn", strerror(-ret));
return 1;
}

// 打開用戶輸入的文件
fd = open(argv[1], O_RDONLY | O_DIRECT);
if (fd < 0) {
perror("open");
return 1;
}

// 將文件屬性放在sb中,主要是獲取文件的大小
if (fstat(fd, &sb) < 0) {
perror("fstat");
return 1;
}

// 拆分成 設(shè)置的 io_uring支持的最大隊(duì)列深度 個(gè)請(qǐng)求,4個(gè)
fsize = 0;
iovecs = calloc(QD, sizeof(struct iovec));
for (i = 0; i < QD; i++) {
if (posix_memalign(&buf, 4096, 4096))
return 1;
iovecs[i].iov_base = buf;
iovecs[i].iov_len = 4096;
fsize += 4096;
}

// 構(gòu)造請(qǐng)求,并存放在 seq中
offset = 0;
i = 0;
do {
sqe = io_uring_get_sqe(&ring);
if (!sqe)
break;
io_uring_prep_readv(sqe, fd, &iovecs[i], 1, offset);
offset += iovecs[i].iov_len;
i++;
if (offset > sb.st_size)
break;
} while (1);

// 提交請(qǐng)求sqe 中的請(qǐng)求到內(nèi)核
ret = io_uring_submit(&ring);
if (ret < 0) {
fprintf(stderr, "io_uring_submit: %sn", strerror(-ret));
return 1;
} else if (ret != i) {
fprintf(stderr, "io_uring_submit submitted less %dn", ret);
return 1;
}

done = 0;
pending = ret;
fsize = 0;
// 等待內(nèi)核處理完所有的請(qǐng)求,并由用戶態(tài)拿到cqe,表示請(qǐng)求處理完成
for (i = 0; i < pending; i++) {
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret < 0) {
fprintf(stderr, "io_uring_wait_cqe: %sn", strerror(-ret));
return 1;
}

done++;
ret = 0;
if (cqe->res != 4096 && cqe->res + fsize != sb.st_size) {
fprintf(stderr, "ret=%d, wanted 4096n", cqe->res);
ret = 1;
}
fsize += cqe->res;
io_uring_cqe_seen(&ring, cqe);
if (ret)
break;
}

// 最后輸出 提交的請(qǐng)求的個(gè)數(shù)(4k),完成請(qǐng)求的個(gè)數(shù),總共處理的請(qǐng)求大小
printf("Submitted=%d, completed=%d, bytes=%lun", pending, done,
(unsigned long) fsize);
close(fd);
io_uring_queue_exit(&ring);
return 0;
}

編譯: gcc -O2 -D_GNU_SOURCE -o io_uring-test io_uring-test.c -luring

運(yùn)行: ./io_uring-test test-file.txt

io_uring 非poll 模式下 的實(shí)現(xiàn)

接下來(lái)記錄一下io_uring的實(shí)現(xiàn),來(lái)填之前說(shuō)到的一些小坑,當(dāng)然…這里描述的內(nèi)容也是站在前人的肩膀 以及 自己經(jīng)過一些測(cè)試驗(yàn)證總體來(lái)看的。

io_uring 能夠支持其他多種I/O相關(guān)的請(qǐng)求:

  • 文件I/O:read, write, remove, update, link,unlink, fadivse, allocate, rename, fsync等
  • 網(wǎng)絡(luò)I/O:send, recv, socket, connet, accept等
  • 進(jìn)程間通信:pipe

還是以 上面案例中 io_uring 處理read 請(qǐng)求為例, 通過io_uring_prep_readv來(lái)填充之前已經(jīng)創(chuàng)建好的sqe。

static inline void io_uring_prep_readv(struct io_uring_sqe *sqe, int fd,
const struct iovec *iovecs,
unsigned nr_vecs, __u64 offset)
{
// 調(diào)度讀請(qǐng)求,將構(gòu)造好的iovecs 中的內(nèi)容填充到sqe中。
io_uring_prep_rw(IORING_OP_READV, sqe, fd, iovecs, nr_vecs, offset);
}

static inline void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,
const void *addr, unsigned len,
__u64 offset)
{
sqe->opcode = (__u8) op;
...
sqe->fd = fd;
sqe->off = offset;
sqe->addr = (unsigned long) addr;
sqe->len = len;
...
sqe->__pad2[0] = sqe->__pad2[1] = 0;
}

那我們需要先回到最開始的io_uring_setup以及 后續(xù)的mmap setup返回的結(jié)果 之后 用戶態(tài)和內(nèi)核態(tài)共享的數(shù)據(jù)結(jié)構(gòu)內(nèi)容。

圖片

數(shù)據(jù)結(jié)構(gòu) 在內(nèi)存中的分布 如上圖:

1.io_uring_setup 之后,會(huì)將內(nèi)核中創(chuàng)建好的一塊內(nèi)存區(qū)域 用 fd標(biāo)識(shí) 以及各個(gè)數(shù)據(jù)結(jié)構(gòu)在這個(gè)內(nèi)存區(qū)域中的偏移量存放在io_uring_params中, 通過mmap 來(lái)將這部分內(nèi)存區(qū)域的數(shù)據(jù)結(jié)構(gòu)映射到用用戶空間。

其中io_uring_params 中的 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)如下:

struct io_uring_params {
__u32 sq_entries; // sq 隊(duì)列的個(gè)數(shù)
__u32 cq_entries; // cq 隊(duì)列的個(gè)數(shù)
__u32 flags; // setup設(shè)置的一些標(biāo)識(shí),比如是否開啟內(nèi)核的io_poll 或者 sq_poll等
__u32 sq_thread_cpu; // 設(shè)置sq_poll 模式下 輪詢的cpu 編號(hào)
__u32 sq_thread_idle;
__u32 features;
__u32 wq_fd;
__u32 resv[3];
struct io_sqring_offsets sq_off; // sq的偏移量
struct io_cqring_offsets cq_off; // cq的偏移量
};

2.Mmap 之后的內(nèi)存形態(tài)就是上圖中的數(shù)據(jù)結(jié)構(gòu)形態(tài),mmap的過程就是填充用戶態(tài)可訪問的sq/cq。

  • SQ ,submission queue,保存用戶空間提交的請(qǐng)求的地址,實(shí)際的用戶請(qǐng)求會(huì)存放在io_uring_sqe的sqes中。
struct io_uring_sq {
unsigned *khead;
unsigned *ktail;
...
struct io_uring_sqe *sqes; // 較為復(fù)雜的數(shù)據(jù)結(jié)構(gòu),保存請(qǐng)求的實(shí)際內(nèi)容
unsigned sqe_head;
unsigned sqe_tail;
...
};

用戶空間的sq更新會(huì)追加到SQ 的隊(duì)尾部,內(nèi)核空間消費(fèi) SQ 時(shí)則會(huì)消費(fèi)隊(duì)頭。

  • CQ, complete queue,保存內(nèi)核空間完成請(qǐng)求的地址,實(shí)際的完成請(qǐng)求的數(shù)據(jù)會(huì)存放在io_uring_cqe的cqes中。
struct io_uring_cq {
unsigned *khead;
unsigned *ktail;
...
struct io_uring_cqe *cqes;
...
};

內(nèi)核完成IO 收割之后會(huì)將請(qǐng)求填充到cqes 中,并更新cq 的隊(duì)尾,用戶空間則會(huì)從cq的隊(duì)頭消費(fèi) 處理完成的請(qǐng)求。

3.在前面的read 案例代碼中,調(diào)用的liburing 的函數(shù) io_uring_get_sqe 就是在用戶空間更新sq的隊(duì)尾部。

struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring)
{
struct io_uring_sq *sq = &ring->sq;
unsigned int head = io_uring_smp_load_acquire(sq->khead);
unsigned int next = sq->sqe_tail + 1;
struct io_uring_sqe *sqe = NULL;

// 當(dāng)前sq的 tail 和 head之間的容量滿足sq的大小,則將當(dāng)前請(qǐng)求的填充到sqe中
// 并更新sq 的隊(duì)尾,向上移動(dòng)
if (next - head <= *sq->kring_entries) {
sqe = &sq->sqes[sq->sqe_tail & *sq->kring_mask];
sq->sqe_tail = next;
}
return sqe;
}

后續(xù),內(nèi)核處理完成之后,用戶空間從cq中獲取 處理完成的請(qǐng)求時(shí)則會(huì)調(diào)用io_uring_wait_cqe_nr進(jìn)行收割。

io_uring 中的ring就是 上圖中的io 鏈路,從sq隊(duì)尾進(jìn)入,最后請(qǐng)求從cq 隊(duì)頭出來(lái),整個(gè)鏈路就是一個(gè)環(huán)形(ring)。而sq和cq在數(shù)據(jù)結(jié)構(gòu)上被存放在了 io_uring 中。加了uring 中的u 猜測(cè)是指用戶態(tài)(userspace)可訪問的,目的是好的,不過讀起來(lái)的單詞諧音就讓一些人略微尷尬(urine。。。)

非poll 模式下的內(nèi)核火焰圖調(diào)用棧如下:

圖片

io_uring poll模式下的實(shí)現(xiàn)

我們?cè)谧铋_始的性能測(cè)試過程中可以看到在開啟 poll 之后,io_uring的性能才能顯著提高。

我們從前面 io_uring 內(nèi)存分布圖 中可以看到在內(nèi)核調(diào)度兩個(gè)隊(duì)列請(qǐng)求的過程中 可以通過異步輪詢的方式進(jìn)行調(diào)度的,也就是io_uring的 poll模式。

io_uring 在io_uring_setup的時(shí)候可以通過設(shè)置flag 來(lái)開啟poll模式,io-uring 支持兩種方式poll模式。

  • IORING_SETUP_IOPOLL,這種方式是由nvme 驅(qū)動(dòng)支持的 io_poll。即用戶態(tài)通過io_uring_enter提交請(qǐng)求到內(nèi)核的文件讀寫隊(duì)列中即可,nvme驅(qū)動(dòng)會(huì)不斷得輪詢文件讀寫隊(duì)列進(jìn)行io消費(fèi),同時(shí)用戶態(tài)在設(shè)置IORING_ENTER_GETEVENTS得flag之后,還需要不斷得調(diào)用io_uring_enter 通過io_iopoll_check 調(diào)用內(nèi)核接口查看 nvme的io_poll 是否完成任務(wù)調(diào)度,從而進(jìn)行填充 cqes。

如果使用nvme驅(qū)動(dòng),則需要單獨(dú)開啟io_poll 才能真正讓 IORING_SETUP_IOPOLL 配置生效。開啟的話,直接嘗試 root 用戶操作:echo 1 > /sys/block/nvme2n1/queue/io_poll,成功則表示開啟。如果出現(xiàn)bash: echo: write error: Invalid argument ,則表示當(dāng)前nvme驅(qū)動(dòng)還不支持,需要通過驅(qū)動(dòng)層打開這個(gè)配置才行,可以嘗試執(zhí)行如下步驟:如果執(zhí)行之前,通過modinfo nvme 查看當(dāng)前設(shè)備是否有nvme驅(qū)動(dòng)失敗,則需要先編譯當(dāng)前內(nèi)核版本的nvme驅(qū)動(dòng)才行,否則下面的操作沒有nvme驅(qū)動(dòng)都是無(wú)法進(jìn)行的。

  1. umount fs , 卸載磁盤上掛載的文件系統(tǒng)
  2. echo 1 > /sys/block/nvme0n1/device/device/remove , 將設(shè)備從當(dāng)前服務(wù)器移除
  3. rmmod nvme
  4. modprobe nvme poll_queues=1, 重新加載nvme驅(qū)動(dòng),來(lái)支持io_poll的隊(duì)列深度為1
  5. echo 1 > /sys/bus/pci/rescan ,重新將磁盤加載回來(lái)
  • IORING_SETUP_SQPOLL,這種模式的poll則是我們fio測(cè)試下的 sqthread_poll開啟的配置。開啟之后io_uring會(huì)啟動(dòng)一個(gè)內(nèi)核線程,用來(lái)輪詢submit queue,從而達(dá)到不需要系統(tǒng)調(diào)用的參與就可以提交請(qǐng)求。用戶請(qǐng)求在用戶空間提交到SQ 之后,這個(gè)內(nèi)核線程處于喚醒狀態(tài)時(shí)會(huì)不斷得輪詢SQ,也就可以立即捕獲到這次請(qǐng)求。(我們前面的案例中會(huì)先在用戶空間構(gòu)造指定數(shù)量的SQ放到ring-buffer中,再由io_uring_enter一起提交到內(nèi)核),這個(gè)時(shí)候有了sq_thread 的輪詢,只要用戶空間提交到SQ,內(nèi)核就能夠捕獲到并進(jìn)行處理。如果sq_thread 長(zhǎng)時(shí)間捕獲不到請(qǐng)求,則會(huì)進(jìn)入休眠狀態(tài),需要通過調(diào)用io_uring_enter系統(tǒng)調(diào)用,并設(shè)置IORING_SQ_NEED_WAKEUP來(lái)喚醒sq_thread。

大體的調(diào)度方式如下圖:

圖片

這種sq_thread 內(nèi)核對(duì)SQ的輪詢模式能夠極大得減少請(qǐng)求在submit queue中的排隊(duì)時(shí)間,同時(shí)減少了io_uring_enter系統(tǒng)調(diào)用的開銷。

圖片

開啟sq_thread之后的輪詢模式可以看到 用戶提交請(qǐng)求 對(duì)CPU消耗僅僅只占用了一小部分的cpu。

io_uring 在 rocksdb 中的應(yīng)用

Rocksdb 針對(duì)io_uring的調(diào)用大體類似前面提到的使用liburing 接口實(shí)現(xiàn)的一個(gè)read 文件的案例,同樣是調(diào)用io_uring_prep_readv 來(lái)實(shí)現(xiàn)對(duì)文件的讀寫。

Io_uring 的特性決定了在I/O層 的批量讀才能體現(xiàn)它的優(yōu)勢(shì),所以rocksdb 將io_uring集成到了 MultiGet 中的 MultiRead 接口之中。

需要注意的是 rocksdb 設(shè)置的 io_uring的SQ 隊(duì)列深度大小是256,且setup的時(shí)候并沒有開啟sq_poll模式,而是默認(rèn)開啟io_poll,即flag是0;如果想要開啟sq_poll模式,則需要變更這個(gè)接口的flags配置,比如將0設(shè)置為IORING_SETUP_SQPOLL,然后重新編譯源代碼即可。

inline struct io_uring* CreateIOUring() {
struct io_uring* new_io_uring = new struct io_uring;
int ret = io_uring_queue_init(kIoUringDepth, new_io_uring, 0);
if (ret) {
delete new_io_uring;
new_io_uring = nullptr;
}
return new_io_uring;
}

大家在使用db_bench測(cè)試io_uring的時(shí)候 如果不變更rocksdb這里的io_uring_queue_init接口的話,需要保證自己的磁盤支持io_poll模式,也就是通過上一節(jié)說(shuō)的那種查看/修改 nvme 驅(qū)動(dòng)配置來(lái)支持io_poll。

在io_poll模式下,對(duì)MultiGet的接口測(cè)試性能數(shù)據(jù)大概如下:

我的環(huán)境不支持io_poll,大體收益應(yīng)該和fio的poll模式下的性能收益差不了太多

圖片圖片來(lái)自官方

db_bench的配置可以使用,直接用rocksdb的master, CMakeList.txt 默認(rèn)會(huì)開啟io_uring:

生成數(shù)據(jù):

./db_bench_uring
--benchmarks=fillrandom,stats
--num=3000000000
--threads=32
--db=./db
--wal_dir=./db
--duration=3600
-report_interval_seconds=1
--stats_interval_seconds=10
--key_size=16
--value_size=128
--max_write_buffer_number=16
-max_background_compactions=32
-max_background_flushes=7
-subcompactions=8
-compression_type=none

io_uring 測(cè)試MultiGet,不使用block_cache:

./db_bench_uring
--benchmarks=multireadrandom,stats
--num=3000000000
--threads=32
--db=./db
--wal_dir=./db
--duration=3600
-report_interval_seconds=1
--stats_interval_seconds=10
--key_size=16
--value_size=128
-compression_type=none
-cache_size=0
-use_existing_db=1
-batch_size=256 # 每次MultiGet的 請(qǐng)求總個(gè)數(shù)
-multiread_batched=true # 使用 MultiGet 的新的API,支持MultiRead,否則就是逐個(gè)Get
-multiread_stride=0 # 指定MultiGet 時(shí)生成的key之間的跨度,本來(lái)是連續(xù)的隨機(jī)key,現(xiàn)在可以讓上一個(gè)隨機(jī)key和下一個(gè)隨機(jī)key之間間隔指定的長(zhǎng)度。

總結(jié)

總的來(lái)說(shuō),io_uring能夠在內(nèi)核的各個(gè)組件都能夠正常運(yùn)行的基礎(chǔ)上進(jìn)一步提升了性能,提升的部分包括 減少系統(tǒng)調(diào)用的開銷,減少內(nèi)核上下文的開銷,以及支持io_poll和sq_poll 這樣的高速輪詢處理機(jī)制。而且相比于libaio 僅能夠使用direct-io來(lái)調(diào)度,那這個(gè)限制本身就對(duì)存儲(chǔ)應(yīng)用軟件不夠友好了。

可見的未來(lái),存儲(chǔ)系統(tǒng)是內(nèi)核的直接用戶,隨著未來(lái)硬件介質(zhì)的超高速發(fā)展,互聯(lián)網(wǎng)應(yīng)用對(duì)存儲(chǔ)系統(tǒng)的高性能需求就會(huì)反作用于內(nèi)核,那內(nèi)核的一些I/O鏈路的性能也需要不斷得跟進(jìn)提升,然而每一項(xiàng)on-linux kernel的更改都因?yàn)閮?nèi)核精密復(fù)雜高要求 的 標(biāo)準(zhǔn)都會(huì)比普通的應(yīng)用復(fù)雜很多,io_uring 能夠合入5系內(nèi)核的upstream,那顯然證明了其未來(lái)的發(fā)展?jié)摿?以及 內(nèi)核社區(qū) 對(duì)其潛力的認(rèn)可。

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

    關(guān)注

    3

    文章

    1292

    瀏覽量

    57237
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4751

    瀏覽量

    68359
  • 編譯
    +關(guān)注

    關(guān)注

    0

    文章

    654

    瀏覽量

    32810
  • 組件
    +關(guān)注

    關(guān)注

    1

    文章

    505

    瀏覽量

    17805
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    帶你深入探索okio組件的奧秘 提升IO效率

    三方組件庫(kù)上新了一批JS/eTS組件,其中就包括okio組件。okio是一個(gè)可應(yīng)用于OpenAtom OpenHarmony(以下簡(jiǎn)稱“OpenHarmony”)的高效IO庫(kù),它依托于
    發(fā)表于 07-04 15:39 ?1036次閱讀

    如何將Runtime IO組件添加到現(xiàn)有項(xiàng)目?

    我最近在SPC5studio上下載了運(yùn)行時(shí)IO組件的更新,但是,我似乎找不到從IDE gui導(dǎo)入向?qū)?b class='flag-5'>組件添加到現(xiàn)有項(xiàng)目或新項(xiàng)目的方法。以上來(lái)自于谷歌翻譯以下為原文I recently
    發(fā)表于 11-15 10:59

    可擴(kuò)展的高性能RISC-V 內(nèi)核IP

    性能SiFive U8系列內(nèi)核IP的介紹開啟了現(xiàn)代SoC設(shè)計(jì)的新篇章。性能、面積效率和能效是芯片設(shè)計(jì)人員的關(guān)鍵指標(biāo),而SiFive U8系列在各個(gè)方面都表現(xiàn)搶眼。SiFive這種獨(dú)有
    發(fā)表于 08-13 15:14

    The Wirele Mea uring Sy tem of

    The Wirele Mea uring Sy tem of Temperature and Humidity and lt Application:介紹了利用無(wú)線收發(fā)元件構(gòu)成的無(wú)線溫濕度測(cè)量系統(tǒng)的設(shè)計(jì)方法。采用這種測(cè)量方案,不必敷設(shè)電纜,節(jié)省了費(fèi)用和時(shí)間。
    發(fā)表于 03-24 09:13 ?13次下載

    IO系統(tǒng)衡量性能的幾個(gè)指標(biāo)

    本系列文章試圖從基本概念開始對(duì)磁盤存儲(chǔ)相關(guān)的各種概念進(jìn)行綜合歸納,讓大家能夠?qū)?b class='flag-5'>IO性能相關(guān)的基本概念,IO性能的監(jiān)控和調(diào)整有個(gè)比較全面的了解
    發(fā)表于 03-28 12:01 ?2697次閱讀

    IO多路復(fù)用的幾種實(shí)現(xiàn)機(jī)制的分析

    服務(wù)器端編程經(jīng)常需要構(gòu)造高性能IO模型,常見的IO模型有四種:同步和異步的概念描述的是用戶線程與內(nèi)核的交互方式:同步是指用戶線程發(fā)起IO請(qǐng)
    發(fā)表于 03-07 11:40 ?5723次閱讀
    <b class='flag-5'>IO</b>多路復(fù)用的幾種實(shí)現(xiàn)機(jī)制的分析

    論述學(xué)習(xí)Linux內(nèi)核各個(gè)階段

    第三階段(回歸第一階段):你已經(jīng)工作了一段時(shí)間,寫了一些代碼,修復(fù)了一些bug,提交了一些patch,然后你重新回來(lái)迭代整體的知識(shí)框架,搞清楚各個(gè)子系統(tǒng)內(nèi)在的聯(lián)系。這階段你如果有興趣可以讀《深入
    的頭像 發(fā)表于 08-20 17:23 ?4987次閱讀

    io_uring 優(yōu)化 nginx,基于通用應(yīng)用 nginx 的實(shí)戰(zhàn)

    引言 io_uring是Linux內(nèi)核在v5.1引入的一套異步IO接口,隨著其迅速發(fā)展,現(xiàn)在的io_uring已經(jīng)遠(yuǎn)遠(yuǎn)超過了純IO的范疇。從
    的頭像 發(fā)表于 10-10 16:19 ?3047次閱讀
    <b class='flag-5'>io_uring</b> 優(yōu)化 nginx,基于通用應(yīng)用 nginx 的實(shí)戰(zhàn)

    Linux 6.0生命周期結(jié)束介紹

    在去年 10 月初,Linux 6.0 正式發(fā)布,當(dāng)時(shí)新版本帶來(lái)了非常多的新特性 / 功能,如 F2FS 低內(nèi)存模式、在使用 XFS 和 io_uring 時(shí)的異步緩沖寫入、對(duì) RISC-V 和 AArch64(ARM64)硬件架構(gòu)的改進(jìn),以及 Btrfs 和 OverlayFS 文件系統(tǒng)的新功能和改進(jìn)等。
    的頭像 發(fā)表于 01-16 11:43 ?808次閱讀

    現(xiàn)代異步存儲(chǔ)訪問API探索:libaio、io_uring和SPDK

    最近的高性能存儲(chǔ)設(shè)備暴露了現(xiàn)有軟件棧的低效,因而催生了對(duì)I/O棧的改進(jìn)。Linux內(nèi)核的最新API是io_uring。作者提供了第一個(gè)針對(duì)io_uring的深度研究,并且和libaio
    的頭像 發(fā)表于 06-27 10:54 ?1015次閱讀
    現(xiàn)代異步存儲(chǔ)訪問API探索:libaio、<b class='flag-5'>io_uring</b>和SPDK

    信號(hào)驅(qū)動(dòng)IO與異步IO的區(qū)別

    一. 談信號(hào)驅(qū)動(dòng)IO (對(duì)比異步IO來(lái)看) 信號(hào)驅(qū)動(dòng)IO 對(duì)比 異步 IO進(jìn)行理解 信號(hào)驅(qū)動(dòng)IO: 內(nèi)核
    的頭像 發(fā)表于 11-08 15:32 ?1010次閱讀
    信號(hào)驅(qū)動(dòng)<b class='flag-5'>IO</b>與異步<b class='flag-5'>IO</b>的區(qū)別

    linux異步io框架iouring應(yīng)用

    Linux內(nèi)核5.1支持了新的異步IO框架iouring,由Block IO大神也即Fio作者Jens Axboe開發(fā),意在提供一套公用的網(wǎng)絡(luò)和磁盤異步IO,不過
    的頭像 發(fā)表于 11-08 15:39 ?647次閱讀
    linux異步<b class='flag-5'>io</b>框架iouring應(yīng)用

    異步IO框架iouring介紹

    前言 Linux內(nèi)核5.1支持了新的異步IO框架iouring,由Block IO大神也即Fio作者Jens Axboe開發(fā),意在提供一套公用的網(wǎng)絡(luò)和磁盤異步IO,不過
    的頭像 發(fā)表于 11-09 09:30 ?2212次閱讀
    異步<b class='flag-5'>IO</b>框架iouring介紹

    如何去提高EtherCAT IO性能呢?

    進(jìn)行EtherCAT IO性能優(yōu)化涉及多個(gè)方面,包括硬件選擇、網(wǎng)絡(luò)配置、軟件優(yōu)化和應(yīng)用程序設(shè)計(jì)。
    的頭像 發(fā)表于 03-07 09:28 ?449次閱讀

    組件測(cè)試儀如何提高太陽(yáng)能組件性能

      JD-EL4太陽(yáng)能組件測(cè)試儀在太陽(yáng)能行業(yè)中扮演著至關(guān)重要的角色,不僅可以檢測(cè)組件的缺陷和問題,還可以幫助提高太陽(yáng)能組件性能。以下是太陽(yáng)能組件
    的頭像 發(fā)表于 05-21 17:13 ?648次閱讀