1.15+ 张剖析内存分配之 malloc 详解
2.Recast Navigation 源码剖析 01 - Meadow Map论文解析与实验
3.UE4源码剖析——光照贴图(LightMap) 之 由烘焙到渲染流程
4.33张图解析ReentrantReadWriteLock源码
5.Golang源码剖析panic与recover,源码看不懂你打我好了
6.76 张图,剖析剖析 Spring AOP 源码,源码小白居然也能看懂,剖析大神,源码请收下我的剖析微同城源码架构膝盖!
15+ 张剖析内存分配之 malloc 详解
本文将深入剖析内存分配中的源码malloc函数,虽然不详述源码,剖析但重点讲解其实际操作。源码首先,剖析理解malloc分配的源码内存结构至关重要。
当malloc分配内存时,剖析会额外添加首部和尾部。源码如图所示,剖析分配的源码0x字节内存中,浅绿色fill部分是用户请求的,返回的是该区域的起始指针。fill区域周围有预填充的gap,用于区分用户可使用区域和不可使用区域,且在归还时能检测是否越界。
内存管理涉及层次结构,系统在程序启动前会用__cdecl_heap_init分配堆空间,构建管理动态内存的个HEADER节点链表,每个节点管理1MB内存。每个节点结构中包含指向虚拟地址空间的pHeapData,这部分相当于未分配的"门牌号"。
接下来是内存页的划分和管理,新的内存页被分为K大小的段,并按需挂载到链表。分配和归还过程包括从链表查找空间、开辟新group、合并空闲内存以及记录使用情况。当一个group全回收后,不会立即归还系统,而是等待其他group的回收一起操作,以提高效率。
通过以上图解和步骤,我们对malloc的内存分配有了直观的认识。要了解更多细节,可以参考相关视频教程,如"C++开发"系列,以及获取更多C/C++和Linux架构师学习资源。
Recast Navigation 源码剖析 - Meadow Map论文解析与实验
本文深入解析了Meadow Map论文及其在Recast Navigation中的应用。Recast Navigation是一款常见的游戏开发寻路库,源于芬兰开发者Mikko Mononen的初始工作。Meadow Map方法,由Ronald C. Arkin于年提出,为现代Navmesh系统奠定了基础,特别强调长时间存储地图的有效策略。
Meadow Map通过凸多边形化动机,提出了一种优化存储和访问3D地图数据的方法。相较于传统的基于网格的寻路方法,Meadow Map采用凸多边形化来减少节点数量,从而提高性能效率,特别是针对平坦区域。凸多边形化的核心在于利用凸多边形内部任意两点直接相连的特性,构建寻路图。
Recast Navigation系统使用凸多边形化来处理3D场景,通过算法自动将3D场景转换为2.5D形式,以便于寻路。与Meadow Map类似,Recast也采用了基于凸多边形边缘中点作为寻路节点的策略,构建寻路图以供A*算法使用。这种方法简化了搜索空间,提高了寻路效率。
在实现Meadow Map时,需解决多边形分解成多个凸多边形的闲置交易小程序源码问题。此过程通过不断消除多边形中的非凸角,递归生成凸多边形,实现多边形化。同时,处理多边形内部的障碍物(holes)时,需找到与可见顶点相连的内部对角线,将空洞并入多边形内部。
路径改进方面,Recast Navigation采用String Pulling方法,旨在优化路径,避免路径的抖动和非最优行为。这一策略在实际应用中提升了路径质量,使得寻路过程更为流畅。
总之,Meadow Map和Recast Navigation在采用凸多边形化来构建寻路图的基础上,通过不同实现细节和优化策略,有效提高了游戏中的路径寻路效率和性能。通过深入理解这两种方法,游戏开发者可以更好地选择和应用合适的寻路库,以满足不同游戏场景的需求。
UE4源码剖析——光照贴图(LightMap) 之 由烘焙到渲染流程
在离线编辑器阶段,通过构建(Build)按钮启动光照烘焙流程,UE4引擎在构建场景光照、反射球信息、预计算静态网格可见性、构建导航网格、构建HLOD、构建流式贴图等,仅关注光照相关只构建光照(Build Lighting Only)阶段,Lightmass系统负责计算光照,Swarm分布式工具加速并分担计算任务。
Swarm初始化并启动烘焙流程,Startup阶段计算光照构建的关卡与灯光信息,统计静态几何体数据并初始化Swarm,Swarm分为协调与代理程序,负责数据导出与任务分配。AmortizedExport阶段进行分摊式数据导出,SwarmKickoff阶段Swarm全面启动,AsynchronousBuilding阶段消费者程序执行任务,完成光照信息计算。AutoApplyingImport阶段根据配置决定是否自动导入烘焙结果,WaitingForImport与ImportRequested阶段等待导入烘焙数据,Import阶段完成数据导入,Finished阶段地图构建完成。
光照贴图合并大图过程,为每个静态几何体独立生成光照贴图后,UE4将多张贴图尽可能合并到一张大贴图中,以优化IO加载与渲染性能。合并算法简单,通过排序、读取最大尺寸限制与重新摆放光照贴图完成。
贴图像素设置与Mipmap生成,合并后的光照贴图设置像素值,为每种类型的光照贴图创建,最终将数据以真实形式存储。贴图包含SkyOcclusionTexture、AOMaterialMaskTexture、ShadowMapTexture与低分辨率系数贴图。
贴图渲染资源合并中,判断不同几何体使用的贴图集合是否一致,优化判断效率。创建FLightmapClusterResourceInput类代表贴图集合,并统计所有集合用于判断几何体是否使用相同贴图集合。
运行时光照贴图传递到Shader流程包括UE4几何体渲染架构窥探、光照信息存储、赋值LCI与生成渲染批次、死叉底部抬高 源码绑定Shader。FLODInfo类存储光照信息,FMeshBatchElement中设置LCI字段,FBasePassMeshProcessor绑定贴图集合到Shader。在Shader代码中访问LightmapResourceCluster变量访问贴图集合中的光照贴图。
UE4通过Swarm分布式框架、Lightmass光照系统与优化的贴图合并与传递流程,实现了高效、实时的光照计算与渲染。
以上内容详细介绍了UE4引擎中光照贴图从烘焙到渲染的完整流程,包括分布式工具、数据合并、贴图存储与Shader访问,实现了高性能的光照计算与渲染。
张图解析ReentrantReadWriteLock源码
今天,我们深入探讨ReentrantReadWriteLock源码,解析其内部结构与工作原理。文章分为多个部分,逐一剖析读写锁的创建、获取与释放过程。读写锁规范与实现
ReentrantReadWriteLock(简称RRW)作为读写锁,其核心功能在于控制并发访问的读与写操作。为了规范读写锁的使用,RRW首先声明了ReadWriteLock接口,并通过ReadLock与WriteLock实现接口,确保读锁与写锁的正确操作。 为了实现锁的基本功能,WriteLock与ReadLock都继承了Lock接口。这些类内部依赖于AQS(AbstractQueuedSynchronizer)抽象类,AQS为加锁和解锁过程提供了统一的模板函数,简化了锁实现的复杂性。核心组件与流程
AQS提供了一套多线程访问共享资源的同步模板,包括tryAcquire、release等核心抽象函数。WriteLock与ReadLock通过继承Sync类,实现了AQS中的tryAcquire、release(写锁)和tryAcquireShared、tryReleaseShared(读锁)函数。 Sync类在ReentrantReadWriteLock中扮演关键角色,它不仅实现了AQS的抽象函数,还通过位运算优化了读写锁状态的存储,减少了资源消耗。此外,Sync类还定义了HoldCounter与ThreadLocalHoldCounter,进一步管理锁的状态与操作。公平与非公平策略
为了适应不同场景的需求,ReentrantReadWriteLock支持公平与非公平策略。通过Sync类的FairSync与NonfairSync子类,实现了读锁与写锁的阻塞控制。公平策略确保了线程按顺序获取锁,而非公平策略允许各线程独立竞争。全局图与细节解析
文章最后,构建了一张全局图,清晰展示了ReentrantReadWriteLock的各个组件及其相互关系。通过深入细节,分别解释了读写锁的创建、获取与释放过程。以Lock接口的lock与unlock方法为主线,追踪了从Sync类出发的实现路径,包括tryAcquire、tryRelease等核心函数,以及它们在流程图中的表现。 总结,ReentrantReadWriteLock通过继承AQS并扩展公平与非公平策略,实现了高效、灵活的在post中修改源码读写锁功能。通过精心设计的Sync类及其相关组件,确保了多线程环境下的并发控制与资源访问优化。深入理解其内部实现,有助于在实际项目中更好地应用读写锁,提升并发性能与系统稳定性。Golang源码剖析panic与recover,看不懂你打我好了
哈喽,大家好,我是asong,今天与大家来聊一聊go语言中的"throw、try.....catch{ }"。如果你之前是一名java程序员,我相信你一定吐槽过go语言错误处理方式,但是这篇文章不是来讨论好坏的,我们本文的重点是带着大家看一看panic与recover是如何实现的。上一文我们讲解了defer是如何实现的,但是没有讲解与defer紧密相连的recover,想搞懂panic与recover的实现也没那么简单,就放到这一篇来讲解了。废话不多说,直接开整。
Go 语言中panic 关键字主要用于主动抛出异常,类似 java 等语言中的 throw 关键字。panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
Go 语言中recover 关键字主要用于捕获异常,让程序回到正常状态,类似 java 等语言中的 try ... catch 。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
recover只能在defer中使用这个在标准库的注释中已经写明白了,我们可以看一下:
这里有一个要注意的点就是recover必须要要在defer函数中使用,否则无法阻止panic。最好的验证方法是先写两个例子:
运行我们会发现example2()方法的panic是没有被recover住的,导致整个程序直接crash了。这里大家肯定会有疑问,为什么直接写recover()就不能阻止panic了呢。我们在 详解defer实现机制(附上三道面试题,我不信你们都能做对)讲解了defer实现原理,一个重要的知识点**defer将语句放入到栈中时,也会将相关的值拷贝同时入栈。**所以defer recover()这种写法在放入defer栈中时就已经被执行过了,panic是发生在之后,所以根本无法阻止住panic。
通过运行结果可以看出panic不会影响defer函数的使用,所以他是安全的。
这里我开了两个协程,一个协程会发生panic,导致程序崩溃,但是只会执行自己所在Goroutine的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutine 在 panic 时也不应该执行其他 Goroutine 的延迟函数。
其实我们在实际项目开发中,经常会遇到panic问题, Go 的 runtime 代码中很多地方都调用了 panic 函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现panic,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover,他已经为我们兜住底,比如gin,我们看一看他是怎么做的。
我们先来写个简单的代码,看看他的汇编调用:执行go tool compile -N -l -S main.go就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是克隆源码编辑器画红线的三处,第一步调用runtime.deferprocStack创建defer对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer总共有三种模型,编译一个函数里只会有一种defer模式。在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:
简单介绍一下上面的字段:
上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。
下面就开始我们的重点吧~。
在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:简单介绍一下上面的字段:上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。写个简单的例子:运行上面的例子你就会发现,即使在主goroutine中调用了runtime.Goexit,其他goroutine是没有任何影响的。所以结构中的pc、sp、goexit三个字段都是为了修复runtime.Goexit,这三个字段就是为了保证该函数的一定会生效,因为如果在defer中发生panic,那么goexit函数就会被取消,所以才有了这三个字段做保护。看这个例子:
英语好的可以看一看这个: github.com/golang/go/is...,这就是上面的一个例子,这里就不过多解释了,了解就好。
接下来我们再来看一看gopanic方法。
gopanic的代码有点长,我们一点一点来分析:
根据不同的类型判断当前发生panic错误,这里没什么多说的,接着往下看。
上面的代码都是截段,这些部分都是为了判断当前defer是否可以使用开发编码模式,具体怎么操作的就不展开了。
在第三部分进行defer内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover把recoverd = true,具体这个函数的操作留在下面讲,因为runtime.gorecover函数并不包含恢复程序的逻辑,程序的恢复是在gopanic中执行的。先看一下代码:
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... },正常recover是会绕过Goexit的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit也会被recover住,这里是通过从runtime._panic中取出了程序计数器pc和栈指针sp并且调用runtime.recovery函数触发goroutine的调度,调度之前会准备好 sp、pc 以及函数的返回值。
第二部分主要是做panic的recover,这也与上面的流程基本差不多,他是从runtime._defer中取出了程序计数器pc和栈指针sp并调用recovery函数触发Goroutine,跳转到recovery函数是通过runtime.call进行的,我们看一下其源码(src/runtime/asm_amd.s 行):
因为go语言中的runtime环境是有自己的堆栈和goroutine,recovery函数也是在runtime环境执行的,所以要调度到m->g0来执行recovery函数,我们在看一下recovery函数:
在recovery 函数中,利用 g 中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo 重新调度 g , goroutine 继续执行,recovery在调度过程中会将函数的返回值设置为1。这个有什么作用呢? 在deferproc函数中找到了答案:
当延迟函数中recover了一个panic时,就会返回1,当 runtime.deferproc 函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。
在这里runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics 打印出全部的 panic 消息以及调用时传入的参数。
这就是这个逻辑流程,累死我了。。。。
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic,要注意这些:这几个是比较典型的,还有很多会发生panic的地方,交给你们自行学习吧~。
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
张图,剖析 Spring AOP 源码,小白居然也能看懂,大神,请收下我的膝盖!
本文将简要介绍AOP(面向切面编程)的基础知识与使用方法,并深入剖析Spring AOP源码。首先,我们需要理解AOP的基本概念。
1. **基础知识
**1.1 **什么是AOP?
**AOP全称为Aspect Oriented Programming,即面向切面编程。AOP的思想中,周边功能(如性能统计、日志记录、事务管理等)被定义为切面,核心功能与切面功能独立开发,然后将两者“编织”在一起,这就是AOP的核心。
AOP能够将与业务无关、却为业务模块共同调用的逻辑封装,减少系统重复代码,降低模块间的耦合度,有利于系统的可扩展性和可维护性。
1.2 **AOP基础概念
**解释较为官方,以下用“方言”解释:AOP包括五种通知分类。
1.3 **AOP简单示例
**创建`Louzai`类,添加`LouzaiAspect`切面,并在`applicationContext.xml`中配置。程序入口处添加`"睡觉"`方法并添加前置和后置通知。接下来,我们将探讨Spring内部如何实现这一过程。
1.4 **Spring AOP工作流程
**为了便于理解后面的源码,我们将整体介绍源码执行流程。整个Spring AOP源码分为三块,结合示例进行讲解。
第一块是前置处理,创建`Louzai`Bean前,遍历所有切面信息并存储在缓存中。第二块是后置处理,创建`Louzai`Bean时,主要处理两件事。第三块是执行切面,通过“责任链+递归”执行切面。
2. **源码解读
**注意:Spring版本为5.2..RELEASE,否则代码可能不同!这里,我们将从原理部分开始,逐步深入源码。
2.1 **代码入口
**从`getBean()`函数开始,进入创建Bean的逻辑。
2.2 **前置处理
**主要任务是遍历切面信息并存储。
这是重点!请务必注意!获取切面信息流程结束,后续操作都从缓存`advisorsCache`获取。
2.2.1 **判断是否为切面
**执行逻辑为:判断是否包含切面信息。
2.2.2 **获取切面列表
**进入`getAdvice()`,生成切面信息。
2.3 **后置处理
**主要从缓存拿切面,与`Louzai`方法匹配,创建AOP代理对象。
进入`doCreateBean()`,执行后续逻辑。
2.3.1 **获取切面
**首先,查看如何获取`Louzai`的切面列表。
进入`buildAspectJAdvisors()`,方法用于存储切面信息至缓存`advisorsCache`。随后回到`findEligibleAdvisors()`,从缓存获取所有切面信息。
2.3.2 **创建代理对象
**有了`Louzai`的切面列表,开始创建AOP代理对象。
这是重点!请仔细阅读!这里有两种创建AOP代理对象方式,我们选择使用Cglib。
2.4 **切面执行
**通过“责任链+递归”执行切面与方法。
这部分逻辑非常复杂!接下来是“执行切面”最核心的逻辑,简述设计思路。
2.4.1 **第一次递归
**数组第一个对象执行`invoke()`,参数为`CglibMethodInvocation`。
执行完毕后,继续执行`CglibMethodInvocation`的`process()`。
2.4.2 **第二次递归
**数组第二个对象执行`invoke()`。
2.4.3 **第三次递归
**数组第三个对象执行`invoke()`。
执行完毕,退出递归,查看`invokeJoinpoint()`执行逻辑,即执行主方法。回到第三次递归入口,继续执行后续切面。
切面执行逻辑已演示,直接查看执行方法。
流程结束时,依次退出递归。
2.4.4 **设计思路
**这部分代码研究了大半天,因为这里不是纯粹的责任链模式。
纯粹的责任链模式中,对象内部有一个自身的`next`对象,执行当前对象方法后,启动`next`对象执行,直至最后一个`next`对象执行完毕,或中途因条件中断执行,责任链退出。
这里`CglibMethodInvocation`对象内部无`next`对象,通过`interceptorsAndDynamicMethodMatchers`数组控制执行顺序,依次执行数组中的对象,直至最后一个对象执行完毕,责任链退出。
这属于责任链,实现方式不同,后续会详细剖析。下面讨论类之间的关系。
主对象为`CglibMethodInvocation`,继承于`ReflectiveMethodInvocation`,`process()`的核心逻辑在`ReflectiveMethodInvocation`中。
`ReflectiveMethodInvocation`的`process()`控制整个责任链的执行。
`ReflectiveMethodInvocation`的`process()`方法中,包含一个长度为3的数组`interceptorsAndDynamicMethodMatchers`,存储了3个对象,分别为`ExposeInvocationInterceptor`、`MethodBeforeAdviceInterceptor`、`AfterReturningAdviceInterceptor`。
注意!这3个对象都继承了`MethodInterceptor`接口。
每次`invoke()`调用时,都会执行`CglibMethodInvocation`的`process()`。
是否有些困惑?别着急,我将再次帮你梳理。
对象与方法的关系:
可能有同学疑惑,`invoke()`的参数为`MethodInvocation`,没错!但`CglibMethodInvocation`也继承了`MethodInvocation`,可自行查看。
执行逻辑:
设计巧妙之处在于,纯粹的责任链模式中,`next`对象需要保证类型一致。但这里3个对象内部没有`next`成员,不能直接使用责任链模式。怎么办呢?就单独设计了`CglibMethodInvocation.process()`,通过无限递归`process()`实现责任链逻辑。
这就是我们为什么要研究源码,学习优秀的设计思路!
3. **总结
**本文首先介绍了AOP的基本概念与原理,通过示例展示了AOP的应用。之后深入剖析了Spring AOP源码,分为三部分。
本文是Spring源码解析的第三篇,感觉是难度较大的一篇。图解代码花费了6个小时,整个过程都沉浸在代码的解析中。
难度不在于抠图,而是“切面执行”的设计思路,即使流程能走通,将设计思想总结并清晰表达给读者,需要极大的耐心与理解能力。
今天的源码解析到此结束,有关Spring源码的学习,大家还想了解哪些内容,欢迎留言给楼仔。
Spark-Submit 源码剖析
直奔主题吧:
常规Spark提交任务脚本如下:
其中几个关键的参数:
再看下cluster.conf配置参数,如下:
spark-submit提交一个job到spark集群中,大致的经历三个过程:
代码总Main入口如下:
Main支持两种模式CLI:SparkSubmit;SparkClass
首先是checkArgument做参数校验
而sparksubmit则是通过buildCommand来创建
buildCommand核心是AbstractCommandBuilder类
继续往下剥洋葱AbstractCommandBuilder如下:
定义Spark命令创建的方法一个抽象类,SparkSubmitCommandBuilder刚好是实现类如下
SparkSubmit种类可以分为以上6种。SparkSubmitCommandBuilder有两个构造方法有参数和无参数:
有参数中根据参数传入拆分三种方式,然后通过OptionParser解析Args,构造参数创建对象后核心方法是通过buildCommand,而buildCommand又是通过buildSparkSubmitCommand来生成具体提交。
buildSparkSubmitCommand会返回List的命令集合,分为两个部分去创建此List,
第一个如下加入Driver_memory参数
第二个是通过buildSparkSubmitArgs方法构建的具体参数是MASTER,DEPLOY_MODE,FILES,CLASS等等,这些就和我们上面截图中是对应上的。是通过OptionParser方式获取到。
那么到这里的话buildCommand就生成了一个完成sparksubmit参数的命令List
而生成命令之后执行的任务开启点在org.apache.spark.deploy.SparkSubmit.scala
继续往下剥洋葱SparkSubmit.scala代码入口如下:
SparkSubmit,kill,request都支持,后两个方法知识支持standalone和Mesos集群方式下。dosubmit作为函数入口,其中第一步是初始化LOG,然后初始化解析参数涉及到类
SparkSubmitArguments作为参数初始化类,继承SparkSubmitArgumentsParser类
其中env是测试用的,参数解析如下,parse方法继承了SparkSubmitArgumentsParser解析函数查找 args 中设置的--选项和值并解析为 name 和 value ,如 --master yarn-client 会被解析为值为 --master 的 name 和值为 yarn-client 的 value 。
这之后调用SparkSubmitArguments#handle(MASTER, "yarn-client")进行处理。
这个函数也很简单,根据参数 opt 及 value,设置各个成员的值。接上例,parse 中调用 handle("--master", "yarn-client")后,在 handle 函数中,master 成员将被赋值为 yarn-client。
回到SparkSubmit.scala通过SparkSubmitArguments生成了args,然后调用action来匹配动作是submit,kill,request_status,print_version。
直接看submit的action,doRunMain执行入口
其中prepareSubmitEnvironment初始化环境变量该方法返回一个四元 Tuple ,分别表示子进程参数、子进程 classpath 列表、系统属性 map 、子进程 main 方法。完成了提交环境的准备工作之后,接下来就将启动子进程。
runMain则是执行入口,入参则是执行参数SparkSubmitArguments
Main执行非常的简单:几个核心步骤
先是打印一串日志(可忽略),然后是创建了loader是把依赖包jar全部导入到项目中
然后是MainClass的生成,异常处理是ClassNotFoundException和NoClassDeffoundError
再者是生成Application,根据MainClass生成APP,最后调用start执行
具体执行是SparkApplication.scala,那么继续往下剥~
仔细阅读下SparkApplication还是挺深的,所以打算另外写篇继续深入研读~
图文剖析 big.js 四则运算源码
big.js是一个小型且高效的JavaScript库,专门用于处理任意精度的十进制算术。
在常规项目中,算术运算可能会导致精度丢失,从而影响结果的准确性。big.js正是为了解决这一问题而设计的。与big.js类似的库还有bignumber.js和decimal.js,它们同样由MikeMcl创建。
作者在这里详细阐述了这三个库之间的区别。big.js是最小、最简单的任意精度计算库,它的方法数量和体积都是最小的。bignumber.js和decimal.js存储值的进制更高,因此在处理大量数字时,它们的速度会更快。对于金融类应用,bignumber.js可能更为合适,因为它能确保精度,除非涉及到除法操作。
本文将剖析big.js的解析函数和加减乘除运算的源码,以了解作者的设计思路。在四则运算中,除法运算最为复杂。
创建Big对象时,new操作符是可选的。构造函数中的关键代码如下,使用构造函数时可以不带new关键字。如果传入的参数已经是Big的实例对象,则复制其属性,否则使用parse函数创建属性。
parse函数为实例对象添加三个属性,这种表示与IEEE 双精度浮点数的存储方式类似。JavaScript的Number类型就是使用位二进制格式IEEE 值来表示的,其中位用于表示3个部分。
以下分析parse函数转化的详细过程,以Big('')、Big('0.')、Big('e2')为例。注意:Big('e2')中e2以字符串形式传入才能检测到e,Number形式的Big(e2)在执行parse前会被转化为Big()。
最后,Big('')、Big('-0.')、Big('e2')将转换为...
至此,parse函数逻辑结束。接下来分别剖析加减乘除运算。
加法运算的源码中,k用于保存进位的值。上面的过程可以用图例表示...
减法运算的源码与加法类似,这里不再赘述。减法的核心逻辑如下...
减法的过程可以用图例表示,其中xc表示被减数,yc表示减数...
乘法运算的源码中,主要逻辑如下...
描述的是我们以前在纸上进行乘法运算的过程。以*为例...
除法运算中,对于a/b,a是被除数,b是除数...
注意事项:big.js使用数组存储值,类似于高精度计算,但它是在数组中每个位置存储一个值,然后对每个位置进行运算。对于超级大的数字,big.js的算术运算可能不如bignumber.js快...
在使用big.js进行运算时,有时没有设置足够大的精度会导致结果不准确...
总结:本文剖析了big.js的解析函数和四则运算源码,用图文详细描述了运算过程,逐步还原了作者的设计思路。如有不正确之处或不同见解,欢迎各位提出。