Linux新的IO模型io_uring
一、Linux下的网络通信模型
在网络开发的过程中,需要处理好几个问题。首先是通信的内核支持问题;其次是通信的模型问题;最后是框架问题。这些问题在闭源的OS如Windows上,基本上不算什么大问题(因为只能用人家的API)。但在开源的OS上,典型的就是Linux上就是一个重要的问题。
在开源的系统上,如果内核原生支持一种IO通信,那么,效率一般来说会比不支持要高很多。网络通信有一个问题,它有点和算法在本质上有些相通,即选用哪种模型会导致它的通信效率差距非常大。举一个不恰当的例子,用一个只有两个槽的插座和十个槽的插座,能同时支持的用电接口个数完全不同。而在前面两个相同,而应用上,由于不同人对整个应用的理解和设计架构不同,导致其对应用支持的效率又有不同。同时,可能一种网络通信模型无法满足一些应用场景,所以框架一般会封装多种网络通信模型,这也是网络通信框架存在的意义。
但是有的时候,模型和框架往往混在一起,所以同一种技术在网络和书籍上的叫法均有不同,大家自己明白怎么回事儿即可,不要在这些细节上纠结。
看过Redis源的都清楚,在Redis中,对类linux的网络通信模型支持的有select,poll,epoll,kqueue。当然,可能不同的版本有所不同,这些细节就不再讨论。
一般来说,网络通信属于IO通信,而在IO通信中常见的几种通信模型有:
1、阻塞IO(Blocking IO)
2、非阻塞IO(NonBlocking IO)
3、IO多路复用(IO Multiplexing)
4、信号驱动(Signal Driven IO)
5、异步IO(Asynchonous IO)
这几种模型在《Unix网络编程》中都有总结。需要说明的是,这是从模型角度来抽象出来的,所以在其实现的过程中,是结合了内核支持和模型特点及应用支持几方面的来说明的。以前一般认为,在Linux中,对异步支持不如Windows好。
但开源技术有一个好处,缺啥补啥。所以在2019年的Linux内核5.1版本中,出现了一种新的IO框架,也就是io_uring。它是由block IO维护者Jens Axboe开发的一个用来支持异步IO通信的框架。
二、IO框架uring
io_uring为什么叫IO框架?因为其一开始主要是给存储搞的。网上的测评那是和SPDK可以一较高低,如果真那样,io_uring可比SPDK要强大,因为intel毕竟是一家国际大公司再加从底层直接支持。
但是,网络也属于IO啊,所以后来也就支持了网络IO。刚刚提到了,在Linux原生支持的IO通信中,大多数都是同步的,即使有一些框架实现了异步非阻塞仍然做的不是很好(AIO),在不少的情况下,仍然会出现非异步的情况。但是io_uring的出现,可以说是一种革命性的突破,它和eBPF是内核的两个重大的技术里程碑。
一个优秀的异步IO框架,一般具有以下几个特点:
1、异步非阻塞
异步和非阻塞往往是和IO通信分不开的,而异步和非阻塞又往往出现在一起。在IO通信中,非阻塞是实现异步的一个前提。而异步非阻塞往往是一种IO通信框架是否高效的基础。
2、减少或删除中间环节
容易理解的是,同步阻塞通信因为等待的原因,对中间环节通信(事件通知、数据拷贝次数、系统调用次数等)敏感性不强。但是异步非阻塞往往意味着海量的IO操作,所以中间环节增加任何一个步骤,付出的代价往往是难以忍受的。这也是AIO被诟病的地方。
3、扩展性要强
一个IO框架,不能只适应一种IO情况。比如磁盘、网络等都可以支持。
4、应用方便快捷
这是一个好的框架必备的特点。再优秀的东西,一旦变得复杂,就会慢慢失去它的优秀性。复杂就意味着高成本和后期维护的困难。
从Linux的异步框架发展来看,在io_uring之前出现过一些异步框架,但是对上述问题的解决都不是太好,所以一直无法融入内核之中,直到io_ring的出现。
而io_uring基本实现了上述几个特点。
首先,io_uring通过使用共享内存技术,大幅减少了内核层和应用层数据的交互拷贝,也减少了内核层和用户层的调用次数。其次数据隔离、交互减少就意味着内核和用户层可以自行其事,安排自己的线程工作状态时不用等待对方状态结果,这也是异步通信的一个重要前提。为了进一步实现异步,io_uring还实现了一个无锁环形队列,通过操作其实现用户态和内核态的IO非阻塞交互。
通过上述的说明基本可以了解io_uring的工程机制,做为一种框架,需要说明一下io_uring操作的流程:
1、应用程序提交IO操作请求到提交队列
2、SQ内核线程读取IO操作
3、SQ内核线程发起IO请求
4、SQ内核线程将结果写回完成队列
5、应用程序在完成队列读到IO结果
io_uring通过共享内存来减少交互,在io_uring中,主要有四种队列,即:
1、提交队列(Submission Queue, SQ):一整块连续的内存空间存储的环形队列,存放将执行 I/O 操作的数据(指向SQE数组的索引)
2、完成队列(Completion Queue, CQ):一整块连续的内存空间存储的环形队列,存放 I/O 操作完成后的结果
3、提交队列项数组(Submission Queue Entry,SQE) :提交队列中的每项是SQ中的数据项
4、CQE - Completion Queue Entry:完成队列项,这是储存在CQ中的数据项
io_uring 在创建时有两个选项,对应着 io_uring 处理任务的不同方式:
1、IORING_SETUP_IOPOLL,io_uring 会使用轮询的方式执行所有的操作
2、IORING_SETUP_SQPOLL,io_uring 会创建一个内核线程专门用来处理用户提交的任务
这两个选项的设置会对之后的交互产生如下影响:
1、都不设置,即以io_uring_enter 提交任务,内核线程任务无需 syscall
2、设置IORING_SETUP_IOPOLL,以io_uring_enter 提交任务和处理任务。
3、设置IORING_SETUP_SQPOLL,直接提交处理任务(不需要syscall)。内核线程自动控制处理并在一定条件下休眠,然后可使用io_uring_enter唤醒。
当用户设置了 IORING_SETUP_SQPOLL选项(SQPOLL模式)创建 io_uring 时,内核将会创建一个名为 io_uring-sq 的内核线程(即SQ线程),SQ线程从提交队列读取IO操作,并且发起IO请求。
当IO请求完成以后,SQ线程将会把IO操作的结果写回到完成队列,这样,应用程序就可以从中得到IO操作的结果。
三、应用例程
直接使用io_uring有些复杂,开发者在io_uring的基础上又封装了一个库即liburing,这样普通开发者可以直接使用它。看一下其UDP通信的例程:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/udp.h>
#include <arpa/inet.h>#include "liburing.h"#define QD 64
#define BUF_SHIFT 12 /* 4k */
#define CQES (QD * 16)
#define BUFFERS CQES
#define CONTROLLEN 0struct sendmsg_ctx {struct msghdr msg;struct iovec iov;
};struct ctx {struct io_uring ring;struct io_uring_buf_ring *buf_ring;unsigned char *buffer_base;struct msghdr msg;int buf_shift;int af;bool verbose;struct sendmsg_ctx send[BUFFERS];size_t buf_ring_size;
};static size_t buffer_size(struct ctx *ctx)
{return 1U << ctx->buf_shift;
}static unsigned char *get_buffer(struct ctx *ctx, int idx)
{return ctx->buffer_base + (idx << ctx->buf_shift);
}static int setup_buffer_pool(struct ctx *ctx)
{int ret, i;void *mapped;struct io_uring_buf_reg reg = { .ring_addr = 0,.ring_entries = BUFFERS,.bgid = 0 };ctx->buf_ring_size = (sizeof(struct io_uring_buf) + buffer_size(ctx)) * BUFFERS;mapped = mmap(NULL, ctx->buf_ring_size, PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);if (mapped == MAP_FAILED) {fprintf(stderr, "buf_ring mmap: %s\n", strerror(errno));return -1;}ctx->buf_ring = (struct io_uring_buf_ring *)mapped;io_uring_buf_ring_init(ctx->buf_ring);reg = (struct io_uring_buf_reg) {.ring_addr = (unsigned long)ctx->buf_ring,.ring_entries = BUFFERS,.bgid = 0};ctx->buffer_base = (unsigned char *)ctx->buf_ring +sizeof(struct io_uring_buf) * BUFFERS;ret = io_uring_register_buf_ring(&ctx->ring, ®, 0);if (ret) {fprintf(stderr, "buf_ring init failed: %s\n""NB This requires a kernel version >= 6.0\n",strerror(-ret));return ret;}for (i = 0; i < BUFFERS; i++) {io_uring_buf_ring_add(ctx->buf_ring, get_buffer(ctx, i), buffer_size(ctx), i,io_uring_buf_ring_mask(BUFFERS), i);}io_uring_buf_ring_advance(ctx->buf_ring, BUFFERS);return 0;
}static int setup_context(struct ctx *ctx)
{struct io_uring_params params;int ret;memset(¶ms, 0, sizeof(params));params.cq_entries = QD * 8;params.flags = IORING_SETUP_SUBMIT_ALL | IORING_SETUP_COOP_TASKRUN |IORING_SETUP_CQSIZE;ret = io_uring_queue_init_params(QD, &ctx->ring, ¶ms);if (ret < 0) {fprintf(stderr, "queue_init failed: %s\n""NB: This requires a kernel version >= 6.0\n",strerror(-ret));return ret;}ret = setup_buffer_pool(ctx);if (ret)io_uring_queue_exit(&ctx->ring);memset(&ctx->msg, 0, sizeof(ctx->msg));ctx->msg.msg_namelen = sizeof(struct sockaddr_storage);ctx->msg.msg_controllen = CONTROLLEN;return ret;
}static int setup_sock(int af, int port)
{int ret;int fd;uint16_t nport = port <= 0 ? 0 : htons(port);fd = socket(af, SOCK_DGRAM, 0);if (fd < 0) {fprintf(stderr, "sock_init: %s\n", strerror(errno));return -1;}if (af == AF_INET6) {struct sockaddr_in6 addr6 = {.sin6_family = af,.sin6_port = nport,.sin6_addr = IN6ADDR_ANY_INIT};ret = bind(fd, (struct sockaddr *) &addr6, sizeof(addr6));} else {struct sockaddr_in addr = {.sin_family = af,.sin_port = nport,.sin_addr = { INADDR_ANY }};ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));}if (ret) {fprintf(stderr, "sock_bind: %s\n", strerror(errno));close(fd);return -1;}if (port <= 0) {int port;struct sockaddr_storage s;socklen_t sz = sizeof(s);if (getsockname(fd, (struct sockaddr *)&s, &sz)) {fprintf(stderr, "getsockname failed\n");close(fd);return -1;}port = ntohs(((struct sockaddr_in *)&s)->sin_port);fprintf(stderr, "port bound to %d\n", port);}return fd;
}static void cleanup_context(struct ctx *ctx)
{munmap(ctx->buf_ring, ctx->buf_ring_size);io_uring_queue_exit(&ctx->ring);
}static bool get_sqe(struct ctx *ctx, struct io_uring_sqe **sqe)
{*sqe = io_uring_get_sqe(&ctx->ring);if (!*sqe) {io_uring_submit(&ctx->ring);*sqe = io_uring_get_sqe(&ctx->ring);}if (!*sqe) {fprintf(stderr, "cannot get sqe\n");return true;}return false;
}static int add_recv(struct ctx *ctx, int idx)
{struct io_uring_sqe *sqe;if (get_sqe(ctx, &sqe))return -1;io_uring_prep_recvmsg_multishot(sqe, idx, &ctx->msg, MSG_TRUNC);sqe->flags |= IOSQE_FIXED_FILE;sqe->flags |= IOSQE_BUFFER_SELECT;sqe->buf_group = 0;io_uring_sqe_set_data64(sqe, BUFFERS + 1);return 0;
}static void recycle_buffer(struct ctx *ctx, int idx)
{io_uring_buf_ring_add(ctx->buf_ring, get_buffer(ctx, idx), buffer_size(ctx), idx,io_uring_buf_ring_mask(BUFFERS), 0);io_uring_buf_ring_advance(ctx->buf_ring, 1);
}static int process_cqe_send(struct ctx *ctx, struct io_uring_cqe *cqe)
{int idx = cqe->user_data;if (cqe->res < 0)fprintf(stderr, "bad send %s\n", strerror(-cqe->res));recycle_buffer(ctx, idx);return 0;
}static int process_cqe_recv(struct ctx *ctx, struct io_uring_cqe *cqe,int fdidx)
{int ret, idx;struct io_uring_recvmsg_out *o;struct io_uring_sqe *sqe;if (!(cqe->flags & IORING_CQE_F_MORE)) {ret = add_recv(ctx, fdidx);if (ret)return ret;}if (cqe->res == -ENOBUFS)return 0;if (!(cqe->flags & IORING_CQE_F_BUFFER) || cqe->res < 0) {fprintf(stderr, "recv cqe bad res %d\n", cqe->res);if (cqe->res == -EFAULT || cqe->res == -EINVAL)fprintf(stderr,"NB: This requires a kernel version >= 6.0\n");return -1;}idx = cqe->flags >> 16;o = io_uring_recvmsg_validate(get_buffer(ctx, cqe->flags >> 16),cqe->res, &ctx->msg);if (!o) {fprintf(stderr, "bad recvmsg\n");return -1;}if (o->namelen > ctx->msg.msg_namelen) {fprintf(stderr, "truncated name\n");recycle_buffer(ctx, idx);return 0;}if (o->flags & MSG_TRUNC) {unsigned int r;r = io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg);fprintf(stderr, "truncated msg need %u received %u\n",o->payloadlen, r);recycle_buffer(ctx, idx);return 0;}if (ctx->verbose) {struct sockaddr_in *addr = io_uring_recvmsg_name(o);struct sockaddr_in6 *addr6 = (void *)addr;char buff[INET6_ADDRSTRLEN + 1];const char *name;void *paddr;if (ctx->af == AF_INET6)paddr = &addr6->sin6_addr;elsepaddr = &addr->sin_addr;name = inet_ntop(ctx->af, paddr, buff, sizeof(buff));if (!name)name = "<INVALID>";fprintf(stderr, "received %u bytes %d from [%s]:%d\n",io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg),o->namelen, name, (int)ntohs(addr->sin_port));}if (get_sqe(ctx, &sqe))return -1;ctx->send[idx].iov = (struct iovec) {.iov_base = io_uring_recvmsg_payload(o, &ctx->msg),.iov_len =io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg)};ctx->send[idx].msg = (struct msghdr) {.msg_namelen = o->namelen,.msg_name = io_uring_recvmsg_name(o),.msg_control = NULL,.msg_controllen = 0,.msg_iov = &ctx->send[idx].iov,.msg_iovlen = 1};io_uring_prep_sendmsg(sqe, fdidx, &ctx->send[idx].msg, 0);io_uring_sqe_set_data64(sqe, idx);sqe->flags |= IOSQE_FIXED_FILE;return 0;
}
static int process_cqe(struct ctx *ctx, struct io_uring_cqe *cqe, int fdidx)
{if (cqe->user_data < BUFFERS)return process_cqe_send(ctx, cqe);elsereturn process_cqe_recv(ctx, cqe, fdidx);
}int main(int argc, char *argv[])
{struct ctx ctx;int ret;int port = -1;int sockfd;int opt;struct io_uring_cqe *cqes[CQES];unsigned int count, i;memset(&ctx, 0, sizeof(ctx));ctx.verbose = false;ctx.af = AF_INET;ctx.buf_shift = BUF_SHIFT;while ((opt = getopt(argc, argv, "6vp:b:")) != -1) {switch (opt) {case '6':ctx.af = AF_INET6;break;case 'p':port = atoi(optarg);break;case 'b':ctx.buf_shift = atoi(optarg);break;case 'v':ctx.verbose = true;break;default:fprintf(stderr, "Usage: %s [-p port] ""[-b log2(BufferSize)] [-6] [-v]\n",argv[0]);exit(-1);}}sockfd = setup_sock(ctx.af, port);if (sockfd < 0)return 1;if (setup_context(&ctx)) {close(sockfd);return 1;}ret = io_uring_register_files(&ctx.ring, &sockfd, 1);if (ret) {fprintf(stderr, "register files: %s\n", strerror(-ret));return -1;}ret = add_recv(&ctx, 0);if (ret)return 1;while (true) {ret = io_uring_submit_and_wait(&ctx.ring, 1);if (ret == -EINTR)continue;if (ret < 0) {fprintf(stderr, "submit and wait failed %d\n", ret);break;}count = io_uring_peek_batch_cqe(&ctx.ring, &cqes[0], CQES);for (i = 0; i < count; i++) {ret = process_cqe(&ctx, cqes[i], 0);if (ret)goto cleanup;}io_uring_cq_advance(&ctx.ring, count);}cleanup:cleanup_context(&ctx);close(sockfd);return ret;
}
更多的代码可以参看GITHUB中的相关代码。
四、技术说明
通过上面的分析,大家是否有所明白。包括DPDK等一些高效的框架,一些主要的特点,基本都是通过共享内存来减少内核与应用层的交互(特别是数据的拷贝),通过无锁队列来实现高效的非阻塞的通信。这意味着内核以后的工作可能会尽量减少和上层应用的直接通信。换句话说,内核要做一个高效的管理者和协调者而不是一个大而全的工作者。
这其实给开发者的一个借鉴在于:设计和开发时,要适当抽象(抽象越高,意味着引入的中间层越多),在可能的前提下尽量减少中间环节;引入新技术,如无锁编程等;异步工作是方向。
以Linux的IO技术发展为例,可以为两大部分,即内核原生支持和非原生支持(DPDK)。而原生支持里,从同步阻塞IO开始慢慢发展到异步IO的过程,又到io_uring融入内核的过程,就是给予开发者借鉴的一个经典的例子。可能大多数开发者意识不到这一点,也有可能在国内这个过程非常迅速甚至忽略某些中间过程。但实际上,整个的方向和发展的过程的经历基本是类似的。
本文对io_uring只是一个基础的介绍说明,大家有兴趣可以深入学习并引入自己的工程中去。实践出真知。
五、总结
这里没有更细节展开谈IO模型和同步异步以及阻塞非阻塞的问题,这些基础的问题,不明白的需要去查阅相关资料。这篇文章只是一个开始,以后有机会的话,会将整个网络开发进行整体的分析说明和具体应用。
技术的进步一定是向前发展的,开发者们一定睁开眼睛,瞭望世界,不要固守一隅,夜郎自大。
相关文章:
Linux新的IO模型io_uring
一、Linux下的网络通信模型 在网络开发的过程中,需要处理好几个问题。首先是通信的内核支持问题;其次是通信的模型问题;最后是框架问题。这些问题在闭源的OS如Windows上,基本上不算什么大问题(因为只能用人家的API&am…...
FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍
FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍 FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍FFmpeg 简介FFmpeg 基础知识复用与解复用编解码器码率和帧率 资料 FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍 本系列文章要解决的问题࿱…...
数组篇 第一题:删除排序数组中的重复项
更多精彩内容请关注微信公众号:听潮庭。 第一题:删除排序数组中的重复项 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应…...
堆的初步认识
在学习本节文章前要先了解:大顶堆与小顶堆: (优先级队列_加瓦不加班的博客-CSDN博客) 堆实现 计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树实现。 什么叫完全二叉树? 答&#x…...
CycleGAN模型之Pytorch实战
一、CycleGAN基本介绍 1. CycleGAN论文:《Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks》 2. 原文代码:https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix 3. 网传精简代码:https://github.com/aitorzip/PyTorch-CycleGAN …...
C++(STL容器适配器)
前言: 适配器也称配接器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。 《Design Patterns》对adapter的定义如下:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而…...
软考 系统架构设计师系列知识点之软件架构风格(7)
接前一篇文章:软考 系统架构设计师系列知识点之软件架构风格(6) 这个十一注定是一个不能放松、保持“紧”的十一。由于报名了全国计算机技术与软件专业技术资格(水平)考试,11月4号就要考试,因此…...
【Vue3】自定义指令
除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。 1. 生命周期钩子函数 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。 在 <script …...
UG\NX CAM二次开发 加工模块获取 UF _ask_application_module
文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 加工模块获取 UF _ask_application_module 代码: void MyClass::do_it() { // TODO: add your code here // 获取NX当前所在的模块 int module_id = 0; // UF_ask_application_module(&…...
借助GPU算力编译Android
借助GPU算力编译Android 借助GPU编译Android代码的意义在于提高编译的效率和速度。传统的CPU编译方式在处理大量代码时可能会遇到性能瓶颈,而GPU编译利用了显卡的并行计算能力,可以同时处理多个任务,加快编译过程。通过利用GPU的并行计算能力,可以将编译过程中的多个任务分…...
docker-compose一键部署mysql
1.创建安装目录 mnt为硬盘挂载目录,根据实际情况修改 mkdir -p /mnt/mysql cd /mnt/mysql vim docker-compose.yml2.编写docker-compose.yml version: 3.1 services:db:image: mysql:5.7 #mysql版本volumes:- ./data/db:/var/lib/mysql #数据文件- ./etc/my.cnf:/…...
MATLAB 函数签名器
文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) ,在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…...
2019强网杯随便注bugktu sql注入
一.2019强网杯随便注入 过滤了一些函数,联合查询,报错,布尔,时间等都不能用了,尝试堆叠注入 1.通过判断是单引号闭合 ?inject1-- 2.尝试堆叠查询数据库 ?inject1;show databases;-- 3.查询数据表 ?inject1;show …...
Html+Css+Js计算时间差,返回相差的天/时/分/秒(从未来的一个日期时间到当前日期时间的差)。
Html部分 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><link rel"stylesheet" type"text/css" href"css/index.css" /><script src"js/index.js" t…...
mybatis项目启动报错:reader entry: ���� = v
问题再现 解决方案一 由于指定的VFS没有找,mybatis启用了默认的DefaultVFS,然后由于DefaultVFS的内部逻辑,从而导致了reader entry乱码。 去掉mybatis配置文件中关于别名的配置,然后在mapper.xml文件中使用完整的类名。 待删除的…...
【GIT版本控制】--什么是版本控制
一、为什么需要版本控制? 版本控制是在软件开发和许多其他领域中非常重要的工具,因为它解决了许多与协作、追踪更改和管理项目相关的问题。以下是一些主要原因,解释了为什么需要版本控制: 追踪更改历史: 版本控制系统允许您准确…...
ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端 + 小程序最新前端
人类小徐提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序,是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT,流量超级大,引流不要太简单!一键下单即可拥有自己的GPT࿰…...
GEE16: 区域日均降水量计算
Precipitation 1. 区域日均降水量计算2. 降水时间序列3. 降水数据年度时间序列对比分析 1. 区域日均降水量计算 今天分析一个计算区域日均降水量的方法: 数据信息: Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a…...
打开MySQL数据库
在命令行里输入mysql --version就可以查看: mysql -uroot -p之前设置的密码(不用输入)就可登录成功:...
玩转ChatGPT:DALL·E 3生成图像
一、写在前面 好久不更新咯,因为没有什么有意思的东西分享的。 今天更新,是因为GPT整合了自家的图像生成工具,名字叫作DALLE 3。 DALLE 3是OpenAI推出的一种生成图像的模型,它基于GPT-3架构进行训练,但是它的主要目…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
