1.Addressables Runtime源码学习之总览
2.vue runtime源码分析学习——day4:createApp
3.如何实现定时任务- Java Timer/TimerTask 源码解析
4.如何编译 dotnet/runtime 源代码
5.Chromium setTimeout/clearTimeout 源码分析
6.Go 语言一次性定时器使用方式和实现原理
Addressables Runtime源码学习之总览
Addressables系统,源码一套封装了资源管理逻辑的解析系统,主要由Editor和Runtime两部分构成。源码其中,解析Editor负责资源的源码分组管理和打包,而Runtime则主要处理资源的解析股票指标源码分享内存管理。
本文将重点分析Runtime部分,源码首先从整体上对其进行概述。解析Runtime主要包括API、源码初始化、解析资源定位、源码资源加载和解析、解析资源管理以及多平台这些部分。源码这些分类主要是解析基于类的功能性进行总结的。
以下标题分类则是源码按照Runtime下代码的目录层级进行划分的。在ResourceManager目录下,包含ResourceProvider目录,其中Provider主要负责各种资源的加载和解析。此外,Manager目录下还包含Operations目录,Operations作为操作类,负责初始化、加载、卸载等操作。这里包含了Operations的基类以及ProviderOperation等。
APIAddressables是一个API的壳,主要包含以下几个成员:
其中,API的接口主要包括以下几种:
AddressablesImpl是关于Addressables接口的具体实现。
AssetLabelReference是Asset Label的引用类,实现了IKeyEvaluator接口。
RuntimeKey对应LabelString。
AssetReference是Addressable Asset的引用,同样实现了IKeyEvaluator接口。
RuntimeKey对应GUID。
包含资源加载和卸载的接口,最终会调用AddressableImpl的接口。
AssetReferenceUILabelRestriction用于限制AssetReference的Label。
IKeyEvaluator提供RuntimeKey的接口。
初始化、资源定位器ResourceLocators、资源管理ResourceManager、资源内容管理器ResourcesProviders、服务Services、公用Utility等模块在编辑中...
vue runtime源码分析学习——day4:createApp
在深入研究vue runtime源码时,我们首先确定了分析的路径和方法。
createApp这个关键入口点位于@vue/runtime-dom包中,它是开发者项目启动的起点。
在开始代码分析前,k线密码指标源码我们选择在packages\vue\__tests__\index.spec.ts中的测试用例进行,通常选择第一个即可,因为这里模拟的是客户端环境,但需确保testEvironment配置正确并配合jsdom库使用。
createApp方法内部包含一些开发环境特有的检查,如injectCompilerOptionsCheck和injectNativeTagCheck,它们在生产环境不会执行。通过Object.defineProperty绑定,可以防止这些检查被意外修改。
createApp的主要任务包括调用ensureRenderer、createAppApi和mount等。其中,ensureRenderer涉及到typescript的重载,而createAppApi则是通过缓存render和hydrate方法,优化性能。
在render部分,我们首次遇到reload,这是与vue-loader中热更新功能的联系点。尽管loader中的reload方法不接受参数,但它们本质上是处理相同逻辑的。
mount方法的核心内容是将js代码转化为DOM,它会处理createVNode和vnode的生成,以及与container._vnode的更新和比对,即旧vnode与新vnode的差异处理。
虽然今天的内容可能略显琐碎,但createApp的总体流程已经清晰了。后续将继续深入解析其他关键部分。
如何实现定时任务- Java Timer/TimerTask 源码解析
日常实现各种服务端系统时,我们一定会有一些定时任务的需求。比如会议提前半小时自动提醒,异步任务定时/周期执行等。那么如何去实现这样的一个定时任务系统呢? Java JDK提供的Timer类就是一个很好的工具,通过简单的API调用,我们就可以实现定时任务。
现在就来看一下java.util.Timer是如何实现这样的定时功能的。
首先,我们来看一下一个使用demo
基本的使用方法:
加入任务的API如下:
可以看到API方法内部都是调用sched方法,其中time参数下一次任务执行时间点,是通过计算得到。period参数为0的话则表示为一次性任务。
那么我们来看一下Timer内部是如何实现调度的。
内部结构
先看一下Timer的组成部分:
Timer有3个重要的模块,分别是 TimerTask, TaskQueue, TimerThread
那么,在加入任务之后,整个Timer是怎么样运行的呢?可以看下面的示意图:
图中所示是简化的逻辑,多个任务加入到TaskQueue中,会自动排序,小程序点击收藏源码队首任务一定是当前执行时间最早的任务。TimerThread会有一个一直执行的循环,从TaskQueue取队首任务,判断当前时间是否已经到了任务执行时间点,如果是则执行任务。
工作线程
流程中加了一些锁,用来避免同时加入TimerTask的并发问题。可以看到sched方法的逻辑比较简单,task赋值之后入队,队列会自动按照nextExecutionTime排序(升序,排序的实现原理后面会提到)。
从mainLoop的源码中可以看出,基本的流程如下所示
当发现是周期任务时,会计算下一次任务执行的时间,这个时候有两种计算方式,即前面API中的
优先队列
当从队列中移除任务,或者是修改任务执行时间之后,队列会自动排序。始终保持执行时间最早的任务在队首。 那么这是如何实现的呢?
看一下TaskQueue的源码就清楚了
可以看到其实TaskQueue内部就是基于数组实现了一个最小堆 (balanced binary heap), 堆中元素根据 执行时间nextExecutionTime排序,执行时间最早的任务始终会排在堆顶。这样工作线程每次检查的任务就是当前最早需要执行的任务。堆的初始大小为,有简单的倍增扩容机制。
TimerTask 任务有四种状态:
Timer 还提供了cancel和purge方法
常见应用
Java的Timer广泛被用于实现异步任务系统,在一些开源项目中也很常见, 例如消息队列RocketMQ的 延时消息/消费重试 中的异步逻辑。
上面这段代码是RocketMQ的延时消息投递任务 ScheduleMessageService 的核心逻辑,就是使用了Timer实现的异步定时任务。
不管是实现简单的异步逻辑,还是构建复杂的任务系统,Java的Timer确实是一个方便实用,而且又稳定的工具类。从Timer的实现原理,我们也可以窥见定时系统的一个基础实现:线程循环 + 优先队列。这对于我们自己去设计相关的系统,也会有一定的启发。
如何编译 dotnet/runtime 源代码
编译 dotnet/runtime 源代码,首先需要环境准备,参考官方文档《在Windows上构建dotnet/runtime的要求》。我的机器仅提前安装了 Visual Studio ,确保按需自行安装。
初次尝试在命令行窗口进入代码所在目录,输入编译命令时,遇到的第一个问题是缺少 Python 3。安装 Python 3 后,发现新问题,社交网络拓扑生成源码下载文件任务中下载地址参数无法识别。查阅 dotnet/runtime 的 issue,找到解决方案,其中发帖者也是中国人,解答了这一疑惑。
为了找到编译过程中的所有错误,运行命令生成日志。使用“MSBuild Structured Log Viewer”打开日志文件,能够清晰地查看到具体的下载地址。按照日志中的提示,下载文件,复制到指定位置解压,成功解决了下载错误。随后,再次编译,直至提示编译成功。
然而,运行 dotnet/runtime 自带的测试用例时,发现找不到指定 dll,进一步发现对应的 dll 已经编译,但默认编译的是 net7.0-Debug 版本,而需要的是 net-Debug。通过使用 build.cmd -h 查看,发现可以指定编译框架版本。因此,再次编译,指定正确的框架版本,最终运行测试成功。
总结,编译 dotnet/runtime 源代码过程中遇到的主要问题,主要是由于访问国外的网速较慢导致的下载问题。通过生成日志、使用“MSBuild Structured Log Viewer”查看下载地址,以及正确指定编译框架版本等方法,成功解决了编译和运行过程中遇到的问题。
Chromium setTimeout/clearTimeout 源码分析
Chromium版本.0..3中setTimeout函数的工作流程涉及大量源码,包括线程、消息循环、任务队列和操作系统定时器函数。本文仅分析setTimeout的关键步骤。
setTimeout函数通过创建包含回调函数和延时时间的action对象,调用DOMTimer::Install进行处理。DOMTimer::Install通过DOMTimerCoordinator::InstallNewTimeout向定时器哈希表timers_插入一个定时器对象,生成唯一timeout_id。
timeout_id由NextID生成,每次调用setTimeout返回递增的值,用于唯一标识每个定时器任务。卡盟系统php源码timers_是一个哈希表,存放定时器对象,与任务一一对应。
创建定时器对象时,通过定时器的延时时间获取任务类型,并将回调函数与任务类型关联,最终通过web_task_runner_获取相应的任务运行器,并在TimerBase::SetNextFireTime调用web_task_runner_->PostDelayedTask提交延迟任务。
PostDelayedTask将延迟任务插入到延迟任务队列中,并更新当前线程的唤醒时间。延迟任务队列是优先队列,用于管理按延时时间排序的任务。
通过GetNextScheduledWakeUpImpl获取优先队列的队头任务,创建唤醒任务用于在线程唤醒时执行延迟任务。唤醒任务只包含延时时间,不包含回调函数。
UpdateDelayedWakeUpImpl根据新创建的唤醒任务更新唤醒任务队列。如果延迟任务队列中的任务延时时间较短,新任务可能无法立即进入唤醒任务队列。
调用操作系统定时器函数,如在Mac下调用CFRunLoopTimerSetNextFireDate,在Windows下调用SetTimer,在Android下调用timerfd_settime,在指定延时后唤醒线程。
线程睡眠后,唤醒线程执行已到期的延迟任务,将到期任务从延迟任务队列移出并加入工作队列。ThreadControllerWithMessagePumpImpl::DoWorkImpl找到并执行工作队列中的任务。
面试题:setTimeout延迟时间不准确的原因可能有:硬件层面的时间不准确、操作系统不保证定时器函数的精确性、CPU处理大量定时任务时可能出现部分任务延迟执行。
clearTimeout与clearInterval功能相同,DOMTimer::RemoveByID从timers_哈希表中移除指定timeout_id对应的定时器对象,将回调函数置空,视为任务取消。
Go 语言一次性定时器使用方式和实现原理
在 Go 语言的标准库time包中,有一个名为Timer的类型,它代表了一个单一事件的计时器,即一次性定时器。
在Go语言的项目开发中,定时器的使用非常普遍。本文将向大家介绍如何在Go语言中使用Timer,以及其背后的实现原理。
要使用Timer一次性定时器,首先需要导入time包。创建Timer的方式有两种:
func NewTimer(d Duration) *Timer
使用func NewTimer创建Timer时,需要传入定时器的等待时间。时间到达时,会向channel中发送当前时间。
示例代码:
通过阅读上面的代码,我们可以看到我们定义了一个2秒后执行的定时器timer,然后使用select读取timer.C中的数据。当读取到数据时,会执行特定的业务逻辑代码。
func AfterFunc(d Duration, f func()) *Timer
使用func AfterFunc创建Timer时,需要传入定时器的等待时间和时间到达时执行的函数。
示例代码:
细心的读者可能已经发现,在代码末尾我们使用了time.Sleep(),这是因为time.AfterFunc()是异步执行的,所以需要等待协程退出。
在Timer的源码中,我们可以看到一个数据结构,它包含两个字段:一个是可导出字段C,这是一个Time类型的channel;另一个是不可导出字段r,这是一个runtimeTimer类型。
实际上,每个Go应用程序底层都会有一个特定的协程来管理Timer。当监控到某个Timer指定的时间到达时,这个协程会将当前时间发送到C中,然后上层读取到C中的数据时,执行相关的业务逻辑代码。
底层协程会监控Timer的r字段中的数据。在源码中查看runtimeTimer的数据结构,我们可以发现其中包含的所有字段。重点了解when、f和arg。
在简单了解Timer的数据结构后,我们查看func NewTimer的代码,可以看到它的实现非常简单。它实际上就是构造了一个Timer,然后把Timer.r传参给startTimer(),除了startTimer()函数外,还有两个函数,分别是when()和sendTime,其中when()是计算计时器的执行时间,sendTime是计时器时间到达时执行的事件(实际上就是将当前时间写入通道中)。
sendTime源码:
我们已经了解到,func NewTimer将构造的Timer.r传参给startTimer(),它负责将runtimeTimer写入底层协程的数组中(如果底层协程未运行,它将会启动底层协程),将Timer交给底层协程监控。也就是说,当底层协程监控到某个Timer指定时间到达时,将当前时间发送到它的通道中。
本文介绍了Go语言标准库time包提供的一次性定时器Timer,不仅介绍了它的使用方式,还介绍了它的实现原理。
限于篇幅,本文没有介绍Stop()和Reset()方法,感兴趣的读者可以查阅相关资料。
ONNX-Runtime一本通:综述&使用&源码分析(持续更新)
ONNX-Runtime详解:架构概览、实践与源码解析
ONNX-Runtime作为异构模型运行框架,其核心机制是先对原始ONNX模型进行硬件无关的图优化,之后根据支持的硬件选择相应的算子库,将模型分解为子模型并发在各个平台执行。它提供同步模式的计算支持,暂不包括异步模式。ORT(onnx-runtime缩写)是主要组件,包含了图优化(graph transformer)、执行提供者(EP)等关键模块。
EP是执行提供者,它封装了硬件特有的内存管理和算子库,可能只支持部分ONNX算子,但ORT的CPU默认支持所有。ORT统一定义了tensor,但EP可有自定义,需提供转换接口。每个推理会话的run接口支持多线程,要求kernel的compute函数是并发友好的。
ORT具有后向兼容性,能运行旧版本ONNX模型,并支持跨平台运行,包括Windows、Linux、macOS、iOS和Android。安装和性能优化是实际应用中的重要步骤。
源码分析深入到ORT的核心模块,如框架(内存管理、tensor定义等)、图结构(构建、排序与修改)、优化器(包括RewriteRule和GraphTransformer),以及平台相关的功能如线程管理、文件操作等。Session是推理流程的管理核心,构造函数初始化模型和线程池,load负责模型反序列化,initialize则进行图优化和准备工作。
ORT中的执行提供者(EP)包括自定义实现和第三方库支持,如TensorRT、CoreML和SNPE。其中,ORT与CoreML和TensorRT的集成通过在线编译,将ONNX模型传递给这些框架进行计算。ORT通过统一的接口管理元框架之上的算子库,但是否支持异构运算(如SNPE与CPU库的混合)仍有待探讨。
总结来说,ONNX-Runtime处理多种模型格式,包括原始ONNX和优化过的ORT模型,以适应多平台和多设备需求。它通过复杂的架构和优化技术,构建了可扩展且高效的推理软件栈,展示了flatbuffer在性能和体积方面的优势。
附录:深入探讨ORT源码编译过程的细节。
pythontime.clock()和time.perf_counter()的区别?
本文旨在解析 Python 时间模块中的 time.clock() 和 time.perf_counter() 两个函数的区别。
首先,time.clock() 函数的使用在 Python 3.6 版本中是可以的,这与官方文档中提到的“Deprecated since version 3.3”描述相符。虽然该函数在较新版本中被标记为废弃,但并未禁止使用。
其次,时间教程中提到 clock 不计算 sleep 时间的原因,与使用 perf_counter 或 process_time 函数作为替代品相呼应。这两种函数在某些意义上具有同等意义,有助于解决 clock 函数可能带来的平台间差异性问题。
深入探究 Python time 模块源代码,我们可以发现 clock 的实现依赖于 time_clock 函数,而 perf_counter 的实现则与之相似。在不同的操作系统平台上,这两种实现方式也会有所不同。
值得注意的是,time.clock() 的返回结果在不同平台上表现不一,这可能导致用户困惑。相比之下,使用 perf_counter 或 process_time 函数能提供更为明确且一致的结果。这些函数在某些关键特性上具有互补性,如在处理 sleep 时间时的表现。
综合上述分析,time.clock() 和 time.perf_counter() 的主要区别在于它们的实现、返回结果的平台依赖性以及在特定操作(如处理 sleep 时间)上的表现。选择合适的函数取决于具体需求,以确保程序的稳定性和跨平台兼容性。
Go 语言设计与实现 笔记 — 定时器源码分析
本文深入探讨了《Go语言设计与实现》一书中的定时器源码分析,旨在为读者提供关于Go语言中定时器实现的全面理解。阅读过程中,结合源码阅读和资料查阅,补充了书中未详细介绍的内容,旨在帮助读者巩固对Go语言调度器和定时器核心机制的理解。
在数据结构部分,重点分析了runtime.timer结构体中的pp字段。该字段在书中虽未详细讲解,但在源码中表明了pp代表了定时器在四叉堆中的P(P为调度器的核心组件)位置。深入理解了pp字段对于后续源码解读的重要性。
进一步,分析了time.Timer与NewTimer之间的关联,以及time.NewTimer函数的实现细节。这一过程揭示了时间间隔设置(when)、时间发送(sendTime)和启动定时器(startTimer)之间的逻辑关系,清晰地展示了NewTimer函数的完整工作流程。
状态机部分详细解析了addtimer、deltimer、cleantimers和modtimer等函数的实现。addtimer函数用于将定时器添加至当前P的timer四叉堆中,deltimer负责修改定时器状态,cleantimers用于清除堆顶的定时器,而modtimer则用于修改定时器的多个属性。通过深入分析这些函数的源码,揭示了定时器状态转换的完整流程。
在清除计时器(cleantimers)和调整计时器(adjusttimers)中,讨论了函数如何处理不同状态的定时器,以及如何在调整定时器时保持堆结构的正确性。这些过程展示了Go语言中定时器管理的精细操作。
运行计时器(runtimer)部分,探讨了定时器执行的条件以及如何在没有定时器执行或第一个定时器未执行时处理返回值。这一分析深入理解了定时器执行机制。
最后,文章触及了定时器触发机制与调度器、网络轮询器之间的关系,这部分内容有待进一步整理和补充。文章末尾强调了定时器执行时间误差的来源,并鼓励读者提供反馈,以促进学习和知识共享。
通过本文,读者能够获得对Go语言定时器实现的深入理解,从数据结构、状态转换到执行机制,全面涵盖了定时器的核心概念。本文章旨在为读者提供一个全面的资源,帮助在实践中更好地应用Go语言定时器功能。
《Android Runtime源码解析》介绍
《Android Runtime源码解析》是我创作的第二本技术专著,于6月底完成印刷,现已在各大电商平台上市。借此机会,我简要介绍本书内容,以便对此感兴趣的朋友能有所了解。
本书以Android .0.0_r源码为基础,从编译器开发者的视角,分析了ART的各个部分及其主要流程,旨在向读者展示ART的基本框架。由于ART发展至今,规模庞大,复杂度较高,很多细节无法完全覆盖。因此,本书选择基本框架进行介绍,以便读者根据个人兴趣深入挖掘感兴趣的细节。
全书内容分为四个部分。第一部分包括第一章,主要介绍ART的基础知识;第二部分包括第二章至第四章,主要介绍ART中的编译器部分,包括dex2oat工具,这部分属于编译时阶段;第三部分包括第五章和第六章,主要介绍ART的启动和运行,属于运行时阶段;第四部分包括第七章,主要介绍ART中的垃圾回收部分。读者可以按照顺序阅读,也可以根据自己的需要选择阅读相关部分,不影响对内容的理解。
各章内容如下:第一章,从虚拟机基础、ART发展历史、ART核心架构和源码目录结构等方面对ART基础进行了介绍;第二章,介绍了dex2oat工具的入口、driver以及DexToDexCompiler等;第三章,分析了OptimizingCompiler中的JNI处理和Compile过程,并对Compile过程中的主要环节进行了详细阐述;第四章,介绍了OptimizingCompiler中硬件平台无关和硬件平台相关的优化,并深入分析了硬件平台无关优化中的典型优化;第五章,分析了ART在启动时的几个主要流程;第六章,分析了ART在执行时的主要流程;第七章,分析了ART GC的整体架构、种类及具体实现。
本书适合新入行的ART开发者以及想了解ART基本情况的各类开发者。
由于作者水平有限,本书中可能存在诸多问题,敬请各位专家批评指正。