探索 io_uring:理解高效异步 IO 的工作原理与实现细节

news2024/9/20 0:59:36

概述

io_uring 是一个 Linux 内核提供的高性能异步 I/O 框架,最初在 Linux 5.1 版本中引入。它的设计目标是解决传统的异步 I/O 模型(如 epoll 或者 POSIX AIO)在大规模 I/O 操作中效率不高的问题。

关键特点和优势包括:

  • 零拷贝操作:io_uring 允许应用程序直接将数据从用户空间提交给内核,而无需在用户空间和内核空间之间进行额外的数据拷贝操作,从而减少了系统调用的开销。

  • 高效的批处理操作:io_uring 支持批处理操作,即一次系统调用可以处理多个 I/O 请求,这降低了系统调用的次数,减少了上下文切换的开销。

  • 更低的延迟和更高的吞吐量:由于减少了数据拷贝和系统调用的开销,io_uring 在处理大量 I/O 请求时能够提供更低的延迟和更高的吞吐量,特别是在高并发和大数据量的场景下表现突出。

  • 灵活的事件通知机制:io_uring 使用 ring buffer 作为内核和用户空间之间的通信方式,通过 ring buffer 可以实现事件的批量通知,这种机制比传统的事件通知方式更为高效。

  • 适用于各种 I/O 操作:io_uring 不仅支持文件 I/O,还支持网络套接字的 I/O 操作,这使得它在网络应用中也能发挥重要作用。

过往IO接口的缺陷

同步IO接口

最原始的文件IO系统调用就是read,write。

read系统调用从文件描述符所指代的打开文件中读取数据。write系统调用将数据写入一个已打开的文件中。

在文件特定偏移处的IO是pread,pwrite。调用时可以指定位置进行文件IO操作,而非始于文件的当前偏移处,且他们不会改变文件的当前偏移量。

分散输入和集中输出(Scatter-Gather IO)是readv, writev,调用并非只对单个缓冲区进行读写操作,而是一次即可传输多个缓冲区的数据,免除了多次系统调用的开销,提高文件 I/O 的效率,特别是当需要读写多个连续或非连续的数据块时。

该机制使用一个数组iov定义了一组用来传输数据的缓冲区,一个整形数iovcnt指定iov的成员个数,其中,iov中的每个成员都是如下形式的数据结构。

struct iovec {
   void  *iov_base;    /* Starting address */
   size_t iov_len;     /* Number of bytes to transfer */
};

上述接口在读写IO时,系统调用会阻塞住等待,在数据读取或写入后才返回结果。同步导致的后果就是在阻塞的同时无法继续执行其他的操作,只能等待IO结果返回。存储场景中对性能的要求非常高,所以需要异步IO。

异步IO接口:AIO

Linux 的异步 IO(AIO,Asynchronous I/O)是一种高级的文件 IO 模型,允许应用程序在发起 IO 操作后不必等待操作完成,而是可以继续执行其他任务。这与传统的同步 IO 模型不同,后者在 IO 操作完成之前会阻塞应用程序的执行。

在这里插入图片描述

io_uring设计思路

解决“系统调用开销大”的问题

针对这个问题,考虑是否每次都需要系统调用。如果能将多次系统调用中的逻辑放到有限次数中来,就能将消耗降为常数时间复杂度。

解决“拷贝开销大”的问题

之所以在提交和完成事件中存在大量的内存拷贝,是因为应用程序和内核之间的通信需要拷贝数据,所以为了避免这个问题,需要重新考量应用与内核间的通信方式。我们发现,两者通信,不是必须要拷贝,通过现有技术,可以让应用与内核共享内存。

要实现核外与内核的零拷贝,最佳方式就是实现一块内存映射区域,两者共享一段内存,核外往这段内存写数据,然后通知内核使用这段内存数据,或者内核填写这段数据,核外使用这部分数据。因此,需要一对共享的ring buffer用于应用程序和内核之间的通信。

一块用于核外传递数据给内核,一块是内核传递数据给核外,一方只读,一方只写。

  • 提交队列SQ(submission queue)中,应用是IO提交的生产者,内核是消费者。
  • 完成队列CQ(completion queue)中,内核是IO完成的生产者,应用是消费者。

内核控制SQ ring的head和CQ ring的tail,应用程序控制SQ ring的tail和CQ ring的head

解决“API不友好”的问题

问题在于需要多个系统调用才能完成,考虑是否可以把多个系统调用合而为一。有时候,将多个类似的函数合并并通过参数区分不同的行为是更好的选择,而有时候可能需要将复杂的函数分解为更简单的部分来进行重构。

如果发现函数中的某一部分代码可以独立出来成为一个单独的函数,可以先进行这样的提炼,然后再考虑是否需要进一步使用参数化方法重构。

io_uring实现原理

整体架构

在这里插入图片描述SQE:提交队列项,表示IO请求。

CQE:完成队列项,表示IO请求结果。

SQ:Submission Queue,提交队列,用于存储SQE的数组。

CQ:Completion Queue,完成队列,用于存储CQE的数组。

SQ Ring:SQ环形缓冲区,包含SQ,头部索引(head),尾部索引(tail),队列大小等信息。

CQ Ring:CQ环形缓冲区,包含SQ,头部索引(head),尾部索引(tail),队列大小等信息。

SQ线程:内核辅助线程,用于从SQ队列获取SQE,并提交给内核处理,并将IO请求结果生成CQE存储在CQ队列。

io_uring、io_rings结构

io_uring定义了一个环形队列,用于管理异步 I/O 操作的发送队列 (Submission Queue, SQ) 和完成队列 (Completion Queue, CQ)。

io_rings扩展了 struct io_uring,提供了更多的管理和控制异步 I/O 操作的功能。

struct io_uring {
 u32 head ____cacheline_aligned_in_smp;
 u32 tail ____cacheline_aligned_in_smp;
};
struct io_rings {
 struct io_uring  sq, cq;
 u32   sq_ring_mask, cq_ring_mask;
 u32   sq_ring_entries, cq_ring_entries;
 u32   sq_dropped;
 u32   sq_flags;
 u32   cq_flags;
 u32   cq_overflow;
 struct io_uring_cqe cqes[] ____cacheline_aligned_in_smp;
};

struct io_uring 包含了两个成员变量 head 和 tail,它们分别表示环形队列的头部和尾部。

____cacheline_aligned_in_smp 是一个宏,用于确保 head 和 tail 在多处理器环境下的缓存行对齐。

sq 和 cq 是 struct io_uring 类型的变量,分别表示异步I/O的发送队列和完成队列。

sq_ring_mask 和 cq_ring_mask 是掩码,用于将头部和尾部的偏移量转换为有效的索引。

sq_ring_entries 和 cq_ring_entries 分别表示发送队列和完成队列的环形缓冲区大小,必须是2的幂次方。

sq_dropped 表示由于应用程序存储了无效索引而由内核丢弃的无效条目数量。

sq_flags 和 cq_flags 分别是运行时的标志,用于异步I/O操作的状态控制。

cq_overflow 表示由于完成队列已满而丢失的完成事件数量。

cqes[] 是一个包含完成事件的环形缓冲区,类型为 struct io_uring_cqe,并确保缓存行对齐。

Submission Queue Entry单元数据结构

Submission Queue(下称SQ)是提交队列,核外写内核读的地方。Submission Queue Entry(下称SQE),即提交队列中的条目,队列由一个个条目组成。

描述一个SQE会复杂很多,不仅是因为要描述更多的信息,也是因为可扩展性这一设计原则。

struct io_uring_sqe {
 __u8 opcode;  /* type of operation for this sqe */
 __u8 flags;  /* IOSQE_ flags */
 __u16 ioprio;  /* ioprio for the request */
 __s32 fd;  /* file descriptor to do IO on */
 union {
  __u64 off; /* offset into file */
  __u64 addr2;
 };
 union {
  __u64 addr; /* pointer to buffer or iovecs */
  __u64 splice_off_in;
 };
 __u32 len;  /* buffer size or number of iovecs */
 union {
  __kernel_rwf_t rw_flags;
  __u32  fsync_flags;
  __u16  poll_events; /* compatibility */
  __u32  poll32_events; /* word-reversed for BE */
  __u32  sync_range_flags;
  __u32  msg_flags;
  __u32  timeout_flags;
  __u32  accept_flags;
  __u32  cancel_flags;
  __u32  open_flags;
  __u32  statx_flags;
  __u32  fadvise_advice;
  __u32  splice_flags;
 };
 __u64 user_data; /* data to be passed back at completion time */
 union {
  struct {
   /* pack this to avoid bogus arm OABI complaints */
   union {
    /* index into fixed buffers, if used */
    __u16 buf_index;
    /* for grouped buffer selection */
    __u16 buf_group;
   } __attribute__((packed));
   /* personality to use, if used */
   __u16 personality;
   __s32 splice_fd_in;
  };
  __u64 __pad2[3];
 };
};
  • opcode是操作码,例如IORING_OP_READV,代表向量读。
  • flags是标志位集合。
  • ioprio是请求的优先级,对于普通的读写,具体定义可以参照ioprio_set(2),
  • fd是这个请求相关的文件描述符
  • off是操作的偏移量
  • addr表示这次IO操作执行的地址,如果操作码opcode描述了一个传输数据的操作,这个操作是基于向量的,addr就指向struct iovec的数组首地址,这和前文所说的preadv系统调用是一样的用法;如果不是基于向量的,那么addr必须直接包含一个地址,len这里(非向量场景)就表示这段buffer的长度,而向量场景就表示iovec的数量。
  • union,表示一系列针对特定操作码opcode的一些flag。
  • user_data是各操作码opcode通用的,仅仅只是拷贝给完成事件completion event
  • 结构的最后用于内存对齐,对齐到64字节。

这就是核外往内核填写的Submission Queue Entry的数据结构,准备好这样的一个数据结构,将它写到对应的sqes所在的内存位置,然后再通知内核去对应的位置取数据,这样就完成了一次数据交接。

Completion Queue Entry单元数据结构

Completion Queue(下称CQ)是完成队列,内核写核外读的地方。Completion Queue Entry(下称CQE),即完成队列中的条目,队列由一个个条目组成。

描述一个CQE就简单得多。

/*
 * IO completion data structure (Completion Queue Entry)
 */
struct io_uring_cqe {
 __u64 user_data; /* sqe->data submission passed back */
 __s32 res;  /* result code for this event */
 __u32 flags;
};
  • user_data就是sqe发送时核外填写的,在完成时回传。典型用法是将其作为指针,指向原始请求的数据结构或上下文信息。这样,当完成处理时,可以通过 user_data 来识别并处理对应的请求。
  • res 字段用于存储与这个事件相关的结果码或返回值。它通常代表了系统调用的返回值。
  • flags 是一个标志位集合,用于提供关于完成事件的附加信息。

上下文结构io_ring_ctx

io_ring_ctx是贯穿整个io_uring所有过程的数据结构,基本上在任何位置只需要你能持有该结构就可以找到任何数据所在的位置,例如,sq_sqes就是指向io_uring_sqe结构的指针,指向SQEs的首地址。

其提供了完整的上下文信息,包括了对提交给 io_uring 的操作请求的管理、等待队列、文件管理、权限控制、同步处理等多方面的功能。

struct io_ring_ctx {
 struct {
  struct percpu_ref refs;
 } ____cacheline_aligned_in_smp;

 struct {
  unsigned int  flags;
  unsigned int  compat: 1;
  unsigned int  limit_mem: 1;
  unsigned int  cq_overflow_flushed: 1;
  unsigned int  drain_next: 1;
  unsigned int  eventfd_async: 1;
  unsigned int  restricted: 1;

  /*
   * Ring buffer of indices into array of io_uring_sqe, which is
   * mmapped by the application using the IORING_OFF_SQES offset.
   *
   * This indirection could e.g. be used to assign fixed
   * io_uring_sqe entries to operations and only submit them to
   * the queue when needed.
   *
   * The kernel modifies neither the indices array nor the entries
   * array.
   */
  u32   *sq_array;
  unsigned  cached_sq_head;
  unsigned  sq_entries;
  unsigned  sq_mask;
  unsigned  sq_thread_idle;
  unsigned  cached_sq_dropped;
  unsigned  cached_cq_overflow;
  unsigned long  sq_check_overflow;

  struct list_head defer_list;
  struct list_head timeout_list;
  struct list_head cq_overflow_list;

  wait_queue_head_t inflight_wait;
  struct io_uring_sqe *sq_sqes;
 } ____cacheline_aligned_in_smp;

 struct io_rings *rings;

 /* IO offload */
 struct io_wq  *io_wq;

 /*
  * For SQPOLL usage - we hold a reference to the parent task, so we
  * have access to the ->files
  */
 struct task_struct *sqo_task;

 /* Only used for accounting purposes */
 struct mm_struct *mm_account;

#ifdef CONFIG_BLK_CGROUP
 struct cgroup_subsys_state *sqo_blkcg_css;
#endif

 struct io_sq_data *sq_data; /* if using sq thread polling */

 struct wait_queue_head sqo_sq_wait;
 struct wait_queue_entry sqo_wait_entry;
 struct list_head sqd_list;

 /*
  * If used, fixed file set. Writers must ensure that ->refs is dead,
  * readers must ensure that ->refs is alive as long as the file* is
  * used. Only updated through io_uring_register(2).
  */
 struct fixed_file_data *file_data;
 unsigned  nr_user_files;

 /* if used, fixed mapped user buffers */
 unsigned  nr_user_bufs;
 struct io_mapped_ubuf *user_bufs;

 struct user_struct *user;

 const struct cred *creds;

#ifdef CONFIG_AUDIT
 kuid_t   loginuid;
 unsigned int  sessionid;
#endif

 struct completion ref_comp;
 struct completion sq_thread_comp;

 /* if all else fails... */
 struct io_kiocb  *fallback_req;

#if defined(CONFIG_UNIX)
 struct socket  *ring_sock;
#endif

 struct idr  io_buffer_idr;

 struct idr  personality_idr;

 struct {
  unsigned  cached_cq_tail;
  unsigned  cq_entries;
  unsigned  cq_mask;
  atomic_t  cq_timeouts;
  unsigned long  cq_check_overflow;
  struct wait_queue_head cq_wait;
  struct fasync_struct *cq_fasync;
  struct eventfd_ctx *cq_ev_fd;
 } ____cacheline_aligned_in_smp;

 struct {
  struct mutex  uring_lock;
  wait_queue_head_t wait;
 } ____cacheline_aligned_in_smp;

 struct {
  spinlock_t  completion_lock;

  /*
   * ->iopoll_list is protected by the ctx->uring_lock for
   * io_uring instances that don't use IORING_SETUP_SQPOLL.
   * For SQPOLL, only the single threaded io_sq_thread() will
   * manipulate the list, hence no extra locking is needed there.
   */
  struct list_head iopoll_list;
  struct hlist_head *cancel_hash;
  unsigned  cancel_hash_bits;
  bool   poll_multi_file;

  spinlock_t  inflight_lock;
  struct list_head inflight_list;
 } ____cacheline_aligned_in_smp;

 struct delayed_work  file_put_work;
 struct llist_head  file_put_llist;

 struct work_struct  exit_work;
 struct io_restriction  restrictions;
};

io_uring关键流程

使用上,大体分为准备、提交、收割过程。以下是几个io_uring相关的系统调用:

#include <linux/io_uring.h>

int io_uring_setup(u32 entries, struct io_uring_params *p);

int io_uring_enter(unsigned int fd, unsigned int to_submit,
                   unsigned int min_complete, unsigned int flags,
                   sigset_t *sig);
                   
int io_uring_register(unsigned int fd, unsigned int opcode,
                      void *arg, unsigned int nr_args);

创建io_uring对象

在这里插入图片描述

用户程序通过io_uring_setup系统调用创建和初始化io_uring对象,io_uring对象对应于struct io_ring_ctx结构体对象。

io_uring_setup主要工作:

  • 创建struct io_ring_ctx对象并初始化。

  • 创建struct io_urings对象并初始化,注意此时已完成CQ和所有CQE创建。

  • 创建SQ和所有SQE并初始化。

  • 如果struct io_ring_ctx对象flags参数设置IORING_SETUP_SQPOLL,则创建SQ线程。

fd绑定io_uring对象

在这里插入图片描述

已创建的io_ring对象需要和fd进行绑定, 以便能够通过fd找到io_uring对象,创建一个新的file,file private_data成员指向io_ring对象,申请一个未使用的文件描述符fd,fd映射至file,并存储在进程已打开文件表中。

注意:mmap内存映射需要用到该fd。

io_uring对象内存映射

在这里插入图片描述
通过io_uring_setup系统调用创建完io_uring对象后,用户程序还不能直接访问io_uring对象,此时用户程序需要通过mmap函数将io_uring对象SQ,CQ以及head和tail等相关内存空间映射出来。

完成mmap内存映射后,io_uring对象相关内存空间成为用户程序和内核共享内存空间,用户程序可以直接访问io_uring对象,不再需要通过执行系统调用访问,很大程度上提高了系统性能。

提交IO请求

在这里插入图片描述
SQ Ring中有两个成员head(头部索引)和tail(尾部索引),头部索引指向SQ队列第一个已提交IO请求,尾部索引指向SQ下一个空闲SQE。

提交IO请求,只需要将tail指向的SQE填充IO请求信息,并让tail自增1,指向下一个空闲SQE。

注意:head和tail不是直接指向SQ数组,而是需要通过head&mask和tail &mask操作指向SQ数组,mask数组为数组长度减1,因为数组有固定大小,所以需要通过&mask方式防止越界访问数组,这种方式可以让数组形成一个环形缓冲区。

等待IO请求完成

在这里插入图片描述

IO请求的处理有两种方式:SQ线程从SQ队列中获取SQE(已提交IO请求),并发送给内核处理。或者用户程序通过io_uring_enter系统调用从SQ队列中获取SQE(已提交IO请求),并发送给内核处理。

从SQ队列获取SQE只需要获取SQ Ring head指向的SQE,并让head自增指向下一个SQE即可。内核处理完IO请求后,SQ线程会申请CQ Ring tail指向的CQE存储IO请求结果,tail自增1指向下一个空闲CQE。

获取IO请求结果

在这里插入图片描述
用户程序通过判断CQ Ring head和tail之间的差值,可以检测到是否有已完成IO请求,如果有已完成IO请求(CQE),获取CQ Ring head指向CQE,获取IO请求结果。

释放已完成IO请求

释放已完成IO请求只需要将CQ Ring head指针自增1指向下一个CQE即可,这样做的目的是防止重复获取IO请求结果。

io_uring高效的原因

核心原因:io_uring通过mmap内存映射大大减少了系统调用,在高并发场景下,系统调用非常损耗系统性能。

减少拷贝:io_uring通过共享内存减少用户程序和内核数据拷贝。

批量操作:io_uring支持批量操作,一次性可以提交多个I/O请求,减少系统调用的次数,提高系统效率。

无锁环形队列:io_uring采用无锁队列实现用户程序与内核对共享内存的高效访问。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动框架——CMSIS第一部分 RTE驱动框架介绍

一、介绍CMISIS 什么是CMSIS&#xff08;cortex microcontrol software interface standard一种软件标准接口&#xff09;&#xff0c;官网地址&#xff1a;https://arm-software.github.io/CMSIS_6/latest/General/index.html 包含的core、driver、RTOS、dsp、nn等部分&…

【C++】位运算:消失的两个数字

1.题目 2.算法思路 众所周知&#xff1a;相同的两个数字异或在一起等于0&#xff0c;而异或运算又遵循交换律和结合律。 所以这道题目的思路就有了&#xff1a; 1.可以将数组和1~N中的所有整数全部异或在一起&#xff0c;就可以得到缺失的两个数&#xff08;a,b&#xff09;…

使用Python创建和扫描二维码

二维码&#xff08;Quick Response code&#xff09;已成为在物理和数字领域之间架起桥梁的多功能工具。从分享联系信息和网站链接到促进支付和跟踪库存&#xff0c;二维码在各个行业中找到了应用。通过利用Python的功能&#xff0c;用户可以自动化生成个性化的二维码&#xff…

基于SpringBoot+Vue的财务管理系统(带1w+文档)

基于SpringBootVue的财务管理系统(带1w文档) 基于SpringBootVue的财务管理系统(带1w文档) 财务管理系统的开发运用java技术、springboot框架&#xff0c;MIS的总体思想&#xff0c;以及Mysql等技术的支持下共同完成了该系统的开发&#xff0c;实现了财务管理的信息化&#xff0…

C语言函数:编程世界的魔法钥匙(2)-学习笔记

引言 注&#xff1a;由于这部分内容比较抽象&#xff0c;而小编我又是一个刚刚进入编程世界的计算机小白&#xff0c;所以我的介绍可能会有点让人啼笑皆非。希望大家多多包涵&#xff01;万分感谢&#xff01;待到小编我学有所成&#xff0c;一定会把这块知识点重新介绍一遍&a…

VB利用API调用系统的通用颜色对话框

Option Explicit 在窗体上添加一个Command1按钮控件 Private Type ChooseColor lStructSize As Long hwndOwner As Long hInstance As Long rgbResult As Long lpCustColors As String Flags As Long lCustData As Long lpfnHook As Long lpTemplateName As String End Type 该…

pcie拓扑结构与层次结构

一 拓扑结构 PCIE 总线与总线共享式通讯方式的 PCI 不同&#xff0c;PCIE 由点到点的链路组成&#xff0c;并采用树形拓扑结构PCIE 拓扑结构体系由 CPU、根复合体&#xff08;RootComplex&#xff0c;RC&#xff09;、端点设备&#xff08;Endpoint&#xff0c;EP&#xff09;…

Python入门------pycharm加载虚拟环境

pycharm虚拟环境配置&#xff1a; 在按照前面的办法&#xff0c;配置好虚拟环境后,如果我们需要到虚拟环境开发&#xff0c;就需要给编译器配置虚拟环境 1.打开编译器&#xff0c;点击右下角的interpreter选项 2. 点击ADD Interpreter,添加虚拟环境 3. 因为我们使用的是原始…

【LeetCode】二叉树的最大深度

目录 一、题目二、解法完整代码 一、题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 示例 2&#x…

vue2学习笔记9 - 通过观察vue实例中的data,理解Vue中的数据代理

接着上一节&#xff0c;学一学vue中的数据代理。学vue这几天&#xff0c;最大的感受就是&#xff0c;名词众多&#xff0c;听得发懵。。不过&#xff0c;深入理解之后&#xff0c;其实说得都是一回事。 在Vue中&#xff0c;数据代理是指在实例化Vue对象时&#xff0c;将data对…

【C++高阶】精通AVL树:全面剖析与深度学习

目录 &#x1f680; 前言一&#xff1a; &#x1f525; AVL树的性质二&#xff1a; &#x1f525; AVL树节点的定义三&#xff1a; &#x1f525; AVL树的插入四&#xff1a; &#x1f525; AVL树的平衡调整&#xff08;附动图&#xff09; 五&#xff1a;&#x1f525; AVL树的…

防御保护课-防火墙接口配置实验

一、实验拓扑 &#xff08;我做实验用的图如下&#xff09; 二、实验要求 1.防火墙向下使用子接口分别对应生产区和办公区 2.所有分区设备可以ping通网关 三、实验思路 配IP&#xff1b; 划分vlan并配置vlan&#xff1b; 配置路由和安全策略。 四、实验配置 1、画图并…

C++与lua联合编程

C与lua联合编程 一、环境配置二、lua基本语法1.第一个lua和C程序2.基本数据类型和变量2.1 Nil2.2 Booleans2.3 Numbers2.4 String(最常用) 3. 字符串处理3.1 错误处理3.2 字符串长度:string.len3.3 字符串子串 :string.sub3.4 字符串查找: string.find3.5字符串替换: string.gs…

Evil-WinRM一键测试主机安全情况(KALI工具系列四十四)

目录 1、KALI LINUX 简介 2、Evil-WinRM 3、信息收集 3.1 目标IP 3.2 kali的IP 4、操作步骤 4.1 用户访问 4.2 使用哈希值 4.3 文件处理 5、总结 1、KALI LINUX 简介 Kali Linux 是一个功能强大、多才多艺的 Linux 发行版 &#xff0c;广泛用于网络安全社区。它具有全…

swiftui使用ScrollView实现左右滑动和上下滑动的效果,仿小红书页面

实现的效果如果所示&#xff0c;顶部的关注用户列表可以左右滑动&#xff0c;中间的内容区域是可以上下滚动的效果&#xff0c;点击顶部的toolbar也可以切换关注/发现/附近不同页面&#xff0c;实现翻页效果。 首页布局 这里使用了NavigationStack组件和tabViewStyle样式配置…

在项目服务器部署git 并实现自动提交

以下场景适合在服务器当中使用git 方便提交代码&#xff0c;同时不需要外部的git仓库&#xff08;码云gitee或者github作为管理平台&#xff09;。依靠服务器本身ssh 连接协议做为git提交的地址&#xff0c;同时利用钩子自动同步项目代码 首先下载git sudo apt update sudo a…

Linux最直观的性能分析(热点分析)-编译perf并生成火焰图

本文先介绍了linux下perf工具的使用场景&#xff0c;然后对命令行安装和源码编译安装两种方式做了说明&#xff0c;接下来通过最简单的perf top命令给出perf的直观印象&#xff0c;最后通过perf record生成火焰图的方式说明如何发现进程中的函数热点。 一、perf工具介绍 per…

00 JavaWeb

学习资料&#xff1a;B站视频-黑马程序员JavaWeb基础教程 文章目录 JavaWeb1、JavaWeb简介2、 JavaWeb主要内容3、JavaWeb技术栈4、JavaWeb课程安排5、Web核心课程安排 JavaWeb 1、JavaWeb简介 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www)&#xff0c;能够通过浏…

C++: 链表回文结构/分割链表题解

目录 1.链表的回文结构 分析 代码 2.链表分割 ​编辑分析 代码 1.链表的回文结构 分析 这道题的难点是空间复杂度为O&#xff08;1&#xff09; 结合逆置链表找到链表的中间节点就可以解决了。 先找到链表的中间节点&#xff0c;再对中间节点的下一个节点进行逆置&…

macbook pro大模型推理

安装与配置 参考github ollama 链接安装ollama。安装完成后,安装常用的模型,下载速度超快。 性能测试 在进行实际测试之前,我首先对模型进行了预处理,以确保其在 M3 Max 上能够高效运行。测试过程中,我主要关注了以下几个方面: 模型加载时间 加载大型模型通常需要较…