Dart虚拟机运行原理

一、Dart虚拟机

1.1 引言

Dart VM是一种虚拟机,为高级编程语言Dart提供执行环境,但这并意味着Dart在D虚拟机上执行时,总是采用解释执行或者JIT编译。 例如还可以使用Dart虚拟机的AOT管道将Dart代码编译为机器代码,然后运行在Dart虚拟机的精简版环境,称之为预编译运行时(precompiled runtime)环境,该环境不包含任何编译器组件,且无法动态加载Dart源代码。

1.2 虚拟机如何运行Dart代码

Dart VM有多钟方式来执行代码:

  • 源码或者Kernel二进制(JIT)
  • snapshot
    • AOT snapshot
    • AppJIT snapshot

区别主要在于什么时机以及如何将Dart代码转换为可执行的代码。

1.3 Isolate组成

先来看看dart虚拟机中isolate的组成:

image

  • isolate堆是运该isolate中代码分配的所有对象的GC管理的内存存储;
  • vm isolate是一个伪isolate,里面包含不可变对象,比如null,true,false;
  • isolate堆能引用vm isolate堆中的对象,但vm isolate不能引用isolate堆;
  • isolate彼此之间不能相互引用
  • 每个isolate都有一个执行dart代码的Mutator thread,一个处理虚拟机内部任务(比如GC, JIT等)的helper thread;

isolate拥有内存堆和控制线程,虚拟机中可以有很多isolate,但彼此之间不能直接状态,只能通过dart特有的端口;isolate除了拥有一个mutator控制线程,还有一些其他辅助线程:

  • 后台JIT编译线程;
  • GC清理线程;
  • GC并发标记线程;

线程和isolate的关系是什么呢?

  • 同一个线程在同一时间只能进入一个isolate,当需要进入另一个isolate则必须先退出当前的isolate;
  • 一次只能有一个Mutator线程关联对应的isolate,Mutator线程是执行Dart代码并使用虚拟机的公共的C语言API的线程
1.4 ThreadPool组成

虚拟机采用线程池的方式来管理线程,定义在runtime/vm/thread_pool.h

image

ThreadPool的核心成员变量:

  • all_workers_:记录所有的workers;
  • idle_workers:_记录所有空闲的workers;
  • count_started_:记录该线程池的历史累计启动workers个数;
  • count_stopped_:记录该线程池的历史累计关闭workers个数;
  • count_running_:记录该线程池当前正在运行的worker个数;
  • count_idle_:记录该线程池当前处于空闲的worker个数,也就是idle_workers的长度;

ThreadPool核心方法:

  • Run(Task*): 执行count_running_加1,并将Task设置到该Worker,
    • 当idle_workers_为空,则创建新的Worker并添加到all_workers_队列头部,count_started_加1;
    • 当idle_workers_不为空,则取走idle_workers_队列头部的Worker,count_idle_减1;
  • Shutdown(): 将all_workers_和idle_workers_队列置为NULL,并将count_running_和count_idle_清零,将关闭的all_workers_个数累加到count_stopped_;
  • SetIdleLocked(Worker*):将该Worker添加到idle_workers_队列头部,count_idle_加1, count_running_减1;
  • ReleaseIdleWorker(Worker*):从all_workers_和idle_workers_队列中移除该Worker,count_idle_减1,count_stopped_加1;

对应关系图:

count_started_count_stopped_count_running_count_idle_
Run()+1(无空闲worker)+1-1(有空闲worker)
Shutdown()+all_workers_个数清零清零
SetIdleLocked()-1+1
ReleaseIdleWorker()+1-1

可见,count_started_ - count_stopped_ = count_running_ + count_idle_;

二、JIT运行模式

2.1 CFE前端编译器

看看dart是如何直接理解并执行dart源码

// gityuan.dart
main() => print('Hello Gityuan!');//dart位于flutter/bin/cache/dart-sdk/bin/dart
$ dart gityuan.dart
Hello, World!

说明:

  • Dart虚拟机并不能直接从Dart源码执行,而是执行dill二进制文件,该二进制文件包括序列化的Kernel AST(抽象语法树)。
  • Dart Kernel是一种从Dart中衍生而来的高级语言,设计之初用于程序分析与转换(transformations)的中间产物,可用于代码生成与后端编译器,该kernel语言有一个内存表示,可以序列化为二进制或文本。
  • 将Dart转换为Kernel AST的是CFE(common front-end)通用前端编译器。
  • 生成的Kernel AST可交由Dart VM、dev_compiler以及dart2js等各种Dart工具直接使用。

image

2.2 kernel service

有一个辅助类isolate叫作kernel service,其核心工作就是CFE,将dart转为Kernel二进制,然后VM可直接使用Kernel二进制运行在主isolate里面运行。

image

2.3 debug运行

将dart代码转换为kernel二进制和执行kernel二进制,这两个过程也可以分离开来,在两个不同的机器执行,比如host机器执行编译,移动设备执行kernel文件。

image

图解:

  • 这个编译过程并不是flutter tools自身完成,而是交给另一个进程frontend_server来执行,它包括CFE和一些flutter专有的kernel转换器。
  • hot reload:热重载机制正是依赖这一点,frontend_server重用上一次编译中的CFE状态,只重新编译实际更改的部分。
2.4 RawClass内部结构

虚拟机内部对象的命名约定:使用C++定义的,其名称在头文件raw_object.h中以Raw开头,比如RawClass是描述Dart类的VM对象,RawField是描述Dart类中的Dart字段的VM对象。

1)将内核二进制文件加载到VM后,将对其进行解析以创建表示各种程序实体的对象。这里采用了懒加载模式,一开始只有库和类的基本信息被加载,内核二进制文件中的每一个实体都会保留指向该二进制文件的指针,以便后续可根据需要加载更多信息。

image

2)仅在以后需要运行时,才完全反序列化有关类的信息。(例如查找类的成员变量,创建类的实例对象等),便会从内核二进制文件中读取类的成员信息。 但功能完整的主体(FunctionNode)在此阶段并不会反序列化,而只是获取其签名。

image

到此,已从内核二进制文件加载了足够的信息以供运行时成功解析和调用的方法。

所有函数的主体都具有占位符code_,而不是实际的可执行代码:它们指向LazyCompileStub,该Stub只是简单地要求系统Runtime为当前函数生成可执行代码,然后对这些新生成的代码进行尾部调用。

image

2.5 查看Kernel文件格式

gen_kernel.dart利用CFE将Dart源码编译为kernel binary文件(也就是dill),可利用dump_kernel.dart能反解kernel binary文件,命令如下所示:

//将hello.dart编译成hello.dill
$ cd <FLUTTER_ENGINE_ROOT>
$ dart third_party/dart/pkg/vm/bin/gen_kernel.dart          \--platform out/android_debug/vm_platform_strong.dill \-o hello.dill                                        \hello.dart//转储AST的文本表示形式
$ dart third_party/dart/pkg/vm/bin/dump_kernel.dart hello.dill hello.kernel.txt

gen_kernel.dart文件,需要平台dill文件,这是一个包括所有核心库(dart:core, dart:async等)的AST的kernel binary文件。如果Dart SDK已经编译过,可直接使用out/ReleaseX64/vm_platform_strong.dill,否则需要使用compile_platform.dart来生成平台dill文件,如下命令:

//根据给定的库列表,来生成platform和outline文件
$ cd <FLUTTER_ENGINE_ROOT>
$ dart third_party/dart/pkg/front_end/tool/_fasta/compile_platform.dart \dart:core                                                        \        third_party/dart/sdk/lib/libraries.json                          \vm_outline.dill vm_platform.dill vm_outline.dill                 
2.6 未优化编译器

首次编译函数时,这是通过未优化编译器来完成的。

image

未优化的编译器分两步生成机器代码:

  • AST -> CFG: 对函数主体的序列化AST进行遍历,以生成函数主体的控制流程图(CFG),CFG是由填充中间语言(IL)指令的基本块组成。此阶段使用的IL指令类似于基于堆栈的虚拟机的指令:它们从堆栈中获取操作数,执行操作,然后将结果压入同一堆栈
  • IL -> 机器指令:使用一对多的IL指令,将生成的CFG直接编译为机器代码:每个IL指令扩展为多条机器指令。

在此阶段没有执行优化,未优化编译器的主要目标是快速生成可执行代码。

2.7 内联缓存

未优化编译过程,编译器不会尝试静态解析任何未在Kernel二进制文件中解析的调用,因此(MethodInvocation或PropertyGet AST节点)的调用被编译为完全动态的。虚拟机当前不使用任何形式的基于虚拟表(virtual table)或接口表(interface table)的调度,而是使用内联缓存实现动态调用。

虚拟机的内联缓存的核心思想是缓存方法解析后的站点结果信息,对于内联缓存最初是为了解决函数的本地代码:

  • 站点调用的特定缓存(RawICData对象)将接受者的类映射到方法,缓存中记录着一些辅助信息,比如方法和基本块的调用频次计数器,该计数器记录着被跟踪类的调用频次;
  • 共享的查找存根,用于实现方法调用的快速路径。该存根在给定的高速缓存中进行搜索,以查看其是否包含与接收者的类别匹配的条目。 如果找到该条目,则存根将增加频率计数器和尾部调用缓存的方法。否则,存根将调用系统Runtime来解析方法实现的逻辑,如果方法解析成功,则将更新缓存,并且随后的调用无需进入系统Runtime。

image

2.8 编译优化

未优化编译器产生的代码执行比较慢,需要自适应优化,通过profile配置文件来驱动优化策略。内联优化,当与某个功能关联的执行计数器达到某个阈值时,该功能将提交给后台优化编译器进行优化。

优化编译的方式与未优化编译的方式相同:通过序列化内核AST来构建未优化的IL。但是,优化编译器不是直接将IL编译为机器码,而是将未优化的IL转换为基于静态单分配(SSA)形式的优化的IL。

对基于SSA的IL通过基于收集到的类型反馈,内联,范围分析,类型传播,表示选择,存储到加载,加载到加载转发,全局值编号,分配接收等一系列经典和Dart特定的优化来进行专业化推测。最后,使用线性扫描寄存器分配器和一个简单的一对多的IL指令。优化编译完成后,后台编译器会请求mutator线程输入安全点,并将优化的代码附加到该函数。下次调用该函数时,它将使用优化的代码。

image

另外,有些函数包含很长的运行循环,因此在函数仍在运行时将执行从未优化的代码切换到优化的代码是有意义的,此过程之所以称为“堆栈替换”(OSR)。

VM还具有可用于控制JIT并使其转储IL以及用于JIT正在编译的功能的机器代码的标志

$ dart --print-flow-graph-optimized         \--disassemble-optimized              \--print-flow-graph-filter=myFunc     \--no-background-compilation          \hel.dart
2.9 反优化

优化是基于统计的,可能出现违反优化的情况

void printAnimal(obj) {print('Animal {');print('  ${obj.toString()}');print('}');
}// 大量调用的情况下,会推测printAnimal假设总是Cat的情况下来优化代码
for (var i = 0; i < 50000; i++)printAnimal(Cat());// 此处出现的是Dog,优化版本失效,则触发反优化
printAnimal(Dog());

每当只要优化版本遇到无法解决的情况,它就会将执行转移到未优化功能的匹配点,然后继续执行,这个恢复过程称为去优化:未优化的功能版本不做任何假设,可以处理所有可能的输入。

虚拟机通常会在执行一次反优化后,放弃该功能的优化版本,然后在以后使用优化的类型反馈再次对其进行重新优化。虚拟机保护编译器进行推测性假设的方式有两种:

  • 内联检查(例如CheckSmi,CheckClass IL指令),以验证假设是否在编译器做出此假设的使用场所成立。例如,将动态调用转换为直接调用时,编译器会在直接调用之前添加这些检查。 在此类检查中发生的取消优化称为“急切优化”,因为它在达到检查时就急于发生。
  • 运行时在更改优化代码所依赖的内容时,将会丢弃优化代码。例如,优化编译器可能会发现某些类从未扩展过,并且在类型传播过程中使用了此信息。 但是,随后的动态代码加载或类最终确定可能会引入C的子类,导致假设无效。此时,运行时需要查找并丢弃所有在C没有子类的假设下编译的优化代码。 运行时可能会在执行堆栈上找到一些现在无效的优化代码,在这种情况下,受影响的帧将被标记为不优化,并且当执行返回时将进行不优化。 这种取消优化称为延迟取消优化,因为它会延迟到控制权返回到优化代码为止。

三、Snapshots运行模式

3.1 通过Snapshots运行

1)虚拟机有能力将isolate的堆(驻留在堆上的对象图)序列化成二进制的快照,启动虚拟机isolate的时候可以从快照中重新创建相同的状态。

image

Snapshot的格式是低级的,并且针对快速启动进行了优化,本质上是要创建的对象列表以及如何将它们连接在一起的说明。那是快照背后的原始思想:代替解析Dart源码并逐步创建虚拟机内部的数据结构,这样虚拟机通过快照中的所有必要数据结构来快速启动isolate。

2)最初,快照不包括机器代码,但是后来在开发AOT编译器时添加了此功能。开发AOT编译器和带代码快照的动机是为了允许虚拟机在由于平台级别限制而无法进行JIT的平台上使用。

带代码的快照的工作方式几乎与普通快照相同,只是有一点点不同:它们包括一个代码部分,该部分与快照的其余部分不同,不需要反序列化。该代码节的放置方式使其可以在映射到内存后直接成为堆的一部分

image

3.2 通过AppJIT Snapshots运行

引入AppJIT快照可减少大型Dart应用程序(如dartanalyzer或dart2js)的JIT预热时间。当这些工具用于小型项目时,它们花费的实际时间与VM花费的JIT编译这些应用程序的时间一样多。

AppJIT快照可以解决此问题:可以使用一些模拟训练数据在VM上运行应用程序,然后将所有生成的代码和VM内部数据结构序列化为AppJIT快照。然后可以分发此快照,而不是以源(或内核二进制)形式分发应用程序。如果出现实际数据上的执行配置文件与培训期间观察到的执行配置文件不匹配,快照开始的VM仍可以采用JIT模式执行。

3.3 通过AppAOT Snapshots运行

AOT快照最初是为无法进行JIT编译的平台引入的,对于无法进行JIT意味着:

  • AOT快照必须包含应用程序执行期间可能调用的每个功能的可执行代码;
  • 可执行代码不得依赖于执行期间可能违反的任何推测性假设

为了满足这些要求,AOT编译过程会进行全局静态分析(类型流分析, TFA),以确定从已知入口点集中可访问应用程序的哪些部分,分配了哪些类的实例以及类型如何在程序中流动。 所有这些分析都是保守的:这意味着它们会在正确性方面出错,与可以在性能方面出错的JIT形成鲜明对比,因为它始终可以取消优化为未优化的代码以实现正确的行为。

然后,所有可能达到的功能都将编译为本地代码,而无需进行任何推测性优化。但是,类型流信息仍用于专门化代码(例如,取消虚拟化调用),编译完所有函数后,即可获取堆的快照。

最终的快照snapshot可以运行在预编译Runtime,该Runtime是Dart VM的特殊变体,其中不包括诸如JIT和动态代码加载工具之类的组件。

image

AOT编译工具没有包含进Dart SDK。

//需要构建正常的dart可执行文件和运行AOT代码的runtime
$ tool/build.py -m release -a x64 runtime dart_precompiled_runtime// 使用AOT编译器来编译APP
$ pkg/vm/tool/precompiler2 hello.dart hello.aot//执行AOT快照
$ out/ReleaseX64/dart_precompiled_runtime hello.aot
Hello, World!
3.3.1 Switchable Calls

1)即使进行了全局和局部分析,AOT编译的代码仍可能包含无法静态的去虚拟化的调用站点。为了补偿此AOT编译代码和运行时,采用JIT中使用的内联缓存技术的扩展。此扩展版本称为可切换呼叫 (Switchable Calls)。

JIT部分已经描述过,与调用站点关联的每个内联缓存均由两部分组成:一个缓存对象(由RawICData实例表示)和一个要调用的本机代码块(例如InlineCacheStub)。在JIT模式下,运行时只会更新缓存本身。但在AOT运行时中,可以根据内联缓存的状态选择同时替换缓存和要调用的本机代码。

image

最初,所有动态呼叫均以未链接状态开始。首次调用此类呼叫站点时,将调用UnlinkedCallStub,它只是调用运行时帮助程序DRT_UnlinkedCall来链接此呼叫站点。

2)如果可能,DRT_UnlinkedCall尝试将呼叫站点转换为单态状态。在这种状态下,呼叫站点变成直接呼叫,该呼叫通过特殊的单态入口点进入方法,该入口点验证接收方是否具有预期的类。

image

在上面的示例中,假设第一次执行obj.method()时,obj是C的实例,而obj.method则解析为C.method。

下次执行相同的调用站点时,它将直接调用C.method,从而绕过任何类型的方法查找过程。但是,它将通过特殊的入口点(已验证obj仍然是C的实例)进入C.method。如果不是这种情况,将调用DRT_MonomorphicMiss并将尝试选择下一个调用站点状态。

3)C.method可能仍然是调用的有效目标,例如obj是C的扩展类但不覆盖C.method的D类的实例。在这种情况下,检查呼叫站点是否可以转换为由SingleTargetCallStub实现的单个目标状态(见RawSingleTargetCache)。

image

此存根基于以下事实:对于AOT编译,大多数类都使用继承层次结构的深度优先遍历来分配整数ID。如果C是具有D0,…,Dn子类的基类,并且没有一个覆盖C.method,则C.:cid <= classId(obj)<= max(D0.:cid,…,Dn .:cid)表示obj.method解析为C.method。在这种情况下,我们可以将类ID范围检查(单个目标状态)用于C的所有子类,而不是与单个类(单态)进行比较

否则,呼叫站点将切换为使用线性搜索内联缓存,类似于在JIT模式下使用的缓存。

image

最后,如果线性数组中的检查数量超过阈值,则呼叫站点将切换为使用类似字典的结构

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

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

相关文章

光谱相机在AI眼镜领域中的应用

一、核心应用场景‌健康监测系统‌‌实时生理指标分析‌&#xff1a;通过眼周皮肤光谱特征&#xff0c;监测血氧(SpO₂)和血红蛋白变化&#xff0c;精度可达2%‌血糖无创检测‌&#xff1a;近红外光谱(900-1700nm)分析泪液成分&#xff0c;临床测试相关系数R0.87‌疲劳度评估‌…

如何通过url打开本地文件文件夹

安装部署 https://github.com/jixn-hu/notion_link_opener 这是我自己开发的一个后端服务&#xff0c;要一直开着 部署好后 会打开一个前端页面填下好你文件或者文件夹 点击生成短链就可以直接打开本地的文件夹了

第一篇:如何在数组中操作数据【数据结构入门】

记录以下自己重温数据结构的笔记&#xff0c;附带自己实现的C代码&#xff0c; 其中部分Python代码是网上教程里的&#xff0c;顺手粘贴过来&#xff0c;做一对比/ &#xff08;Python确实简洁&#xff0c;但是C更好理解不是吗哈哈哈&#xff09;数组的定义 数组&#xff1a;线…

基于STM32的单片机开发复盘

硬件介绍 底盘&#xff1a;幻尔阿克曼底盘&#xff1b;2个直流霍尔电机、1个PWM舵机开发板&#xff1a;幻尔Ros Controller V1.2&#xff08;STM32F407VET6&#xff09;电源&#xff1a;因为是学习阶段&#xff0c;没有配电池&#xff0c;使用120W可调电源&#xff08;3V~12V&a…

面试常问:注册中心宕机,远程调用还能成功吗?

在微服务架构里&#xff0c;注册中心&#xff08;像 Nacos、Eureka、Consul 等&#xff09;是服务发现与治理的核心。可要是注册中心突然宕机&#xff0c;微服务间的远程调用还能顺利进行吗&#xff1f;这是面试时很常被问到的问题&#xff0c;下面我们就来深入剖析。一、远程调…

《用 Python 和 Matplotlib 绘制折线图:从入门到实战的可视化指南》

《用 Python 和 Matplotlib 绘制折线图:从入门到实战的可视化指南》 一、引言:数据可视化的力量,从一张折线图开始 在我多年的开发与教学经历中,最常被问到的问题之一是:“如何让数据更直观?”我的答案始终如一:用图说话。而在众多图表类型中,折线图以其简洁、清晰的…

Seate的XA模式和AT模式

目录 一、XA模式 【1】两阶段提交 【2】Seata的XA模型 【3】优缺点 【4】实现XA模式 二、AT模式 【1】Seata的AT模型 【2】AT与XA的区别 【3】脏写问题 【4】优缺点 【5】实现AT模式 一、XA模式 XA 规范 是 X/Open 组织定义的分布式事务处理&#xff08;DTP&#xf…

CTFHub SSRF通关笔记6:Gopher Redis原理详解与渗透实战

目录 一、SSRF Gopher Redis 1、功能简介 2、攻击原理 &#xff08;1&#xff09;SSR的作用 &#xff08;2&#xff09;Gopher 协议特性 &#xff08;3&#xff09;攻击 Redis 步骤 二、gopherus 1、功能简介 2、攻击Redis服务方法 三、Gopherus安装 1、源码下载 2…

数据结构之二叉树(2)

数据结构之二叉树&#xff08;2&#xff09;1.二叉树的存储结构2.实现顺序结构二叉树2.1何为堆2.2堆的性质2.3堆的定义2.3堆的初始化与销毁3.1向上调整算法3.2向下调整算法4.入堆5.出堆让花成花&#xff0c;让树成树上一次我们学习了树的分类&#xff0c;并初步了解了二叉树。今…

Linux操作系统之Ubuntu

Ubuntu是基于Debian的开源Linux发行版&#xff0c;由Canonical公司维护&#xff0c;以用户友好性和稳定性著称。它广泛应用于个人电脑、服务器及云计算环境&#xff0c;支持多种硬件架构。Ubuntu的特点Ubuntu提供定期更新版本&#xff08;每6个月&#xff09;和长期支持版本&am…

kotlin的函数前面增加suspend关键字的作用

deepseek回答&#xff1a; Kotlin中suspend关键字的作用详解 核心作用 suspend关键字用于标记‌挂起函数‌&#xff0c;使其能够在协程中实现非阻塞的异步操作。 suspend关键字的本质作用 suspend关键字的主要作用是‌标记一个函数为挂起函数‌&#xff0c;使其能够在协程中使用…

Vibe Coding实战项目:用Qwen3-Coder做了个AI跳舞视频生成器

大家好&#xff0c;这里是K姐。 一个 Vibe Coding 的探索者。 前段时间发现通义发起了一个Qwen3-Coder挑战赛&#xff0c;最高奖金有10000元&#xff0c;研究了一下&#xff0c;我发现这个赛道太宽了&#xff0c;不限项目&#xff0c;用 AI Coding 做数据分析、个人Blog、抓取…

Kafka面试精讲 Day 13:故障检测与自动恢复

【Kafka面试精讲 Day 13】故障检测与自动恢复 在“Kafka面试精讲”系列的第13天&#xff0c;我们将深入探讨 Kafka 高可用体系中的关键一环&#xff1a;故障检测与自动恢复机制。作为分布式系统的核心能力&#xff0c;Kafka 如何在 Broker 宕机、网络分区或磁盘故障时快速感知…

【前沿技术拓展Trip Two】具身智能

具身智能&#xff08;Embodied AI&#xff09;的认识&#xff0c;进展&#xff0c;以及为何难以实现 在讲具身智能之前&#xff0c;我们不得不先行介绍一下离身智能与离身认识系统这两个极其相关且更加常见的概念 离身认识系统 其实目前绝大多数的AI&#xff0c;例如DeepSeek&a…

使用electron将vue3网页项目包装成pc客户端

一、准备前工作在项目的根目录 打开命令行工具 安装四个依赖库安装报错的话二、准备工作完成之后&#xff0c;在项目根目录需要有俩个文件在项目根目录创建electron文件夹在vite.config.js中添加配置项在package.json中添加配置项运行命令 npm run electron:build 打包关于mac&…

基于安全抽象模型(SAM)的汽车网络安全防御与攻击分析

摘要自动驾驶汽车比以往任何一种个人出行交通工具都具有更大的受攻击可能性。这主要是因为这类汽车对通信有极高的需求&#xff0c;一方面是出于功能和安全方面的考虑&#xff0c;另一方面则是为了满足舒适性需求。无人驾驶汽车需要与周围环境进行通信的接口、直接连接&#xf…

线扫相机不出图原因总结

1、帧触发信号有问题 线扫相机出图由帧信号决定开始采集,如果没有帧信号线扫相机无法识别开始信号,所以不出图 1)没有给相机帧信号 帧信号是一个短暂的脉冲信号,持续时间不要太长,相机能识别就可以,一般由plc或者控制卡的数字量输出口触发,可以通过监测数字量输出口来确…

开发避坑指南(46):Java Stream 对List的BigDecimal字段进行求和

需求 对int&#xff0c;long类型的数据求和直接用stream().mapToInt()、stream().mapToDouble()&#xff0c;可是没有stream().mapToBigDecimal()这样的方法&#xff0c;那么如何用stream对List的BigDecimal字段进行求和&#xff1f; 代码实现 直接上代码 public class OrderIn…

pycharm如何处理python项目间引用

1. 如何在pycharm中将其它项目添加到打开的项目中 如图所示&#xff1a;文件->打开->附加&#xff08;Attach&#xff09;即可2.如何引用:直接作为一个普通package引用即可 from attack_projectxxx.modulexxx import xxx3.pyinstaller如何编译这种引用其它项目的可执行文…

家庭劳务机器人发展阶段与时间预测

家庭劳务机器人大规模进入家庭不会是一个单一的时间点&#xff0c;而是一个分阶段、渐进式的过程。我们可以将这个进程分为以下几个阶段&#xff0c;并对每个阶段的时间线进行预测&#xff1a;第一阶段&#xff1a;单一功能机器人普及&#xff08;现在 - 2025年&#xff09;这个…