当前位置: 首页 > news >正文

优化单元测试效率:Spring 工程启动耗时统计

相关文章:

  • Java Agent 的简单使用

本文相关代码地址:https://gitee.com/dongguabai/blog

单元测试在软件项目的可持续发展中扮演着不可或缺的角色,这一点毫无疑问。不久前,公司大佬在内部分享时也提到过:单元测试是每个研发工程师的基本技能。

然而,在实际的开发过程中,我们发现很多研发人员并不愿意编写单元测试。其中一个普遍的原因是很多单元测试需要启动 Spring 容器(而许多 Mock 工具本身并不好用),导致启动过程非常耗时。

以我们的核心项目为例,一次单元测试的运行时间接近 5 分钟。考虑到一般需求开发涉及多个接口,再加上中间调试的时间,可能测试几个接口就会耗费一整天的时间。这严重影响了研发工作的效率。

[2023-10-23 12:34:07] [xxx_release] [Dongguabai.local] [INFO] [dfe7d0de51f548dd90e31411312acbd1] 705c1661f4044444b10b53009a32e8e1 main org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started SrvCustomerMapperProxyTest in 277.059 seconds (JVM running for 279.544) [12:34:07.659]

也有同学吐槽过:
在这里插入图片描述

本文就是希望可以解决这一痛点问题。

切入点

现在的问题是我们的项目启动 Spring 容器慢,那么我们就需要知道启动到底慢在哪里,结合 Spring 的生命周期,就需要去统计每个 Bean 实例化的时间,有几种很常见的切入点:

  • 基于 BeanPostProcessor

    BeanPostProcessor 提供了两个函数可以帮助我们在 Bean 实例化之后,属性赋值前做一些事情,这也是网上很多博客介绍的一种方案,但是这个方案有个细节要注意,一个 Bean 可能被多个 BeanPostProcessor 去处理,所以要注意计算耗时的 BeanPostProcessorpostProcessBeforeInitialization 是第一个调用,且 postProcessAfterInitialization 是最后一个调用。这里就涉及到多个 BeanPostProcessor 处理的顺序问题。

    在 Spring 容器中,多个 BeanPostProcessor ,它们的执行顺序是:

    1. postProcessBeforeInitialization 方法:按照 BeanPostProcessor 在 Spring 容器中的注册顺序依次执行。
    2. InitializingBean.afterPropertiesSet 方法:这是Bean初始化后调用的方法。
    3. Bean 配置中的 init-method:如果有配置,则执行该方法。
    4. postProcessAfterInitialization 方法:按照 BeanPostProcessor 在 Spring 容器中的注册顺序依次执行。

​ 很明显,与我们期望的计算耗时的 BeanPostProcessorpostProcessBeforeInitializationpostProcessAfterInitialization 的执行顺序是有冲突的,所以需要特殊处理。当然如果实际场景影响不大,直接基于 BeanPostProcessor 来做问题也不大。

  • 基于 InstantiationStrategy

    它负责基于默认构造器,实例化 Bean。需要在 instantiate 函数执行前后进行统计,但整体实现相对比较复杂

  • 基于 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

    统计的时间范围太宽了,属性赋值的情况也会计入其中,不太准确

  • 其实以上三点皆有缺陷,在业务系统中,每个 Bean 的耗时点可能各不一样,有的在实例化阶段,有的在初始化阶段(初始化又分多种情况,如 @PostConstructafterPropertiesSetinit-method)。所以这几块要单独统计,便于真正知道 Bean 初始化的耗时

耗时统计实现思路

其实耗时统计说白了就是在方法前后记录一下时间,然后求出差值即可。很常见的方案就是基于代理模式去做,但无论是基于 BeanPostProcessor 还是 InstantiationStrategy 其实都需要考虑 Spring 很多基础组件的顺序问题,相对比较麻烦。本文是基于 Java Agent 实现(关于 Java Agent 可以参看:Java Agent 的简单使用),对 Bean 的实例化和初始化两个阶段分别统计耗时。

耗时记录拦截器:

package blog.dongguabai.spring.bean.timestatistics;import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.Callable;/*** @author dongguabai* @date 2023-10-22 14:18*/
public class BeanLifeInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @Argument(1) String beaName, @AllArguments Object[] args, @This Object obj) throws Exception {long start = System.currentTimeMillis();try {return callable.call();} finally {long end = System.currentTimeMillis();if (Objects.equals(method.getName(), "instantiate")) {instantiateLog(beaName, end - start);}if (Objects.equals(method.getName(), "postProcessBeforeInitialization")) {beforeInitializationLog(beaName, start);}if (Objects.equals(method.getName(), "postProcessAfterInitialization")) {afterInitializationLog(beaName, end);}}}private synchronized static void instantiateLog(String beaName, long time) {Log log = LogInterceptor.logs.get(beaName);if (log == null) {log = new Log(beaName);}TimeDate timeDate = log.getTimeDate();timeDate.setBeanName(beaName);timeDate.setInstantiateTime(time);LogInterceptor.logs.put(beaName, log);}private synchronized static void beforeInitializationLog(String beaName, long time) {Log log = LogInterceptor.logs.get(beaName);if (log == null) {log = new Log(beaName);}log.setStartInitializationTime(Math.min(log.getStartInitializationTime() == 0L ? Long.MAX_VALUE : log.getStartInitializationTime(), time));LogInterceptor.logs.put(beaName, log);}private synchronized static void afterInitializationLog(String beaName, long time) {Log log = LogInterceptor.logs.get(beaName);if (log == null) {log = new Log(beaName);}log.setEndInitializationTime(Math.max(time, log.getEndInitializationTime()));LogInterceptor.logs.put(beaName, log);}
}

装载耗时记录拦截器:

package blog.dongguabai.spring.bean.timestatistics;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;/*** @author dongguabai* @date 2023-10-22 14:19*/
public class PreMainAgent {public static void premain(String agentArgs, Instrumentation inst) {new AgentBuilder.Default().type(ElementMatchers.hasSuperType(ElementMatchers.named("org.springframework.beans.factory.support.InstantiationStrategy"))).transform((builder, typeDescription, classLoader, javaModule) -> builder.method(ElementMatchers.named("instantiate")).intercept(MethodDelegation.to(BeanLifeInterceptor.class))).type(ElementMatchers.hasSuperType(ElementMatchers.named("org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter"))).transform((builder, typeDescription, classLoader, javaModule) -> builder.method(ElementMatchers.named("postProcessBeforeInitialization").or(ElementMatchers.named("postProcessAfterInitialization"))).intercept(MethodDelegation.to(BeanLifeInterceptor.class))).installOn(inst);new AgentBuilder.Default().type(ElementMatchers.named(agentArgs.split("#")[0])).transform((builder, typeDescription, classLoader, javaModule) -> builder.method(ElementMatchers.named(agentArgs.split("#")[1])).intercept(MethodDelegation.to(LogInterceptor.class))).installOn(inst);}
}

将 Agent 工程进行打包:

➜  spring-bean-timestatistics mvn package
[INFO] Scanning for projects...
...
[WARNING] Replacing pre-existing project main-artifact file: /Users/dongguabai/IdeaProjects/github/spring-bean-timestatistics/target/dongguabai-spring-bean-timestatistics-1.0-SNAPSHOT.jar
with assembly file: /Users/dongguabai/IdeaProjects/github/spring-bean-timestatistics/target/dongguabai-spring-bean-timestatistics-1.0-SNAPSHOT.jar
...

查看耗时

在启动单元测试时,增加 VM options:

-javaagent:/Users/dongguabai/IdeaProjects/github/spring-bean-timestatistics/target/dongguabai-spring-bean-timestatistics-1.0-SNAPSHOT.jar=com.xxx.SrvCustomerMapperProxyTest#testInsert

这里 com.xxx.SrvCustomerMapperProxyTest 是指要代理的类,testInsert 是指要代理的函数。

接下来启动单元测试看看效果(可以看到这次启动居然只用了 200s,也说明每次启动的时间都不稳定):

...
[2023-10-24 18:04:45] [maf_release] [Dongguabai.local] [INFO] [b4f3a146566b46a49214e48ffabfb615] 06df0988e4364d499d90aa4f7b702b2f main org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started SrvCustomerMapperProxyTest in 199.152 seconds (JVM running for 204.326) [18:04:45.346]
...
BEANS TIME STATISTICS:
【shardingDatasourceRoutingDataSource-> totalTime=50526, instantiateTime=76, initializeTime=50450】
【basicDataDubboService-> totalTime=5876, instantiateTime=0, initializeTime=5876】
【com.alibaba.boot.dubbo.DubboProviderAutoConfiguration-> totalTime=4641, instantiateTime=0, initializeTime=4641】
【orgBaseManager-> totalTime=4481, instantiateTime=0, initializeTime=4481】
【srvCommonDubboServiceImpl-> totalTime=2845, instantiateTime=17, initializeTime=2828】
【storageProxyService-> totalTime=2708, instantiateTime=0, initializeTime=2708...

可以看到各个 Bean 实例化和初始化的耗时,接下来要做的就是逐个分析降低它们的耗时,这就比较简单了。

耗时分析

这里分析几个 Top 耗时。

shardingDatasourceRoutingDataSource

可以看到耗时在 initializeTime,而这个 Bean 实现了 AbstractRoutingDataSource,重写了 afterPropertiesSet 方法:

    private void initDataSource(DruidDataSource druidDataSource) {try {druidDataSource.init();} catch (SQLException e) {throw new RuntimeException(e);}}

而耗时主要就在连接池初始化,这个其实有两种方案:

  1. 异步初始化

    设置一下即可,但是这个要注意,可能我们跑单测的时候涉及到数据库访问,如果此时连接池没有初始化完成的话会出现异常

    druidDataSource.setAsyncInit(true);
    
  2. 初始化连接数为 0

    就是不需要一开始就设置数据库连接:

    ds.setInitialSize(NumberUtils.toInt(env.getProperty("ds.initialSize"), 10));
    

我这边使用第二种方案。

basicDataDubboService

这是一个普通被 @Service 标注的 Bean,可以看到也是耗时在 initializeTime,这是因为这个项目使用的是自己封装的 Dubbo 接口,依赖注入 Dubbo Reference 是依赖于 BeanPostProcessor 实现。

basicDataDubboService 中引入 Dubbo 接口方式是使用的自定义注解 @DubboConsumer

@Service
@Slf4j
public class BasicDataDubboService {@DubboConsumer(check = false)          private FaultService faultService;
}

@DubboConsumer 会被 BeanPostProcessor 处理:

 @Beanpublic BeanPostProcessor beanPostProcessor() {return new BeanPostProcessor() {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {Class<?> objClz = bean.getClass();if (org.springframework.aop.support.AopUtils.isAopProxy(bean)) {objClz = org.springframework.aop.support.AopUtils.getTargetClass(bean);}try {for (Field field : objClz.getDeclaredFields()) {DubboConsumer dubboConsumer = field.getAnnotation(DubboConsumer.class);
...

分析后可以设置 @DubboConsumer 属性 lazytrue,相当于 ReferenceBeanlazy 属性,这样可以懒加载创建 Reference 代理。

同时也发现有很多无用的注入,这里可以直接清除:

在这里插入图片描述

com.alibaba.boot.dubbo.DubboProviderAutoConfiguration

这个也是团队内部封装的 Dubbo 配置类,它依赖 @PostConstruct 对外暴露 Dubbo 服务:

    @PostConstructpublic void init() throws Exception {Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(Service.class);for (Map.Entry<String, Object> entry : beans.entrySet()) {this.initProviderBean(entry.getKey(), entry.getValue());if(this.properties.getProviderDefaultGroup()){this.initProviderBeanWithGroup(entry.getKey(), entry.getValue());}}}
 private void initProviderBeanWithGroup(String beanName, Object bean) throws Exception {...serviceConfig.export();}

可以看到整个实现也是相对比较粗糙(比如根本没必要获取全部的 @Service 标注的 Bean,可以自定义一个实现注解,降低查找范围),先不纠结于细节,至少本地启动单元测试是没必要暴露 Dubbo 服务的,所以是否暴露 Dubbo 服务可以基于 profile 做一个限制。

结果查看

上面三耗时分析,除了 com.alibaba.boot.dubbo.DubboProviderAutoConfiguration 由于涉及到代码改造,暂时没有处理,其余的两个已经处理,重新启动,看看效果:

...
[2023-10-24 18:52:03] [maf_release] [Dongguabai.local] [INFO] [d53b3ebcc40240f19b12ca0bdf434436] 375000afa08a48b49f9121a5c4760386 main org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started SrvCustomerMapperProxyTest in 142.282 seconds (JVM running for 148.136) [18:52:03.086]
...
【basicDataDubboService-> totalTime=5567, instantiateTime=0, initializeTime=5567】
【shardingDatasourceRoutingDataSource-> totalTime=272, instantiateTime=70, initializeTime=202...

可以看到整个启动速度大大提升,主要还是 shardingDatasourceRoutingDataSource 的初始化速度从 50450ms 降低至 202ms。至于 basicDataDubboService,效果相对一般,但也从中找到了内部封装的 Dubbo 组件优化点。

总结

基于 TOP3 耗时 Bean 的分析来看,耗时有这么几种原因:

  1. 数据库连接可以延迟初始化
  2. 移除无用的依赖注入
  3. 内部封装的 Dubbo 组件存在很多优化点,如:
    • Dubbo Provider 在本地没必要对外暴露服务
    • 懒加载创建 Reference 代理

尽管不同项目的耗时原因各有差异,但只要能够确定具体的耗时点,问题就不会太棘手。本文介绍了一个简单的工具,用于获取 Spring Bean 初始化的时间,借此能够准确定位具体的 Bean,从而有针对性地进行优化。通过优化这些耗时操作,就能够有效提升整个项目的启动速度,显著增强研发工作的效率。

References

  • https://github.com/microsphere-projects/microsphere-spring
  • https://github.com/raphw/byte-buddy/issues/748

欢迎关注公众号:
在这里插入图片描述

相关文章:

优化单元测试效率:Spring 工程启动耗时统计

相关文章&#xff1a; Java Agent 的简单使用 本文相关代码地址&#xff1a;https://gitee.com/dongguabai/blog 单元测试在软件项目的可持续发展中扮演着不可或缺的角色&#xff0c;这一点毫无疑问。不久前&#xff0c;公司大佬在内部分享时也提到过&#xff1a;单元测试是…...

华纳云:连接mysql出现2059错误怎么解决

MySQL连接错误2059通常表示MySQL服务器拒绝了连接。这种错误可能由多种原因引起&#xff0c;以下是一些可能的解决方法&#xff1a; 检查MySQL服务器是否正在运行&#xff1a; 确保MySQL服务器正在正常运行。您可以使用以下命令检查MySQL服务器的状态&#xff1a; systemctl st…...

零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题

目录 1. 线程控制 1.1 线程创建(pthread_create) 1.2 线程结束(pthread_exit) 1.3 线程等待(pthread_join) 1.4 线程取消(pthread_cancel结束) 1.5 线程tid(pthread_self()) 1.6 线程局部存储(__thread) 1.7 线程分离(pthread_detach) 2. C的多线程 3. 笔试选择题 答…...

docker版本的Jenkins安装与更新技巧

因为jenkins/jenkins镜像默认带的jenkins版本比较低&#xff0c;导致安装完以后&#xff0c;很多插件因为版本问题无法安装。以下是最权威&#xff0c;最方便的安装教程。 1. 创建本地挂载目录 mkdir -p /mnt/dockerdata/jenkins/home/2. 修改挂载目录权限 chown -R 1000:10…...

[C++]3.类和对象下(this指针补充)+ 类和对象中构造函数和析构函数。

类和对象下&#xff08;this指针补充&#xff09; 类和对象中构造函数和析构函数 一.this补充&#xff1a;1.概念总结&#xff1a;2.两个问题&#xff1a; 二.构造函数和析构函数&#xff1a;一.类的默认构造&#xff1a;1.初始化和清理&#xff1a;2.拷贝复制&#xff1a;3.取…...

OpenLDAP LDIF详解

手把手一步步搭建LDAP服务器并加域 有必要理解的概念LDAPWindows Active Directory 服务器配置安装 OpenLDAP自定义安装修改对象&#xff08;用户和分组等&#xff09;修改olcSuffix 和 olcRootDN 属性增加olcRootPW 属性修改olcAccess属性验证新属性值 添加对象&#xff08;用…...

Leetcode.33 搜索旋转排序数组

题目链接 Leetcode.33 搜索旋转排序数组 mid 题目描述 整数数组 n u m s nums nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c; n u m s nums nums 在预先未知的某个下标 k &#xff08; 0 ≤ k < n u m s . l e n g t h &#xff09;…...

ES 8.x 向量检索性能测试 把向量检索性能提升100倍!

向量检索不仅在的跨模态检索场景中应用广泛&#xff0c;随着chat gpt的或者&#xff0c;利用es的向量检索&#xff0c;在Ai领域发挥着越来越大的作用。 本文&#xff0c;主要测试es的向量检索性能。我从8.x就开始关注ES的向量检索了。当前ES已经发布到 8.10 版本。以下是官方文…...

云计算——ACA学习 云计算架构

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 写在前面 前期回顾 本期介绍 一.云计算架…...

基于深度学习实现一张单图,一个视频,一键换脸,Colab脚本使用方法,在线版本,普通人也可以上传一张图片体验机器学习一键换脸

基于深度学习实现一张单图,一个视频,一键换脸,Colab脚本使用方法,在线版本,普通人也可以上传一张图片体验机器学习一键换脸。 AI领域人才辈出,突然就跳出一个大佬“s0md3v”,开源了一个单图就可以进行视频换脸的项目。 项目主页给了一张换脸动图非常有说服力,真是一图…...

leetcode 21

递归的方式 class Solution { public:ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {if(l1 nullptr){return l2;}else if(l2 nullptr){return l1;}else if(l1->val < l2->val){l1->next mergeTwoLists(l1->next, l2);return l1;}else if(l1->va…...

【Spring Cloud】openfeign负载均衡方案(和lb发展历史)

文章目录 版本1&#xff1a;原始loadBalancerClient方案版本2&#xff1a;ribbon-loadbalancer方案版本3&#xff1a;openfeign方案&#xff08;即**方案2openfeign版本**&#xff09; 本文描述了Spring Cloud微服务中&#xff0c;各个服务间调用的负载均衡方案的升级历史&…...

R语言:主成分分析PCA

文章目录 主成分分析处理步骤数据集code主成分分析 主成分分析(或称主分量分析,principal component analysis)由皮尔逊(Pearson,1901)首先引入,后来被霍特林(Hotelling,1933)发展。 主成分分析是一种通过降维技术把多个变量化为少数几个主成分(即综合变量)的统计分…...

Linux下磁盘备份、文件备份和定时备份命令指南

文章目录 磁盘备份和定时备份命令指南1. 引言2. 磁盘备份命令dda. 简介和基本用法b. dd命令的参数和选项说明c. 使用dd命令进行磁盘镜像备份的步骤d. 恢复备份数据的方法和注意事项e. 示例&#xff1a;使用dd命令备份和还原磁盘镜像 3. 磁盘备份命令tara. 简介和基本用法b. tar…...

电脑软件:推荐一款非常强大的pdf阅读编辑软件

目录 一、软件简介 二、功能介绍 1、界面美观&#xff0c;打开速度快 2、可直接编辑pdf 3、非常强大好用的注释功能 4、很好用的页面组织和提取功能 5、PDF转word效果非常棒 6、强大的OCR功能 三、软件特色 四、软件下载 pdf是日常办公非常常见的文档格式&#xff0c;…...

Android 13.0 系统开机屏幕设置默认横屏显示

1.概述 在13.0的系统产品开发中,对于产品需求来说,由于是宽屏设备所以产品需要开机默认横屏显示,开机横屏显示这就需要从 两部分来实现,一部分是系统开机动画横屏显示,另一部分是系统屏幕显示横屏显示,从这两方面就可以做到开机默认横屏显示了 2.系统开机设置默认横屏显…...

Redis -- 基础知识1

1.介绍 1.初识Redis Redis&#xff1a;The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker. in-memory data&#xff1a;在内存中存储&#xff0c;Redis是在分布式系统中存储起作用的 解释&am…...

ubuntu 20.04 passwd 指令不能使用

Linux 更改用户密码报Changing password for user 用户名. passwd: Module is unknown或更改新增用户密码passwd&#xff1a;未知的用户名 报错信息如下&#xff1a; 解决方法&#xff1a; 可以排查 /etc/pam.d/passwd配置文件 注释掉包含pam_passwdqc.so模块的行&#xff0c…...

单片机郭天祥(02)

1&#xff1a;解决keil5软件的乱码问题&#xff0c;修改编码为UTF-8 2&#xff1a;打开keil5使用debug对编写好的程序进行调试 给程序打上断点 使用仿真芯片 更改设备管理器相关设置 接通电源后点击debug连接到51单片机 使用stc-isp获取延时函数 将延时函数添加进入创建好的…...

Hadoop3教程(三十五):(生产调优篇)HDFS小文件优化与MR集群简单压测

文章目录 &#xff08;168&#xff09;HDFS小文件优化方法&#xff08;169&#xff09;MapReduce集群压测参考文献 &#xff08;168&#xff09;HDFS小文件优化方法 小文件的弊端&#xff0c;之前也讲过&#xff0c;一是大量占用NameNode的空间&#xff0c;二是会使得寻址速度…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

代理服务器-LVS的3种模式与调度算法

作者介绍&#xff1a;简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器&#xff0c;其中以Nginx为主&#xff0c;本章我们来讲解几个代理软件&#xff1a…...