深入浅出 Compose Compiler(1) Kotlin Compiler KCP
前言
Compose 的语法简洁、代码效率非常高,这主要得益于 Compose Compiler 的一系列编译期魔法,帮开发者生成了很多样板代码。但编译期插桩也阻碍了我们对于 Compose 运行原理的认知,想要真正读懂 Compose 就必须先了解它的 Compiler。本系列文章将带大家揭开 Compose Compiler 的神秘面纱。
Compose 是一个 Kotlin Only 框架,所以 Compose Compiler 的本质是一个 KCP(Kotlin Compiler Plugin)。在研究 Compose Compiler 源码之前,先要铺垫一些 Kotlin Compiler 以及 KCP 的基础知识
Kotlin 编译流程
Kotlin 是一门跨平台语言,Kotlin Compiler 可以将 Kt 源码编译成多个平台的目标代码:JS、JVM 字节码,甚至 LLVM 机器码。但无论编译成何种目标代码,其编译过程都可以分为两个阶段:
- Frontend(编译器前端):对源代码分析得到 AST (抽象语法树)以及符号表,并完成静态检查
- Backend(编译器后端):基于 AST 等前端产物,生成平台目标代码
简而言之:前端负责源码的解析和检查,后端负责目标代码的生成
如上,以 Kotlin/JVM 为例:
- Frontend 处理中,Kt 源文件经过词法、语法和语义分析(Lexer&Paser)生成 PSI 以及对应的 BindingContext。
- Backend 处理中,基于 PSI 和 BindingContext 先生成 JVM 字节码,然后通过 ASM 将字节码二进制化生成 class 文件
不同目标平台的编译流程中 Frontend 的处理流程都一样,只是在 Backend 中生成不同的目标代码
K1 编译器:PSI & BindingContext
PSI 全称 Program Structure Interface, 可以将它理解为 JetBrains 专用的 AST(标准 AST 之上有一些扩展)。PSI 可以用于编译过程中的语法静态检查,PSI 也用于 IntelliJ 系列 IDE 的静态检查,我们在编写代码过程中能实时提示语法错误就是靠它。因此 PSI 有助于编译和编写阶段复用静态检查逻辑。我们在开发 IDE Plugin 或者编写 Detekt 静态检查用例时都有机会使用到 PSI。
- PSI: https://plugins.jetbrains.com/docs/intellij/psi-elements.html
- Detekt: https://github.com/detekt/detekt
在 IDE 中通过 PsiViewer 插件可以实时看到源码对应的 PSI,以下面代码为例:
fun main() {println("Hello, World!")
}
上图是 PsiViewer 中的输出结果,可以看到它体现了以下树形结构:
PSI 树的节点是源码经分析后的语法元素,例如一个特殊符号,一个字符串等,这都是一个个 PsiElement。PsiElement 仍然缺少了基于上下文的语义信息,比如对于一个 KtFunction,它的参数信息,修饰符信息等等,这就需要 BindingContext 的辅助了。
BindingContext 相当于 PSI 配套的符号表,PsiElement 经语义分析后得到对应的 Descriptor (描述符)并记录到 BindingContext 中,BindingContext 可以快速索引到 PSI 节点对应的 Descriptor。Descriptor 包含我们需要的语义信息,例如 FunctionDescriptor 可以获取 TypeParameters,isInline 等信息。
BindingContext 结构类似一个 Map<Type, Map<key, Descriptor>
,第一个 Map 的 key 代表 PSI 节点类型,第二个 Map 的 key 是 PsiElement 实例,Value 是其对应的 Descriptor。KtFunction 为 key 可以获取对应的 FunctionDescriptor;KtCallExpression 获取对应的 ResolvedCall,这里面包含了调用方法的 FunctionDescriptor 以及传入的 Parameters。
K2 编译器:FIR & IR
通过上面的介绍我们知道,Kotlin Compiler 的 Frotend 产物是 PSI 以及 BindingContext,Backend 将基于它们直接输出目标代码。由于 Backend 耦合了目标代码生成逻辑,一些编译期的处理和优化逻辑难以多平台复用。例如我们都知道的 suspend 函数在编译期会生成额外的代码,而我们希望这些 codegen 逻辑得以复用,为此 Kotlin 开发了新一代编译器,取名为 K2 。
K2: https://blog.jetbrains.com/zh-hans/kotlin/2021/10/the-road-to-the-k2-compiler/
K2 编译器的最大特点是引入了 IR(Intermediate Representation,中间表达)。IR 是连接前后端的中间产物, 它与平台无关,类似 suspend 这类编译期优化可以面向 IR 实现并跨平台复用。
K2 中使用新的基于 IR 的 Backend 替代旧有的基于 PSI 和 BindingContext 的 Backend。Kotlin 1.5 开始 Kotlin/JVM 默认启用新的 IR Backend,1.6 开始 Kotin/JS IR Backend 成了标配。下图是引入 IR Backend 的编译流程。
IR 也是一颗树形数据结构,但它的抽象表达更加“低级”,更贴近 CPU 架构。IrElement 带有多种语义信息,例如 FUN 的 visibility,modality 以及 returnType 等等,不必像 PsiElement 那样需要通过查询 BindingContext 获取这些信息。
前面 Hello World 的例子,其对应的 IR 树打印如下:
FUN name:main visibility:public modality:FINAL <> () returnType:kotlin.UnitBLOCK_BODYCALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline] declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=nullmessage: CONST String type=kotlin.String value="Hello, World!"
除了新的 IR Backend,K2 也更新了 Frontend,主要变化是使用 FIR (Frontend IR)替代了 PSI 与 BindingContext。1.7.0 起我们可以使用到 K2 的新前端。
综上可见: K2 相对于 K1 的主要变化引入了 FIR Frontend 和 IR Backend。
IR 可以由 FIR 转化而来,它们都是树型结构,那么这两者又有什么区别呢?可以从以下三个方面进行区分:
FIR | IR | |
---|---|---|
目标不同 | FIR 整合了 PSI 与 BindingContext 信息,更快速地查找描述符信息,它的首要目标是提升前端静态分析以及检查的性能 | 性能不是 IR 的考虑,它的数据结构的出发点不是为了提升后端编译速度,而是服务于不同后端之间的编译逻辑共享,降低不同平台支持新语言特性的成本 |
结构不同 | FIR 仍然是一颗 AST,只是增强了一些符号信息,加速静态分析 | IR 不仅是一颗 AST,它提供了更丰富的基于上下文的语义信息,比如我可以知道某个代码块中的某个变量是临时变量还是成员变量,而 FIR 难以做到 |
能力不同 | 虽然 FIR 也可以处理一些简单的脱糖和代码生成工作,但整体上仍然是服务于前端,不能对 AST 大幅度修改 | IR 具有丰富的 Godegen API,可以更加灵活地对树形结构进行 add/remove/update,实现任意编译期的魔改需求 |
KCP(Kotlin Compiler Plugin)
KCP 允许我们在上述 Kotlin 编译过程中,通过增加扩展点以实现各种编译期魔改。Kotlin
的不少语法糖都是基于 KCP 实现的,比如大家熟知的 No-arg、All-open、kotlinx-serialization 等等。
KCP 也可以像 KAPT 那样在编译期进行注解处理,但它相对于 KATP 更具优势:
-
KCP 在 Kotlin 编译过程中进行,而 KAPT 需要在正式编译之前增加额外的预编译环节,因此 KCP 的性能更好。KSP(Kotlin Symbol Processing)也是基于 KCP 实现的,这也是为什么 KSP 的性能更好的原因
-
KAPT 主要是用来生成新代码,难以针对原有代码逻辑做修改。KCP 可以针对 Bytecode 或者 IR 做任意修改,能力更强大。
KCP 的开发步骤
KCP 虽然功能强大但是开发难度较高,开发一个完整的 KCP 要涉及多个步骤:
-
Gradle Plugin:
- Plugin:KCP 是通过 Gradle 配置的,需要定义一个 Gradle 插件,并在 Gradle 中配置 KCP 所需的编译参数。
- Subplugin: 建立从 Gradle Plugin 到 Kotlin Plugin 的连接,并将 Gradle 中配置的参数传递给 Kotlin Plugin
-
Kotlin Plugin:
- CommandLineProcessor:KCP 的入口,定义 KCP 的 id、解析命令行参数等
- ComponentRegister:注册 KCP 中的 Extension 扩展点。它与 CommandLineProcessor 一样都是通过 SPI 调用,需要添加 auto-service 注解
- XXExtension:这是实现 KCP 逻辑的地方。Kotlin 提供了许多类型的 Extension 供我们实现。编译器会在前端、后端的各个编译环节中调用 KCP 注册的对应类型的 Extension。例如 ExpressionCodegenExtension 可用来修改 Class 的 Body;ClassBuilderInterceptorExtension 可以修改 Class 的 Definition 等等
随着 Kotlin Compiler 从 K1 升级到 K2,KCP 也提供了面向 K2 的 Extension。
以 No-arg 为例 ,No-arg 通过为 Class 添加注解自动生成无参构造函数。No-arg 源码中存在 K1、K2 两套 Extension,可以兼容不同 Kotlin 版本的使用:
- No-arg: https://kotlinlang.org/docs/no-arg-plugin.html
- source:https://cs.android.com/android-studio/kotlin/+/master:plugins/noarg/
-
NoArg K1:
- CliNoArgDeclarationChecker:NoArg 不能作用于 Inner Class,这里使用基于 PSI 的前端检查逻辑检查是否是 Inner Class
- CliNoArgExpressionCodegenExtension:继承自 ExpressionCodegenExtension,基于 PSI 和对应的 Descriptor 以 JVM 字节码的形式在 Class Body 中添加无参构造函数
-
NoArg K2:
- FirNoArgDeclarationChecker:新的 K2 前端,可基于 FIR 检查 InnerClass
- NoArgIrGenerationExtension:继承自 IrGenerationExtension ,基于 IR 添加无参构造函数
以 Backend Extension 为例,体会以下具体实现上的区别:
- CliNoArgExpressionCodegenExtension 中的处理:
// 1. 基于 descriptor 获取 class 信息
val superClassInternalName = typeMapper.mapClass(descriptor.getSuperClassOrAny()).internalName
val constructorDescriptor = createNoArgConstructorDescriptor(descriptor)
val superClass = descriptor.getSuperClassOrAny()// 2. 通过 Codegen 直接生成无参构造函数对应的字节码
functionCodegen.generateMethod(JvmDeclarationOrigin.NO_ORIGIN, constructorDescriptor, object : CodegenBased(state) {override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {codegen.v.load(0, AsmTypes.OBJECT_TYPE)if (isParentASealedClassWithDefaultConstructor) {codegen.v.aconst(null)codegen.v.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName, "<init>","(Lkotlin/jvm/internal/DefaultConstructorMarker;)V", false)} else {codegen.v.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName, "<init>", "()V", false)}if (invokeInitializers) {generateInitializers(codegen)}codegen.v.visitInsn(Opcodes.RETURN)}
})
- NoArgIrGenerationExtension 中的处理:
// 1. 基于 IrClass 获取 Class 信息
val superClass =klass.superTypes.mapNotNull(IrType::getClass).singleOrNull { it.kind == ClassKind.CLASS }?: context.irBuiltIns.anyClass.owner
val superConstructor =if (needsNoargConstructor(superClass))getOrGenerateNoArgConstructor(superClass)else superClass.constructors.singleOrNull { it.isZeroParameterConstructor() }?: error("No noarg super constructor for ${klass.render()}:\n" + superClass.constructors.joinToString("\n") { it.render() })// 2. 基于 irFactory 等 IR API 创建构造函数
context.irFactory.buildConstructor {startOffset = SYNTHETIC_OFFSETendOffset = SYNTHETIC_OFFSETreturnType = klass.defaultType
}.also { ctor ->ctor.parent = klassctor.body = context.irFactory.createBlockBody(ctor.startOffset, ctor.endOffset,listOfNotNull(IrDelegatingConstructorCallImpl(ctor.startOffset, ctor.endOffset, context.irBuiltIns.unitType,superConstructor.symbol, 0, superConstructor.valueParameters.size),IrInstanceInitializerCallImpl(ctor.startOffset, ctor.endOffset, klass.symbol, context.irBuiltIns.unitType).takeIf { invokeInitializers }))
}
NoArgIrGenerationExtension 是一个 IrGenerationExtension,这是专门用来更新 Ir 的扩展点,可以看到里面已经没有了对字节码的操作,取而代之使用 IR 中的各种 buildXXX API。
Compose Compiler 的代码生成也是依靠 IrGenerationExtension 实现的,所以:即使最早版本的 Compose 也要求 Kotlin 版本大于 1.5.10,就是因其 Compiler 只支持 IR Backend Extension。
Compose Compiler
Compose Compiler 本质上是一个 KCP,在了解了 KCP 的基本构成之后,我们知道 Compose Compiler 的核心在于 Extension
Compose Compiler: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/compiler/compiler-hosted/
直接找到 ComposeComponentRegistrar,查看注册了哪些 Extension:
class ComposeComponentRegistrar : ComponentRegistrar {//...StorageComponentContainerContributor.registerExtensioproject,ComposableCallChecker())StorageComponentContainerContributor.registerExtensioproject,ComposableDeclarationChecker())StorageComponentContainerContributor.registerExtensioproject,ComposableTargetChecker())ComposeDiagnosticSuppressor.registerExtension(project,ComposeDiagnosticSuppressor())@Suppress("OPT_IN_USAGE_ERROR")TypeResolutionInterceptor.registerExtension(project,@Suppress("IllegalExperimentalApiUsage")ComposeTypeResolutionInterceptorExtension())IrGenerationExtension.registerExtension(project,ComposeIrGenerationExtension(configuration = configuration,liveLiteralsEnabled = liveLiteralsEnabled,liveLiteralsV2Enabled = liveLiteralsV2EnabledgenerateFunctionKeyMetaClasses = generateFuncsourceInformationEnabled = sourceInformationEintrinsicRememberEnabled = intrinsicRememberEdecoysEnabled = decoysEnabled,metricsDestination = metricsDestination,reportsDestination = reportsDestination,))DescriptorSerializerPlugin.registerExtension(project,ClassStabilityFieldSerializationPlugin())//...
}
- ComposableCallChecker:检查是否可以调用 @Composable 函数
- ComposableDeclarationChecker:检查 @Composable 的位置是否正确
- ComposeDiagnosticSuppressor:屏蔽不必要的编译诊断错误
- ComposeIrGenerationExtension:负责 Composable 函数的代码生成
- ClassStabilityFieldSerializationPlugin:分析 Class 是否稳定,并添加稳定性信息
这里的各种 Checker 是 Frontend Extension ,目前仍然是基于 K1 实现的,而位于 Backend 的 ComposeIrGenerationExtension 则面向 K2,这也是 Compose 代码生成的核心,会在本系列的后续文章中重点介绍。
参考
-
Writing Your First Kotlin Compiler Plugin
https://resources.jetbrains.com/storage/products/kotlinconf2018/slides/5_Writing%20Your%20First%20Kotlin%20Compiler%20Plugin.pdf -
Kotlin Compiler Internals In 1.4 and beyond
https://docs.google.com/presentation/d/e/2PACX-1vTzajwYJfmUi_Nn2nJBULi9bszNmjbO3c8K8dHRnK7vgz3AELunB6J7sfBodC2sKoaKAHibgEt_XjaQ/pub?slide=id.g955e8c1462_0_190
相关文章:

深入浅出 Compose Compiler(1) Kotlin Compiler KCP
前言 Compose 的语法简洁、代码效率非常高,这主要得益于 Compose Compiler 的一系列编译期魔法,帮开发者生成了很多样板代码。但编译期插桩也阻碍了我们对于 Compose 运行原理的认知,想要真正读懂 Compose 就必须先了解它的 Compiler。本系列…...

BatchNormalization和LayerNormalization的理解、适用范围、PyTorch代码示例
文章目录 为什么要NormalizationBatchNormLayerNormtorch代码示例 学习神经网络归一化时,文章形形色色,但没找到适合小白通俗易懂且全面的。学习过后,特此记录。 为什么要Normalization 当输入数据量级极大或极小时,为保证输出数…...

大数据 | 实验二:文档倒排索引算法实现
文章目录 📚实验目的📚实验平台📚实验内容🐇在本地编写程序和调试🥕代码框架思路🥕代码实现 🐇在集群上提交作业并执行🥕在集群上提交作业并执行,同本地执行相比即需修改…...
Java文档注释-JavaDoc标签
标签含义author指定作者{code}使用代码字体以原样显示信息,不处理HTML样式deprecated指定程序元素已经过时{docRoot}指定当前文档的根目录路径exception标识由方法或构造函数抛出的异常{inheritDoc}从直接超类中继承注释{link}插入指向另外一个主题的内联链接{linkp…...

黑盒测试过程中【测试方法】详解5-输入域,输出域,猜错法
在黑盒测试过程中,有9种常用的方法:1.等价类划分 2.边界值分析 3.判定表法 4.正交实验法 5.流程图分析 6.因果图法 7.输入域覆盖法 8.输出域覆盖法 9.猜错法 黑盒测试过程中【测试方法】讲解1-等价类,边界值,判定表_朝一…...
Python学习之sh(shell脚本)在Python中的使用
文章目录 前言一、sh是什么?二、使用步骤1.安装2.使用示例3.使用sh执行命令4.关键字参数5.查找命令6.Baking参数 前言 本文章向大家介绍[Python库]分析一个python库–sh(系统调用),主要内容包括其使用实例、应用技巧、基本知识点…...
追求卓越:编写高质量代码的方法和技巧
本文讨论了编写高质量代码的重要性,并详细介绍了高质量代码的特征、编程实践技巧和软件工程方法论。通过遵循这些原则和实践,程序员可以编写出更稳定、可维护和可扩展的代码。 一、 前言 写出高质量代码是每个程序员的追求和目标。高质量的代码可以使程…...
MATLAB算法实战应用案例精讲-【人工智能】机器视觉(概念篇)(最终篇)
目录 前言 几个高频面试题目 如何评价一个光源的好坏? 如何依靠光源增强图像对比度?...

【老王读SpringMVC-3】根据 url 是如何找到 controller method 的?
前面分析了 request 与 handler method 映射关系的注册,现在再来分析一下 SpringMVC 是如何根据 request 来获取对应的 handler method 的? 可能有人会说,既然已经将 request 与 handler method 映射关系注册保存在了 AbstractHandlerMethodMapping.Ma…...

人机交互到艺术设计及玫瑰花绘制实例
Python库之图形用户界面 Riverbank Computing | Introduction Welcome to wxPython! | wxPython Overview — PyGObject Python库之游戏开发 https://www.pygame.org/news Panda3D | Open Source Framework for 3D Rendering & Games python.cocos2d.org Python库之…...

多臂老虎机问题
1.问题简介 多臂老虎机问题可以被看作简化版的强化学习问题,算是最简单的“和环境交互中的学习”的一种形式,不存在状态信息,只有动作和奖励。多臂老虎机中的探索与利用(exploration vs. exploitation)问题一直以来都…...
DNS 查询原理详解
DNS(Domain Name System)是互联网上的一种命名系统,它将域名转换为IP地址。在进行DNS查询时,先要明确需要查询的主机名,然后向本地DNS服务器发出查询请求。 1. 本地DNS服务器查询 当用户在浏览器中输入一个URL或者点…...

浅谈软件测试工程师的技能树
软件测试工程师是一个历史很悠久的职位,可以说从有软件开发这个行业以来,就开始有了软件测试工程师的角色。随着时代的发展,软件测试工程师的角色和职责也在悄然发生着变化,从一开始单纯的在瀑布式开发流程中担任测试阶段的执行者…...

转型产业互联网,新氧能否再造辉煌?
近年来,“颜值经济”推动医美行业快速发展,在利润驱动下,除了专注医美赛道的企业之外,也有不少第三方互联网平台正强势进入医美领域,使以新氧为代表的医美企业面对不小发展压力,同时也展现出强大的发展韧性…...

CRE66365 应用资料
CRE66365是一款高度集成的电流模式PWM控制IC,为高性能、低待机功耗和低成本的隔离型反激转换器。在正常负载条件下,AC输入高电压下工作在QR模式。为了最大限度地减少开关损耗,QR 模式下的最大开关频率被内部限制为 77kHz。当负载较低时&#…...
vue3快速上手学习笔记,还不快来看看?
Vue3快速上手 1.Vue3简介 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)耗时2年多、2600次提交、30个RFC、600次PR、99位贡献者github上的tags地址:https://github.com/vuejs/vue-next/release…...
HDU 5927 Auxiliary Set
原题链接: https://acm.hdu.edu.cn/showproblem.php?pid5927 题意: 有一颗根节点是1的树,其中有重要的点和不重要的点,重要的点需满足以下两个条件至少一个: 1.本来就是重要的点 2.是两个重要的点的最近共同祖先 有t…...
24:若所有参数皆需类型转换,请为此采用non-member函数
令class支持隐式类型转换通常是个糟糕的主意。 这条规则有其例外,最常见的例外是在建立数值类型时。 例,假设你设计一个class用来表现有理数,则允许整数“隐式转换”为有理数就很合理。 class Rational{ public:Rational(int numerator0,i…...
CMake(2)-详解-编译-安装-支持GDB-添加环境检查-添加版本号-生成安装包
目录 1.什么是CMake 1.1 编译流程CMakeLists.txt a) 最简单 demo1 b) 常用demo2 c) 单目录,源文件-输出文件 DIR_SRCS中 d)多目录,多源文件 1.2.执行命令: 1.3.自定义编译选项 2.安装和测试 3.支持GDB 4.添加环境检查 5.添加…...
java面试题(redis)
目录 1.redis主要消耗什么物理资源? 2.单线程为什么快 3.为什么要使用Redis 4.简述redis事务实现 5.redis缓存读写策略 6.redis除了做缓存,还能做些什么? 7.redis主从复制的原理 8.Redis有哪些数据结构?分别有哪些典型的应…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...