新时代异步 IO 框架:IO_URING 的原理、用法、业界示例分析

文章目录

  • IO_URING
    • 基本介绍
      • 常见 I/O 模型
      • IO_URING
    • 原理
      • 核心结构
      • 工作模式
      • 高级特性
    • 用法
      • API
      • liburing
        • 基本流程
        • Demo
    • 业界示例
      • SeaStar / ScyllaDB
      • CEPH
      • RocksDB
      • ClickHouse

IO_URING

基本介绍

常见 I/O 模型

当前 Linux 的几种 I/O 模型:
在这里插入图片描述

I/O 模型

  • 同步 I/O 是目前应用最广的 I/O 模型,其缺点非常明显:大量内存拷贝、系统调用导致上下文切换频繁;随着设备性能越来越高,这种方式已经无法有效利用设备的全部性能。
  • AIO 的优点就是通过异步方式和 Linux Kernel 交互,减少了对用户态应用程序的阻塞,提高了并发度,但其最大的缺点就是仅支持 Direct I/O,无法有效利用文件系统和 Page Cache。
  • SPDK 通过 Kernal Bypass 的方式实现,其基于 VFIO 在用户态重新实现 NVMe 驱动和协议,无系统调用、无上下文切换、无锁,是目前性能最高的 I/O 模型。但是它仅限于 NVMe,不支持其他磁盘类型,且使用起来非常困难。
    在这里插入图片描述
    各 I/O 模型的对比

为了弥补上述方案的缺陷,Linux Kernel 5.1 版本加入一个特性——IO_URING


IO_URING

IO_URING 的设计目标是提供一个统一、易用、可扩展、功能丰富、高效的网络和磁盘系统接口。其具有以下几个特点:

  • 真正异步:只要设置了合适的 flag,它在系统调用上下文中就只是将请求放入队列, 不会做其他任何额外的事情,保证了应用永远不会阻塞。
  • 支持任何类型的 I/O:cached files、direct-access files 甚至 blocking sockets。无需 poll+read/write 来处理 sockets。 只需提交一个阻塞式读(blocking read),请求完成之后,就会出现在 completion ring。
  • 可拓展、灵活:基于 IO_URING 甚至能重写 Linux 的每个系统调用。
  • 高性能
    • 用户态和内核态共享提交队列(submission queue)和完成队列(completion queue)。
    • 用户态支持 Polling 模式,不依赖硬件的中断,通过调用 IORING_ENTER_GETEVENTS 不断轮询收割完成事件。
    • 内核态支持 Polling 模式,IO 提交和收割可以 offload 给 Kernel,且提交和完成不需要经过系统调用。
    • 可以提前注册用户态内存地址/文件描述符,减小地址映射/引用计数的开销。
    • ……


原理

核心结构

如下图,每个 IO_URING 实例都有两个环形队列(RingBuffer,通过共享内存实现),由用户态和内核态共同管理。

  • 提交队列 submission queue (SQ):用户态线程生产,通过系统调用通知内核消费。
  • 完成队列 completion queue (CQ):内核生产,通知用户态消费。

两个队列都提供了无锁接口(内部通过 barriers 同步),都是单生产者,单消费者。
在这里插入图片描述

IO_URING 核心结构

工作方式

  • 提交:
    • 应用尝试获取一个 SQ Entry,并向 SQ Entry 中填充数据。
    • 提交 SQ Entry 到 SQ 中,更新 SQ tail。
  • 完成
    • 内核从 SQ head 处取出 SQ Entry 消费,并更新 SQ head。
    • 内核为完成的一个或多个请求创建 CQ Entry,更新 CQ tail。
    • 应用消费 CQ Entry,更新 CQ head。(无需切换到内核态)

这里的提交和完成都支持批量处理,如连续提交多个 Entry 到 SQ,或持续消费 CQ。


工作模式

  • 中断驱动模式(interrupt driven):默认,通过 io_uring_enter 通知内核 IO 请求的产生以及阻塞等待内核完成请求。
  • 轮询模式(polled):为了提升性能,内核提供了轮询的方式来提交 IO 请求
    • 提交 IO 的轮询(SQPOLL)
      • 当通过 IORING_SETUP_SQPOLL 开启提交队列轮询时,会启动一个内核线程不停的去检查 SQ 是否存在任务,并立即取出任务进行消费。而用户态程序只需要将任务塞进 SQ 提交队列即可,不再需要调用 io_uring_enter。当一段时间(sq_thread_idle 配置)内没有 Poll 到任何请求时,为了避免线程空转,会将其挂起并通过 IORING_SQ_NEED_WAKEUP 标志位更新状态到共享内存中。用户进程可以在每次提交任务时,通过该标志位检查内核 SQ 线程是否运行,如果未运行,则需要通过调用 io_uring_enter,并使用 IORING_SQ_NEED_WAKEUP 参数,来唤醒 SQ 线程。
      • 由于内核和用户态共享内存,所以完成的时候,用户态遍历直接遍历完成队列消费 CQ Entry 即可。在最理想的情况下,IO 提交和收割都不需要使用系统调用。
    • 完成 IO 的轮询(IOPOLL)
      • 在传统的模式下,将 I/O 请求提交给块设备后,进程会进入睡眠状态,当块设备处理完 I/O 请求后则会通过一个中断来唤醒进程,通知 I/O 已完成。IO_URING 中可通过 IORING_SETUP_IOPOLL 开启块设备轮询操作,即提交 I/O 后不直接进入睡眠,而是启动一个内核线程来循环检查 I/O 是否完成。由于不需要被动等待设备通知,因此可以更快获取 I/O 请求的完成状态,这对于延迟非常低以及 IOPS 很高的设备,能够显著提高性能,同时也避免了高频的中断所带来的性能开销,但同时也提高了 CPU 的开销。
      • 仅支持 Direct I/O;仅支持存储设备,且设备/文件系统必须要支持轮询。
  • 内核轮询模式(kernel polled):即同时开启 IORING_SETUP_SQPOLLIORING_SETUP_IOPOLL,内核会同时轮询 SQ 队列和设备驱动队列,无需主动调用 io_uring_enter 来触发。在这种模式下应用无需切换到内核态,无需任何系统调用也能够进行提交和完成,只需要在用户态轮询 CQ 即可。


高级特性

  • 资源预注册
    • 预注册缓冲区:对于频繁操作的 buffer,可以通过 IORING_REGISTER_BUFFERS 将 buffer 注册到内核中,避免每次 I/O 时都需要调用 get_user_pagesunpin_user_pages 进行虚拟地址到物理 Page 的映射。
    • 预注册文件描述符:Linux 在执行 I/O 操作时为了避免文件描述符被释放或者关闭,在访问文件时通过 fget 增加引用计数,在操作完成后通过 fput 减少,这也带来了大量的性能开销(https://lwn.net/Articles/787473/)。为了优化这一点,支持通过 IORING_REGISTER_FILES 提前将文件描述符注册到内核中,使文件描述符的引用计数始终为 1,避免掉这一部份开销。
  • 链接 I/O 操作:支持通过 IOSQE_IO_LINK 将多个 I/O 操作链接在一起,这些操作会通过一次调用同时提交,并按照链接的顺序进行执行。
  • 快速轮询:kernel5.7 后引入的新特性,通过 IORING_FEAT_FAST_POLL 优化对大量非阻塞文件描述符的轮询操作(用于网络 I/O)。其原理就是维护了一个快速轮询队列(类似 epoll),对于未就绪的描述符不再直接转交给异步线程,而是放入该队列中,内核会定期检查描述符是否就绪,一旦就绪就立即开始进行 I/O 操作。避免了不必要的异步线程创建以及线程阻塞,同时也省去了使用 epoll 的开销。
    • 当开启 IORING_SETUP_SQPOLLIORING_SETUP_IOPOLLIORING_FEAT_FAST_POLL 时,在最理想情况下轮询+读/写都能在用户态完成。


用法

API

接口可参考文档:https://tchaloupka.github.io/during/during.io_uring.html

IO_URING 只提供了三个接口:

/*
作用:用于初始化 io_uring 以及 SQ、CQ
参数:
entries:队列深度
params:params
返回值:io_uring 的描述符,失败时返回 -1 并设置 errno
*/
int io_uring_setup(unsigned entries, struct io_uring_params *params);


/*
作用:用于初始化 io_uring 以及 SQ、CQ
参数:
fd:io_uring 描述符
to_submit:指定了 SQ 中提交的 I/O 数量
min_complete:会等待这个数量的 I/O 事件完成再返回;当使用IOPOLL模式时如果未0,则立即返回当前结果。而非0时如果有完成事件,则立即返回;如果没有则poll指定次数或等到线程时间片结束后返回。
flags:标识符
sig:指向信号掩码的指针。调用io_uring_enter时会将当前信号掩码替换为sig,然后等待完成队列中的事件就绪后,再恢复为原始信号掩码
返回值:io_uring 的描述符,失败时返回 -1 并设置 errno
*/
int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig);

/*
作用:注册用于异步 I/O 的文件或用户缓冲区
参数:
fd:io_uring 描述符
opcode:操作码
arg:操作指定的参数
nr_args:参数数量
返回值:成功时返回 0,失败时返回 -1 并设置 errno
*/
int io_uring_register(unsigned int fd, unsigned int opcode, void *arg, unsigned int nr_args);


liburing

为了简化 IO_URING 的使用,避免一些繁琐的底层操作(如 ring buffer 管理、memory barrier、mmap 等),Jens Axboe 还封装了一套易用的高级 API —— liburing。

代码仓库:GitHub - axboe/liburing

API 手册:Manpages of liburing-dev in Debian unstable — Debian Manpages

基本流程
  • 使用 io_uring_queue_init 初始化 io_uring
    • 如果需要指定特殊参数可以使用 io_uring_queue_init_params
  • 注册文件描述符(可选)
    • io_uring_register_files 注册待操作文件描述符,之后操作这些文件时通过索引而不是描述符,减少系统调用,优化开销。
    • io_uring_register_eventfd 注册 eventfd,当 I/O 完成后,内核会往这个 fd 中写入一个值,通知异步 I/O 操作已完成。
  • 通过 io_uring_get_sqe 获取 sqe
  • 通过 io_uring_prep_$option 将 sqe 提交到提交队列中
    • 如果需要设置 user_data,可以通过 io_uring_sqe_set_data 传入。在 I/O 操作完成后可以通过 io_uring_cqe_get_data 从 cqe 中读出。
    • 如果有多个请求,可以使用 io_uring_sqe_set_flags 设置 IOSQE_IO_LINK,将请求链接到一起进行批处理。
  • 通过 io_uring_submit 通知 io_uring 从提交队列中消费 sqe
  • 等待完成队列中的任务就绪
    • 阻塞:io_uring_wait_cqe,阻塞等待有一个 cqe 返回
    • 非阻塞:io_uring_peek_cqe,如果没有就绪的 cqe,则直接报错返回。支持批量操作 io_uring_peek_batch_cqe
  • 当前 cqe 中的数据处理完成后,通过 io_uring_cqe_seen 将其标记成已处理,从完成队列中移除。(如果不处理则会一直保留,被重复消费)
  • 完成所有 I/O 操作后,使用 io_uring_queue_exit 销毁 io_uring


Demo

更多例子参考:

  • liburgin:https://github.com/axboe/liburing/tree/master/examples
  • echo_server:https://github.com/frevib/io_uring-echo-server/blob/master/io_uring_echo_server.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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: file\n", argv[0]);
		return 1;
	}

	ret = io_uring_queue_init(QD, &ring, 0);
	if (ret < 0) {
		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
		return 1;
	}

	fd = open(argv[1], O_RDONLY | O_DIRECT);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	if (fstat(fd, &sb) < 0) {
		perror("fstat");
		return 1;
	}

	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;
	}

	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);

	ret = io_uring_submit(&ring);
	if (ret < 0) {
		fprintf(stderr, "io_uring_submit: %s\n", strerror(-ret));
		return 1;
	} else if (ret != i) {
		fprintf(stderr, "io_uring_submit submitted less %d\n", ret);
		return 1;
	}

	done = 0;
	pending = ret;
	fsize = 0;
	for (i = 0; i < pending; i++) {
		ret = io_uring_wait_cqe(&ring, &cqe);
		if (ret < 0) {
			fprintf(stderr, "io_uring_wait_cqe: %s\n", strerror(-ret));
			return 1;
		}

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

	printf("Submitted=%d, completed=%d, bytes=%lu\n", pending, done,
						(unsigned long) fsize);
	close(fd);
	io_uring_queue_exit(&ring);
	return 0;
}


业界示例

SeaStar / ScyllaDB

在 SeaStar 中,reactor 是可插拔的组件(epoll、aio、io_uring),其基于 IO_URING 封装了一个新的 reactor_backend_uring,接管了所有网络和磁盘 I/O(buffer/direct)与事件循环。

实现参考:https://github.com/scylladb/scylladb/commit/1247be44b01b3402e8694dd622f8ed8306053c32

  • 网络 I/O:

    • 测试环境:8-core x86

    • 内核版本:linux v5.7

    • 测试参数:wrk -c 128 -t 4

    • 测试数据:https://github.com/scylladb/seastar/pull/1235#discussion_r989364804

AIO:
Running 10s test @ http://localhost:10000/
  4 threads and 128 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.49ms  109.56us   3.81ms   87.41%
    Req/Sec    21.51k   783.10    28.03k    87.84%
  862627 requests in 10.10s, 112.71MB read
Requests/sec:  85407.13
Transfer/sec:     11.16MB

IO_URING:
Running 10s test @ http://localhost:10000/
  4 threads and 128 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.35ms  400.14us  37.64ms   98.36%
    Req/Sec    23.94k     2.36k   26.30k    70.25%
  952788 requests in 10.00s, 124.48MB read
Requests/sec:  95266.43
Transfer/sec:     12.45MB
  • 磁盘 I/O:

    • 测试数据:https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
      在这里插入图片描述
      ScyllaDB 性能对比


CEPH

CEPH 主要是对其底层的存储引擎 BlueStore 进行改造。其原本架构图如下图所示,CEPH 本身就已经在 BlueFS 与磁盘的交互之间抽象出了一个 BlockDevice 层,且原本就采用了 AIO,只需要简单的修改就 API 就可以切换到 liburing,并且其运用了大部分高级特性(SQPOLL、IOPOLL、REGISTER_FILES)。

实现参考:https://github.com/ceph/ceph/pull/27392/files

在这里插入图片描述

BlueStore 架构

以下是官方给出的测试数据:

内核版本:
  linux v5.1
测试参数:
  rw=randwrite
  iodepth=16
  nr_files=1
  numjobs=1
  size=256m

  bluestore_min_alloc_size = 4096
  bluestore_max_blob_size  = 65536

  bluestore_block_path     = /dev/ram0
  bluestore_block_db_path  = /dev/ram1
  bluestore_block_wal_path = /dev/ram2

使用AIO:
bluestore_iouring=false
   4k  IOPS=25.5k, BW=99.8MiB/s, Lat=0.374ms
   8k  IOPS=21.5k, BW=168MiB/s,  Lat=0.441ms
  16k  IOPS=17.2k, BW=268MiB/s,  Lat=0.544ms
  32k  IOPS=12.3k, BW=383MiB/s,  Lat=0.753ms
  64k  IOPS=8358,  BW=522MiB/s,  Lat=1.083ms
 128k  IOPS=4724,  BW=591MiB/s,  Lat=1.906ms

使用IO_URING:
bluestore_iouring=true
   4k  IOPS=29.2k, BW=114MiB/s,  Lat=0.331ms
   8k  IOPS=30.7k, BW=240MiB/s,  Lat=0.319ms
  16k  IOPS=27.4k, BW=428MiB/s,  Lat=0.368ms
  32k  IOPS=22.7k, BW=709MiB/s,  Lat=0.475ms
  64k  IOPS=15.6k, BW=978MiB/s,  Lat=0.754ms
 128k  IOPS=9572,  BW=1197MiB/s, Lat=1.223ms

IOPS 提升:
Overall IOPS increase is the following:
   4k  +14%
   8k  +42%
  16k  +59%
  32k  +89%
  64k  +85%
 128k  +102%


RocksDB

RocksDB 基于 IO_URING 实现了 PosixRandomAccessFile::MultiRead(),并且也只是使用了最基本的功能。RocksDB 是直接在该接口中构造了一个 uring,一次性将所有读取请求批量填充进去,并循环等待所有请求完成(未让出线程,本质上还是同步读取),如果出现任何异常,则退化至原先的方式(线程池 + pread)。

实现参考:https://github.com/facebook/rocksdb/pull/5881/files

在这里插入图片描述

RocksDB 异步架构

官方给的测试数据如下:
在这里插入图片描述

RocksDB 性能提升

详细数据参考:https://www.slideshare.net/ennael/kernel-recipes-2019-faster-io-through-iouring

除了官方的实现,很多公司都有对 RocksDB 进行 IO_URING 改造,例如 TIKV 用 IO_URING 重构了 wal、sstable 的 write 和 compaction 。
https://openinx.github.io/ppt/io-uring.pdf


ClickHouse

ClickHouse 只是用了使用了最基础的 IO_URING(未用到高级特性,主要是由于 CK 中需要根据文件大小来动态决定是否使用 PageCache,因此无法兼容 IOPOLL),将文件系统的同步读取改造为异步(OLAP 数据库主要开销在读取上)。ClickHouse 本身就已经抽象出了 Reader 来接管 FS 层的读 I/O,因此他们只需要简单封装一个 IOUringReader 并返回一个异步的 Future 即可完成改造。

实现参考:https://github.com/ClickHouse/ClickHouse/pull/36103/files

官方给出了测试数据

  • 测试环境:i7-7700K / 32GB desktop with a 7200rpm WD HDD dis、Linux V5.17
  • 测试语句:select count(ignore(*)) from visits
  • 测试参数: min_bytes_to_use_direct_iolocal_filesystem_read_prefetch
  • 测试结果:
+----------------------+---------------+---------------+-------------+-------------+-----------+
| direct_io / prefetch |     pread     |   io_uring    | improvement | significant | cpu_usage |
+----------------------+---------------+---------------+-------------+-------------+-----------+
| no  / no             | 10.75 ± 0.47s |  9.91 ± 0.49s |       7.75% |         yes |    -1.29% |
| no  / yes            |  8.86 ± 0.23s |  7.85 ± 0.31s |      11.48% |         yes |    -2.86% |
| yes / yes            | 14.41 ± 0.57s | 11.49 ± 0.28s |      20.27% |         yes |    -1.74% |
| yes / no             | 14.37 ± 0.50s | 14.34 ± 0.47s |       0.25% |          no |   -24.52% |
+----------------------+---------------+---------------+-------------+-------------+-----------+

在并发场景下,随着并发度的提升,IO_URING 的优势也越来越大。
在这里插入图片描述

ClickHouse 查询性能对比

在这里插入图片描述

ClickHouse CPU 开销对比

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/389098.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024.2.10 HCIA - Big Data笔记

1. 大数据发展趋势与鲲鹏大数据大数据时代大数据的应用领域企业所面临的挑战和机遇华为鲲鹏解决方案2. HDFS分布式文件系统和ZooKeeperHDFS分布式文件系统HDFS概述HDFS相关概念HDFS体系架构HDFS关键特性HDFS数据读写流程ZooKeeper分布式协调服务ZooKeeper概述ZooKeeper体系结构…

【C++】C++11上

C11上 1.C11简介2.统一的列表初始化2.1 {} 初始化2.2 initializer_list 3.变量类型推导3.1auto3.2decltype3.3nullptr 4.范围for循环5.final与override6.智能指针7. STL中一些变化8.右值引用和移动语义8.1左值引用和右值引用8.2左值引用与右值引用比较8.3右值引用使用场景和意义…

GPU独显下ubuntu屏幕亮度不能调节解决方法

GPU独显下屏幕亮度不能调节&#xff08;假设你已经安装了合适的nvidia显卡驱动&#xff09;&#xff0c;我试过修改 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT"quiet splash acpi_backlightvendor" &#xff0c;没用。修改和xorg.conf相关的文件&#xff0c;…

【python】类创建、实例化和调用类方法、子类、继承、私有属性、静态方法

一、类属性&#xff1a;定义在类中函数外的属性;self代表类的实例。 其中 number属于类属性&#xff0c;name、age属于实例属性&#xff1b;实例属性一般在初始函数中定义。 class people:number300 #类属性def __init__(self,name,age):#初始化方法 左右两个下划线self.nam…

就是民族的气节

我们拥有一个名字叫中国 - 张明敏 一把黄土塑成千万个你我 静脉是长城 动脉是黄河五千年的文化是生生不息的脉搏&#xff08;齐楚燕韩赵魏秦&#xff09;提醒你 提醒我我们拥有个名字叫中国&#xff08;中原地区为主体&#xff0c;河南&#xff0c;山东&#xff0c;安徽&…

语言与真实世界的关系(超级语言生成能力将促进世界深刻变化)

语言与真实世界之间存在着紧密且复杂的关系。在人类社会中&#xff0c;语言是认知、表达和交流现实世界的主要工具&#xff0c;它帮助我们构建并理解周围环境&#xff0c;并将我们的思维和经验概念化。 1. 符号与指代&#xff1a; 语言是一种符号系统&#xff0c;通过词汇、句…

【精选】Java面向对象进阶——接口

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

(11)Hive调优——explain执行计划

一、explain查询计划概述 explain将Hive SQL 语句的实现步骤、依赖关系进行解析&#xff0c;帮助用户理解一条HQL 语句在底层是如何实现数据的查询及处理&#xff0c;通过分析执行计划来达到Hive 调优&#xff0c;数据倾斜排查等目的。 官网指路&#xff1a; https://cwiki.ap…

第14讲投票帖子详情实现

投票帖子详情实现 后端,根据id查询投票帖子信息&#xff1a; /*** 根据id查询* param id* return*/ GetMapping("/{id}") public R findById(PathVariable(value "id")Integer id){Vote vote voteService.getById(id);WxUserInfo wxUserInfo wxUserInf…

【Go语言】第一个Go程序

第一个 Go 程序 1 安装 Go Go语言官网&#xff1a;Download and install - The Go Programming Language&#xff0c;提供了安装包以及引导流程。 以 Windows 为例&#xff0c;进入windows安装包下载地址&#xff1a;All releases - The Go Programming Language&#xff0c…

BDD - Python Behave 用户自定义配置文件

BDD - Python Behave 用户自定义配置文件 引言默认 behave.ini 配置文件自定义配置文件json 格式的配置文件ini 格式的配置文件 实例应用项目结构代码BDD/Features/user_data.feature 文件BDD/steps/user_data_steps.py 文件BDD/environment.py 文件默认配置文件 behave.ini自定…

构建智慧交通平台:架构设计与实现

随着城市交通的不断发展和智能化技术的迅速进步&#xff0c;智慧交通平台作为提升城市交通管理效率和水平的重要手段备受关注。本文将探讨如何设计和实现智慧交通平台的系统架构&#xff0c;以应对日益增长的城市交通需求&#xff0c;并提高交通管理的智能化水平。 ### 1. 智慧…

Spring 用法学习总结(二)之基于注解注入属性

Spring学习 5 基于注解方式创建对象6 基于注解注入属性 5 基于注解方式创建对象 注解是代码的特殊标记&#xff0c;可以简化xml配置&#xff0c;格式&#xff1a;注解名称(属性名称属性值&#xff09;&#xff0c;可以作用在类、方法、属性上 以下注解都可以创建bean实例 Com…

【Qt】qt常用控件之QIcon 以及 qrc机制设置图片路径(QtCreator)

文章目录 1. QIcon / windowIcon2. setIcon() 与 setwindowIcon()2.1 setIcon() 介绍与使用2.2 setWindowIcon 介绍与使用 3. 路径问题 & qrc机制的引入3.1 绝对路径 / 相对路径 的问题3.2 qrc机制3.3 在QtCreator下利用qrc机制引入图片 1. QIcon / windowIcon QIcon QIco…

Java学习第十四节之冒泡排序

冒泡排序 package array;import java.util.Arrays;//冒泡排序 //1.比较数组中&#xff0c;两个相邻的元素&#xff0c;如果第一个数比第二个数大&#xff0c;我们就交换他们的位置 //2.每一次比较&#xff0c;都会产生出一个最大&#xff0c;或者最小的数字 //3.下一轮则可以少…

009集——磁盘详解——电脑数据如何存储在磁盘

很多人也知道数据能够保存是由于设备中有一个叫做「硬盘」的组件存在&#xff0c;但也有很多人不知道硬盘是怎样储存这些数据的。这里给大家讲讲其中的原理。 首先我们要明白的是&#xff0c;计算机中只有0和1&#xff0c;那么我们存入硬盘的数据&#xff0c;实际上也就是一堆0…

WMS仓库管理系统:一文掌握,不懂的的看过来。

本期B端系统扫盲带来WMS系统&#xff0c;这个的应用也非常常见&#xff0c;贝格前端工场力争用浅显的语言将此系统介绍清楚&#xff0c;如有系统升级和定制需求的可以单独沟通&#xff0c;闲言少叙开整。 一、WMS系统的定义 WMS是Warehouse Management System的缩写&#xff…

平时积累的FPGA知识点(9)

平时在FPGA群聊等积累的FPGA知识点&#xff0c;第9期&#xff1a; 31 ldpc的license是什么&#xff1f; 解释&#xff1a;Xilinx公司的Zynq UltraScale RFSoC系列芯片进行项目开发&#xff0c;在某些芯片型号中&#xff0c;自身带有SD-FEC硬核资源&#xff0c;具体查询方式&a…

算法学习——LeetCode力扣贪心篇4

算法学习——LeetCode力扣贪心篇4 763. 划分字母区间 763. 划分字母区间 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xf…

找负环(图论基础)

文章目录 负环spfa找负环方法一方法二实际效果 负环 环内路径上的权值和为负。 spfa找负环 两种基本的方法 统计每一个点的入队次数&#xff0c;如果一个点入队了n次&#xff0c;则说明存在负环统计当前每个点中的最短路中所包含的边数&#xff0c;如果当前某个点的最短路所…