15.Netty源码之EventLoop


highlight: arduino-light

Netty配置主从Reactor模式

通过将NioServerSocketChannel绑定到了bossGroup。

将NioServerSocketChannel接收到请求创建的SocketChannel放入workerGroup。

将2个不同的SocketChannel绑定到2个不同的Group完成了主从 Reactor 模式。

分配NIOEventLoop的规则

根据不同策略给Channel分配的规则不同。

1.普通:递增取模

2.高级:executors总数是2的幂次方才会使用位运算 效率更高

如何跨平台创建选择器

在创建NioEventLoopGroup的时候,会根据指定的线程数,循环遍历创建NioEventLoop。

java @Override    protected EventLoop newChild(Executor executor, Object... args) throws Exception {        return new NioEventLoop(this, executor, (SelectorProvider) args[0],           ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);   }

java NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {        super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);        if (selectorProvider == null) {            throw new NullPointerException("selectorProvider");       }        if (strategy == null) {            throw new NullPointerException("selectStrategy");       }        provider = selectorProvider;     //使用selectorProvider创建selector 属于EventLoop的成员变量        final SelectorTuple selectorTuple = openSelector();        selector = selectorTuple.selector;        unwrappedSelector = selectorTuple.unwrappedSelector;        selectStrategy = strategy;   }

那么这个selectorProvider是从哪来的呢?原来是构造NioEventLoopGroup的时候获取的。

java public NioEventLoopGroup(int nThreads, Executor executor) { this(nThreads, executor, SelectorProvider.provider()); }

SelectorProvider.provider()最终会走到sun.nio.ch.DefaultSelectorProvider.create(),查看create()方法,发现该处为Netty的精明之处,调用了JDK自带的DefaultSelectorProvider类,该类会根据不同平台实例化不同的类。策略模式的体现。

下面看下在NIO中Selector的open方法:

java public static Selector open() throws IOException { return SelectorProvider.provider().openSelector(); }

这里使用了SelectorProvider去创建一个Selector,看下provider方法的实现:

```java public static SelectorProvider provider() { synchronized (lock) { if (provider != null) return provider; return AccessController.doPrivileged( new PrivilegedAction () { public SelectorProvider run() { if (loadProviderFromProperty()) return provider; if (loadProviderAsService()) return provider;

//默认走到了这里provider = sun.nio.ch.DefaultSelectorProvider.create();return provider;}});
}

} ```

看下sun.nio.ch.DefaultSelectorProvider.create()方法,该方法在不同的操作系统中的代码是不同的,在windows中的实现如下:

java public static SelectorProvider create() { return new WindowsSelectorProvider(); }

在Mac OS中的实现如下:

java public static SelectorProvider create() { return new KQueueSelectorProvider(); }

在linux中的实现如下:

java public static SelectorProvider create() { String str = (String)AccessController.doPrivileged(new GetPropertyAction("os.name")); if (str.equals("SunOS")) return createProvider("sun.nio.ch.DevPollSelectorProvider"); if (str.equals("Linux")) return createProvider("sun.nio.ch.EPollSelectorProvider"); return new PollSelectorProvider(); }

我们看到create方法中是通过区分操作系统来返回不同的Provider的。其中SunOs就是Solaris返回的是DevPollSelectorProvider,对于Linux,返回的Provder是EPollSelectorProvider,其余操作系统,返回的是PollSelectorProvider。

Reactor的运行步骤

介绍了上述三种 Reactor 线程模型,再结合它们各自的架构图,我们能大致总结出 Reactor 线程模型运行机制的四个步骤,分别为连接注册事件轮询事件分发任务处理,如下图所示。

image.png

  • 连接注册:Channel 建立后,将fd注册至Reactor线程中的Selector选择器。
  • 事件轮询:轮询 Selector 选择器中已注册的所有 Channel 的 I/O 事件。
  • 事件分发:为准备就绪的 I/O 事件分配相应的处理线程。
  • 任务处理:Reactor 线程还负责任务队列中的非 I/O 任务,每个 Worker 线程从各自维护的任务队列中取出任务异步执行。

以上介绍了 Reactor 线程模型的演进过程和基本原理,Netty 也同样遵循 Reactor 线程模型的运行机制,下面我们来了解一下 Netty 是如何实现 Reactor 线程模型的。

事件轮询

EventLoop其实并不是 Netty 独有的,它是一种事件等待和处理的程序模型,可以解决多线程资源消耗高的问题。

例如 Node.js 就采用了 EventLoop 的运行机制,不仅占用资源低,而且能够支撑了大规模的流量访问。

下图展示了 EventLoop 通用的运行模式。

每当事件发生时,应用程序都会将产生的事件放入事件队列当中,然后 EventLoop 会轮询从队列中取出事件执行

或者将事件分发给相应的事件监听者执行。

Netty使用的是局部串行,全局并行的方式。

事件执行的方式通常分为立即执行、延后执行、定期执行几种。

NioEventLoop#run源码

EventLoop 可以理解为 Reactor 线程模型的事件处理引擎。

每个 EventLoop 线程都维护一个 Selector 选择器和任务队列 taskQueue。

它主要负责处理 I/O 事件、普通任务和定时任务。

Netty 中推荐使用 NioEventLoop 作为实现类,那么 Netty 是如何实现 NioEventLoop 的呢?

我们来看 NioEventLoop 最核心的 run() 方法源码,先了解 NioEventLoop 的实现结构。

java switch (selectStrategy.calculateStrategy (selectNowSupplier, hasTasks()))

首先,在 run()方法中,会通过选择策略(selectStrategy )来计算 switch 语句中的条件值。在计算的时候,会先通过 hasTasks() 方法来判断 taskQueue 和 tailQueue 中是否有任务等待被执行,如果有任务,则将调用 selectNow()方法从操作系统中来轮询网络 IO 事件;如果没有任务,则将调用 select(timeout)方法来轮询网络 IO 事件。这个应该好理解。如果有任务正在等待,那么应该使用无阻塞的 selectNow(),如果没有任务在等待,那么就可以使用带阻塞的 select 操作。

为什么要这样做呢?因为 Netty 中为了保证任务被及时执行,selectNow()方法是个非阻塞方法,如果操作系统中没有已经准备好的网络 IO 事件,那么就会立即返回,有已经准备好的网络 IO 事件,那么就会将这些网络 IO 事件查询出来并立马返回。而 select(timeout)方法也是从操作系统中轮询网络 IO 事件,但是它是一个阻塞方法,当 netty 中有任务等待被执行时,使用阻塞方法,显然会造成任务被执行不及时的问题。

如果selectStrategy计算出来的值为-1,那么就会执行到下面这一行代码。

java case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 轮询 I/O 事件 if (wakenUp.get()) { selector.wakeup(); }

这行代码首先会将 wakenUp 的值置为 false。wakenUp 字段表示的含义是是否需要唤醒 selector,在每次进行新的轮询时,都会将 wakenUp 设置为 false。然后调用 select()方法,从操作系统中轮询出来网络 IO 事件。

接着在 run()方法中会对 ioRatio 的值进行判断,ioRatio 的含义又是什么呢?在 Netty 中,NioEventLoop 每一次循环其实主要干两类事,一是处理网络 IO 事件,二是执行任务(包括普通任务和定时任务),但是处理这两类任务的所消耗的时间是不一样的。而且有些系统可能期望分配给处理网络 IO 事件的时间多一点,有些系统可能期望分配给处理任务的时间多一些,那么 netty 就需要提供一个变量来控制执行这两类事的所花的时间的占比,这个变量就是 ioRatio,翻译过来就是 IO 的时间占比。

默认情况下,ioRatio 的值为 50,即处理网络 IO 的时间和处理任务的时间各占一半。所以默认情况下,会进入到 else 语句块中,在 else 语句块中,先进行了网络 IO 的处理(processSelectedKeys()),然后进行任务的处理(runAllTasks(timeoutNanos))。

java final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); // 处理 I/O 事件 } finally { final long ioTime = System.nanoTime() - ioStartTime; // 处理完 I/O 事件,再处理异步任务队列 // (100 - ioRatio) / ioRatio = 1 // 也就是处理io的时间和处理任务的时间耗时是一样的 runAllTasks(ioTime * (100 - ioRatio) / ioRatio); }

(31条消息) Netty源码分析系列之NioEventLoop的执行流程_天堂的博客-CSDN博客

回过神来,我们前面在 register 的时候提交了 register 任务给 NioEventLoop,这是 NioEventLoop 接收到的第一个任务,所以这里会实例化 Thread 并且启动,然后进入到 NioEventLoop 中的 run 方法。

当然了,实际情况也有可能是,Channel 实例被 register 到一个已经启动线程的 NioEventLoop 实例中。

```java protected void run() { for (;;) { try { try { switch (selectStrategy.calculateStrategy (selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.BUSY_WAIT: case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 轮询 I/O 事件 if (wakenUp.get()) { selector.wakeup(); } default: } } catch (IOException e) { rebuildSelector0(); handleLoopException(e); continue; }

cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio;if (ioRatio == 100) {try {processSelectedKeys(); // 处理 I/O 事件} finally {runAllTasks(); // 处理所有任务}} else {final long ioStartTime = System.nanoTime();try {processSelectedKeys(); // 处理 I/O 事件} finally {final long ioTime = System.nanoTime() - ioStartTime;// 处理完 I/O 事件,再处理异步任务队列runAllTasks(ioTime * (100 - ioRatio) / ioRatio); }}} catch (Throwable t) {handleLoopException(t);}try {if (isShuttingDown()) {closeAll();if (confirmShutdown()) {return;}}} catch (Throwable t) {handleLoopException(t);}
}

} ```

上述源码的结构比较清晰,NioEventLoop 每次循环的处理流程都包含事件轮询 select()、事件处理 processSelectedKeys()、任务处理 runAllTasks() 几个步骤,是典型的 Reactor 线程模型的运行机制。

而且 Netty 提供了一个参数 ioRatio,可以调整 I/O 事件处理和任务处理的时间比例。下面我们将着重从事件处理任务处理两个核心部分出发,详细介绍 Netty EventLoop 的实现原理。

processSelectedKeys

processSelectedKeys主要是IO事件处理

image.png

结合 Netty 的整体架构,看下 EventLoop 的事件流转图。

NioEventLoop 的事件处理机制采用的是无锁串行化的设计思路

BossEventLoopGroupWorkerEventLoopGroup 包含一个或者多个 NioEventLoop。BossEventLoopGroup 负责监听客户端的 Accept 事件,当事件触发时,将事件注册至 WorkerEventLoopGroup 中的一个 NioEventLoop 上。

每新建一个 Channel, 只选择一个 NioEventLoop 与其绑定。所以说 Channel 生命周期的所有事件处理都是线程独立的,不同的 NioEventLoop 线程之间不会发生任何交集。

NioEventLoop 完成io数据读取后,会调用绑定的 ChannelPipeline 进行事件传播,ChannelPipeline 也是线程安全的,数据会被传递到 ChannelPipeline 的第一个 ChannelHandler 中。数据处理完成后,将加工完成的数据再传递给下一个 ChannelHandler,整个过程是串行化执行,不会发生线程上下文切换的问题。

NioEventLoop 无锁串行化的设计不仅使系统吞吐量达到最大化,而且降低了用户开发业务逻辑的难度,不需要花太多精力关心线程安全问题。

虽然单线程执行避免了线程切换,但是它的缺陷就是不能执行时间过长的 I/O 操作,一旦某个 I/O 事件发生阻塞,那么后续的所有 I/O 事件都无法执行,甚至造成事件积压。

在使用 Netty 进行程序开发时,我们一定要对 ChannelHandler 的实现逻辑有充分的风险意识。

JDK中Epoll空轮询Bug

NioEventLoop 线程的可靠性至关重要,一旦 NioEventLoop 发生阻塞或者陷入空轮询,就会导致整个系统不可用。

在 JDK 中, Epoll 的实现是存在漏洞的,即使 Selector 轮询的事件列表为空,NIO 线程一样可以被唤醒,导致 CPU 100% 占用。这就是臭名昭著的 JDK epoll 空轮询的 Bug。

Netty 作为一个高性能、高可靠的网络框架,需要保证 I/O 线程的安全性。

那么它是如何解决 JDK epoll空轮询的Bug 呢?

实际上 Netty 并没有从根源上解决该问题,而是巧妙地规避了这个问题。

我们抛开其他细枝末节,直接定位到事件轮询 select() 方法中的最后一部分代码,一起看下 Netty 是如何解决 epoll 空轮询的 Bug。

Netty中的解决思路:

对Selector()方法中的阻塞定时 select(timeMIllinois)操作的 次数进行统计,每完成一次select操作进行一次计数,若在循环周期内 发生N次空轮询,如果N值大于BUG阈值(默认为512),就进行空轮询BUG处理。 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的 Selector上,并将原来的Selector关闭。 https://blog.csdn.net/qq_41884976/article/details/91913820

java select方法分三个部分: //第一部分:超时处理逻辑 //第二部分:定时阻塞select(timeMillins) //第三部分: 解决空轮询 BUG long time = System.nanoTime(); //当前时间 - 循环开始时间 >= 定时select的时间timeoutMillis,说明已经执行过一次阻塞select() if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) { //说明发生过一次阻塞式轮询 重置次数 selectCnt = 1; } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { // 如果空轮询的次数大于空轮询次数阈值 SELECTOR_AUTO_REBUILD_THRESHOLD(512) //1.首先创建一个新的Selecor //2.将旧的Selector上面的键及其一系列的信息放到新的selector上面。 selector = selectRebuildSelector(selectCnt); selectCnt = 1; break; }

Netty 提供了一种检测机制判断线程是否可能陷入空轮询,具体的实现方式如下:

  1. 每次执行 Select 操作之前记录当前时间 currentTimeNanos。
  2. time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos,如果事件轮询的持续时间大于等于 timeoutMillis,那么说明是正常的,否则表明阻塞时间并未达到预期,可能触发了空轮询的 Bug。
  3. Netty 引入了计数变量 selectCnt。在正常情况下,selectCnt 会重置,否则会对 selectCnt 自增计数。当 selectCnt 达到 SELECTORAUTOREBUILD_THRESHOLD(默认512) 阈值时,会触发重建 Selector 对象。

Netty 采用这种方法巧妙地规避了 JDK Bug。异常的 Selector 中所有的 SelectionKey 会重新注册到新建的 Selector 上,重建完成之后异常的 Selector 就可以废弃了。

runAllTasks:任务处理

NioEventLoop 不仅负责处理 I/O 事件,还要兼顾执行任务队列中的任务。

任务队列遵循 FIFO 规则,可以保证任务执行的公平性。NioEventLoop 处理的任务类型基本可以分为三类。

普通任务

通过 NioEventLoop 的 execute() 方法向任务队列 taskQueue 中添加任务。例如 Netty 在写数据时会封装 WriteAndFlushTask 提交给 taskQueue。

taskQueue 的实现类是多生产者单消费者队列 MpscChunkedArrayQueue,在多线程并发添加任务时,可以保证线程安全。

普通任务代码示例

```java /* 说明 1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范) 2. 这时我们自定义一个Handler , 才能称为一个handler */ public class NettyServerHandler extends ChannelInboundHandlerAdapter {

//读取数据实际(这里我们可以读取客户端发送的消息)
/*
1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
2. Object msg: 就是客户端发送的数据 默认Object*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

//比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交到该channel 对应的NIOEventLoop 的 taskQueue中

//解决方案1 用户程序自定义的普通任务//会有1个判断ctx.channel().eventLoop().execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5 * 1000);ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));System.out.println("channel code=" + ctx.channel().hashCode());} catch (Exception ex) {System.out.println("发生异常" + ex.getMessage());}}});}//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));
}//处理异常, 一般是需要关闭通道@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();
}

}

参考链接:https://blog.csdn.net/cold_play/article/details/104343549 ```

定时任务

通过调用 NioEventLoop 的 schedule() 方法向定时任务队列 scheduledTaskQueue 添加一个定时任务,用于周期性执行该任务。例如,心跳消息发送等。定时任务队列 scheduledTaskQueue 采用优先队列 PriorityQueue 实现。

```java /* 说明 1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范) 2. 这时我们自定义一个Handler , 才能称为一个handler */ public class NettyServerHandler extends ChannelInboundHandlerAdapter {

//读取数据实际(这里我们可以读取客户端发送的消息)
/*
1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
2. Object msg: 就是客户端发送的数据 默认Object*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的//NIOEventLoop 的 taskQueue中,//解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中ctx.channel().eventLoop().schedule(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5 * 1000);ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));System.out.println("channel code=" + ctx.channel().hashCode());} catch (Exception ex) {System.out.println("发生异常" + ex.getMessage());}}}, 5, TimeUnit.SECONDS);}//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));
}//处理异常, 一般是需要关闭通道@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();
}

} 参考链接:https://blog.csdn.net/cold_play/article/details/104343549 ```

尾部队列

tailTasks 相比于普通任务队列优先级较低,在每次执行完 taskQueue 中任务后会去获取尾部队列中任务执行。尾部任务并不常用,主要用于做一些收尾工作,例如统计事件循环的执行时间、监控信息上报等。

runAllTasks源码分析

下面结合任务处理 runAllTasks 的源码结构,分析下 NioEventLoop 处理任务的逻辑,源码实现如下:

java protected boolean runAllTasks(long timeoutNanos) { // 1. 合并定时任务到普通任务队列 fetchFromScheduledTaskQueue(); // 2. 从普通任务队列中取出任务 Runnable task = pollTask(); if (task == null) { afterRunningAllTasks(); return false; } // 3. 计算任务处理的超时时间 final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos; long runTasks = 0; long lastExecutionTime; for (;;) { // 4. 安全执行任务 safeExecute(task); runTasks ++; // 5. 每执行 64 个任务检查一下是否超时 if ((runTasks & 0x3F) == 0) { lastExecutionTime = ScheduledFutureTask.nanoTime(); if (lastExecutionTime >= deadline) { break; } } task = pollTask(); if (task == null) { lastExecutionTime = ScheduledFutureTask.nanoTime(); break; } } // 6. 收尾工作 afterRunningAllTasks(); this.lastExecutionTime = lastExecutionTime; return true; }

我在代码中以注释的方式标注了具体的实现步骤,可以分为 6 个步骤。

java 1. fetchFromScheduledTaskQueue 函数:将定时任务从 scheduledTaskQueue 中取出,聚合放入普通任务队列 taskQueue 中,只有定时任务的截止时间小于当前时间才可以被合并。 2. 从普通任务队列 taskQueue 中取出任务。 3. 计算任务执行的最大超时时间。 4. safeExecute 函数:安全执行任务,实际直接调用的 Runnable 的 run() 方法。 5. 每执行 64 个任务进行超时时间的检查,如果执行时间大于最大超时时间,则立即停止执行任务,避免影响下一轮的 I/O 事件的处理。 6. 最后获取尾部队列中的任务执行。

EventLoop 最佳实践

在日常开发中用好 EventLoop 至关重要,这里结合实际工作中的经验给出一些 EventLoop 的最佳实践方案。

  1. 网络连接建立过程中三次握手、安全认证的过程会消耗不少时间。这里建议采用 Boss 和 Worker 两个 EventLoopGroup,有助于分担 Reactor 线程的压力。
  2. 由于 Reactor 线程模式适合处理耗时短的任务场景,对于耗时较长的 ChannelHandler 可以考虑维护一个业务线程池,将编解码后的数据封装成 Task 进行异步处理,避免 ChannelHandler 阻塞而造成 EventLoop 不可用。
  3. 如果业务逻辑执行时间较短,建议直接在 ChannelHandler 中执行。例如编解码操作,这样可以避免过度设计而造成架构的复杂性。
  4. 不宜设计过多的 ChannelHandler。对于系统性能和可维护性都会存在问题,在设计业务架构的时候,需要明确业务分层和 Netty 分层之间的界限。不要一味地将业务逻辑都添加到 ChannelHandler 中。

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

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

相关文章

论文精读之BERT

目录 1.摘要&#xff08;Abstract&#xff09; 2.引言&#xff08;Introduction&#xff09;&#xff1a; 3.结论&#xff08;Conlusion&#xff09;&#xff1a; 4.BERT模型算法: 5.总结 1.摘要&#xff08;Abstract&#xff09; 与别的文章的区别是什么:BERT是用来设计去…

springboot创建并配置环境(三) - 配置扩展属性(上集)

文章目录 一、介绍二、配置文件application.yml 一、介绍 在上一篇文章&#xff1a;springboot创建并配置环境(二) - 配置基础环境中&#xff0c;我们介绍了springboot如何配置基础环境变量。本篇文章讨论如何处理配置文件。即来自不同位置的配置属性&#xff0c;如&#xff1…

【Chat GPT】用 ChatGPT 运行 Python

前言 ChatGPT 是一个基于 GPT-2 模型的人工智能聊天机器人&#xff0c;它可以进行智能对话&#xff0c;同时还支持 Python 编程语言的运行&#xff0c;可以通过 API 接口进行调用。本文将介绍如何使用 ChatGPT 运行 Python 代码&#xff0c;并提供一个实际代码案例。 ChatGPT …

简单理解大模型参数高效微调中的LoRA(Low-Rank Adaptation)

[论文地址] [代码] [ICLR 22] 阅前须知&#xff1a;本博文可能有描述不准确/过度简化/出错的地方&#xff0c;仅供参考。 网络结构 其中&#xff0c;原有模型的参数是直接冻结的&#xff0c;可训练参数只有额外引入的LoRA参数(由nn.Parameter实现)。 模型微调的本质 记网络原…

LabVIEW实现三相异步电机磁通模型

LabVIEW实现三相异步电机磁通模型 三相异步电动机由于经济和出色的机电坚固性而广泛用于工业化应用。这台机器的设计和驱动非常简单&#xff0c;但在控制扭矩和速度方面&#xff0c;它隐藏了相当大的功能复杂性。通过数学建模&#xff0c;可以理解机器动力学。 基于微分方程的…

【嵌入式学习笔记】嵌入式基础9——STM32启动过程

1.MAP文件浅析 1.1.MDK编译后生成的中间过程文件 1.2.Map文件构成&#xff1a; 程序段交叉引用关系&#xff08;Section Cross References&#xff09;&#xff1a;描述各文件之间函数调用关系删除映像未使用的程序段&#xff08;Removing Unused input sections from the im…

【图像处理】使用 OpenCV 将您的照片变成卡通

图像到卡通 一、说明 在当今世界&#xff0c;我们被图像和视频所包围。从社交媒体到广告&#xff0c;图像已成为一种强大的交流媒介。但是你有没有想过&#xff0c;如果你能把你的照片变成卡通会发生什么&#xff1f;想象一下&#xff0c;为您最喜欢的照片创建动画版本&#xf…

Pytorch深度学习-----神经网络的基本骨架-nn.Module的使用

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

python爬虫基础入门——利用requests和BeautifulSoup

(本文是自己学习爬虫的一点笔记和感悟) 经过python的初步学习,对字符串、列表、字典、元祖、条件语句、循环语句……等概念应该已经有了整体印象,终于可以着手做一些小练习来巩固知识点,写爬虫练习再适合不过。 1. 网页基础 爬虫的本质就是从网页中获取所需的信息,对网…

类加载机制与类加载器

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ Java 源码是如何形成类文件的&#xff0c;类文件又是如何加载到虚拟机的&#xff0c;类加载有哪些机制和原则呢&#xff1f;本文将为大家一一介绍。 1 Java 源码形成类文件…

基于拉格朗日-遗传算法的最优分布式能源DG选址与定容(Matlab代码实现)

目录 1 概述 2 数学模型 2.1 问题表述 2.2 DG的最佳位置和容量&#xff08;解析法&#xff09; 2.3 使用 GA 进行最佳功率因数确定和 DG 分配 3 仿真结果与讨论 3.1 33 节点测试配电系统的仿真 3.2 69 节点测试配电系统仿真 4 结论 1 概述 为了使系统网损达到最低值&a…

AI Chat 设计模式:10. 组合模式

本文是该系列的第八篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 给我介绍一下组合模式A.1Q.2 好的&#xff0c;给我举一个组合模式的例子&#xff0c;使…

idea导入maven项目问题

问题产生原因&#xff1a; ①idea加载maven项目&#xff0c;如果网络不通畅&#xff0c;会在maven仓库中产生一个文件&#xff0c;如下图所示: ②当网络通畅时&#xff0c;在下载就会因为此文件导致无法下载正确的maven依赖 解决方案&#xff1a; ①打开maven仓库的根目录 ②…

ts中声明引入未使用的报错——解决方案

在编写ts项目的时候&#xff0c;经常会出现如下报错&#xff1a; 导入声明中的所有导入都未使用 这是因为导入的模块暂时没有使用&#xff0c;ts给的一个提示信息 解决方案&#xff1a; 在ts.config.json中 把noUnusedLocals 设置为false即可 {"compilerOptions"…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块4

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

【UE5 多人联机教程】03-创建游戏

效果 步骤 打开“UMG_MainMenu”&#xff0c;增加创建房间按钮的点击事件 添加如下节点 其中&#xff0c;“FUNL Fast Create Widget”是插件自带的函数节点&#xff0c;内容如下&#xff1a; “创建会话”节点指游戏成功创建一个会话后&#xff0c;游戏的其他实例即可发现&am…

微服务体系<1>

我们的微服务架构 我们的微服务架构和单体架构的区别 什么是微服务架构 微服务就是吧我们传统的单体服务分成 订单模块 库存模块 账户模块单体模块 是本地调用 从订单模块 调用到库存模块 再到账户模块 这三个模块都是调用的同一个数据库 这就是我们的单体架构微服务 就是…

Training-Time-Friendly Network for Real-Time Object Detection 论文学习

1. 解决了什么问题&#xff1f; 目前的目标检测器很少能做到快速训练、快速推理&#xff0c;并同时保持准确率。直觉上&#xff0c;推理越快的检测器应该训练也很快&#xff0c;但大多数的实时检测器反而需要更长的训练时间。准确率高的检测器大致可分为两类&#xff1a;推理时…

银河麒麟安装mysql数据库(mariadb)-银河麒麟安装JDK-银河麒麟安装nginx(附安装包)

银河麒麟离线全套安装教程&#xff08;手把手教程&#xff09; 1.银河麒麟服务器系统安装mysql数据库&#xff08;mariadb&#xff09; 2.银河麒麟桌面系统安装mysql数据库&#xff08;mariadb&#xff09; 3.银河麒麟服务器系统安装JDK 4.银河麒麟桌面系统安装JDK 5.银河麒麟…

青大数据结构【2021】

一、单选&#xff08;17&#xff01;&#xff09; 根据中序遍历得到降序序列可以知道&#xff0c;每个结点的左子树的结点的值比该结点的值小&#xff0c;因为没有重复的关键字&#xff0c;所以拥有最大值的结点没有左子树。 二、简答 三、分析计算 四、算法分析 3.迪杰斯特拉…