欢迎来到皮皮网网首页

【云谷主机源码】【py源码如何】【怎么开源码】sync源码推荐

来源:小程序企业汇报源码 时间:2024-12-24 04:06:37

1.PyTorch 源码解读之 BN & SyncBN:BN 与 多卡同步 BN 详解
2.golang的源码对象池sync.pool源码解读
3.onnxruntime源码学习-编译与调试 (公网&内网)
4.Go并发编程之原子操作sync/atomic
5.Go并发编程 — sync.Once
6.Golang sync.Cond 条件变量源码分析

sync源码推荐

PyTorch 源码解读之 BN & SyncBN:BN 与 多卡同步 BN 详解

       BatchNorm原理

       BatchNorm最早在全连接网络中提出,旨在对每个神经元的推荐输入进行归一化操作。在卷积神经网络(CNN)中,源码这一原理被扩展为对每个卷积核的推荐输入进行归一化,即在channel维度之外的源码所有维度上进行归一化。BatchNorm带来的推荐云谷主机源码优势包括提高网络的收敛速度、稳定训练过程、源码减少过拟合现象等。推荐

       BatchNorm的源码数学表达式为公式[1],引入缩放因子γ和移位因子β,推荐作者在文章中解释了它们的源码作用。

       PyTorch中与BatchNorm相关的推荐类主要位于torch.nn.modules.batchnorm模块中,包括如下的源码类:_NormBase、BatchNormNd。推荐

       具体实现细节如下:

       _NormBase类定义了BN相关的源码一些属性。

       初始化过程。

       模拟BN的forward过程。

       running_mean、running_var的更新逻辑。

       γ、β参数的更新方式。

       BN在eval模式下的行为。

       BatchNormNd类包括BatchNorm1d、BatchNorm2d、BatchNorm3d,它们的区别在于检查输入的合法性,BatchNorm1d接受2D或3D的输入,BatchNorm2d接受4D的输入,BatchNorm3d接受5D的输入。

       接着,介绍SyncBatchNorm的实现。

       BN性能与batch size密切相关。py源码如何在batch size较小的场景中,如检测任务,内存占用较高,单张显卡难以处理较多,导致BN效果不佳。SyncBatchNorm提供了解决方案,其原理是所有计算设备共享同一组BN参数,从而获得全局统计量。

       SyncBatchNorm在torch/nn/modules/batchnorm.py和torch/nn/modules/_functions.py中实现,前者负责输入合法性检查以及参数设置,后者负责单卡统计量计算和进程间通信。

       SyncBatchNorm的forward过程。

       复习方差计算方式。

       单卡计算均值、方差,进行归一化处理。

       同步所有卡的数据,得到全局均值mean_all和逆标准差invstd_all,计算全局统计量。

       接着,介绍SyncBatchNorm的backward过程。

       在backward过程中,需要在BN前后进行进程间通信。这在_functions.SyncBatchNorm中实现。

       计算weight、bias的梯度以及γ、β,进一步用于计算梯度。

golang的对象池sync.pool源码解读

       Go语言对象池sync.pool源码深度解析

       对象池在Go语言中被设计用于解决频繁创建和销毁对象导致的性能问题。sync.pool的核心理念是复用已创建对象,减轻垃圾收集(GC)压力。怎么开源码以下是关键点的理解和代码分析:

       对象池的动机

       新对象的创建会消耗内存,并可能对GC造成负担。sync.pool就是为了解决这个问题,通过预先创建和存储对象,减少创建成本,提高性能。

       池与缓存的相似性

       无论是连接池、线程池还是对象池,它们都体现了池化和缓存的思想:复用资源,减少临时创建,提升响应速度。池化和缓存都是为了减少资源消耗,提升服务效率。

       go1.原理与用法

       对象池使用简单,通过New函数创建,Get和Put操作实现对象的复用。go1.之前的版本可能频繁清空池,导致性能损失。1.改进了设计,引入了victim cache机制,通过双向链表优化获取和存储对象,减少锁竞争。

       源码解析

       从pool的结构体可以看到,victim和victimSize用于管理受害缓存,popTail函数通过无锁操作处理链表,保证了高性能。put操作时,根据对象状态决定放入private或shared区域。

       总结

       对象池通过复用对象、提前准备和性能优化的存储提高性能。理解对象池的主页模板源码关键在于:复用、存储策略和并发控制。在Go 1.中,通过victim cache和链表操作,进一步提升了性能和并发处理能力。

       深入理解

       理解对象池的细节包括如何禁用抢占P以防止GC影响,以及如何通过noCopy防止对象拷贝导致的潜在问题。同时,伪共享的处理也是优化对象池性能的关键点。

       持续学习和实践是技术成长的基石,让我们保持对技术的热情,不断探索和优化。

onnxruntime源码学习-编译与调试 (公网&内网)

       在深入学习ONNX Runtime的过程中,我决定从1.版本开始,以对比与理解多卡并行技术。为此,我选择了通过`./tools/ci_build/build.py`脚本进行编译,而不是直接执行`build.sh`,因为后者并不直接提供所需的参数。在`build.py:::parse_arguments()`函数中,我找到了可选择的参数,例如运行硬件(CPU/GPU)、调试模式(Debug/Release)以及是否并行编译。我特别使用了`--skip_submodule_sync`,以避免因与公网不通而手动下载“submodule”,即`./cmake/external`文件夹下的依赖组件。这样可以节省每次编译时检查依赖组件更新的时间,提高编译效率。同时,我使用`which nvcc`命令来确定`cuda_home`和`cudnn_home`的值。

       我的编译环境配置为gcc8.5.0、cuda.7和cmake3..1,祭祀扫墓源码其中cmake版本需要不低于3.,gcc版本则至少为7.0,否则编译过程中会出现错误。在编译环境的配置中,可以通过设置PATH和LD_LIBRARY_PATH来指定可执行程序和动态库的路径。对于手动下载“submodule”的不便,可以通过先在公网编译cpu版本,然后在编译开始阶段由构建脚本自动下载所有依赖组件并拷贝至所需目录来简化流程。

       编译顺利完成后,生成的so文件并未自动放入bin目录,这可能是由于在安装步骤后bin目录下才会出现相应的文件。接下来,我进入了调试阶段,使用vscode进行调试,最终成功运行了`build/RelWithDebInfo/onnxruntime_shared_lib_test`可执行文件。

       在深入研究ONNX Runtime的编译流程时,我发现了一个更深入的资源,它涵盖了从`build.sh`到`build.py`再到`CmakeList.txt`的编译过程,以及上述流程中涉及的脚本解析。对这个流程感兴趣的读者可以进行更深入的研究。

       在编译过程中,我遇到了一些问题,如下载cudnn并进行安装,以及解决找不到`stdlib.h`的问题。对于找不到`stdlib.h`,我通过查阅相关文章和理解编译过程中搜索路径的逻辑,最终找到了解决方案。如果忽略这个问题,我选择在另一台机器上重新编译以解决问题。

       在使用vscode调试时,我遇到了崩溃问题,这可能是由于vscode、gdb或Debug模式编译出的可执行文件存在潜在问题。通过逐步排除,我最终确定问题可能出在Debug模式编译的可执行文件上。这一系列的探索和解决过程,不仅加深了我对ONNX Runtime的理解,也提高了我的调试和问题解决能力。

Go并发编程之原子操作sync/atomic

       Go语言的并发编程中,sync/atomic包提供了底层的原子内存操作,用于处理并发环境中的数据同步和冲突避免。这个包利用了CPU的原子操作指令,确保在并发情况下,对变量的操作是线程安全的。然而,官方建议仅在必要且确实涉及底层操作时使用,如避免使用channel或sync包中的锁的场景。

       sync/atomic包的核心是5种基本数据类型的原子操作:add(只支持int、int、uint、uint和uintptr),以及一个扩展的Value类型,后者在1.4版本后支持Load、Store、CompareAndSwap和Swap方法,可用于操作任意类型的数据。Value类型尤其重要,因为它扩展了原子操作的适用范围。

       具体来说,swap操作(如SwapInt)用于原子地替换内存中的值,compare-and-swap(CAS)则检查并替换值,如果当前值与预期值一致。add操作(如AddInt)则进行加法操作并返回新值,而load、store操作分别用于读取和写入值,如LoadInt和StoreInt。

       在实际使用时,例如对map的并发读写,可以通过Value类型避免加锁。sync/atomic的相关源码和示例可在GitHub的教程[1]和作者的个人网站[2]中找到。至于进一步学习,可以关注公众号coding进阶获取更多资源,或者在知乎[3]上查找无忌的资料。

       

参考资料:

       [1] Go语言初级、中级和高级教程: github.com/jincheng9/go...

       [2] Jincheng's Blog: jincheng9.github.io/

       [3] 无忌: zhihu.com/people/thucuh...

Go并发编程 — sync.Once

       ç®€ä»‹

       Once 可以用来执行某个函数,但是这个函数仅仅只会执行一次,常常用于单例对象的初始化场景。说到这,就不得不说一下单例模式了。

单例模式

       å•ä¾‹æ¨¡å¼æœ‰æ‡’汉式和饿汉式两种,上代码。

饿汉式

       é¥¿æ±‰å¼é¡¾åæ€ä¹‰å°±æ˜¯æ¯”较饥饿,所以就是上来就初始化。

var?instance?=?&Singleton{ }type?Singleton?struct?{ }func?GetInstance()?*Singleton?{ return?instance}懒汉式

       æ‡’汉式顾名思义就是偷懒,在获取实例的时候在进行初始化,但是懒汉式会有并发问题。并发问题主要发生在 instance == nil 这个判断条件上,有可能多个 goruntine 同时获取 instance 对象都是 nil ,然后都开始创建了 Singleton 实例,就不满足单例模式了。

var?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}加锁

       æˆ‘们都知道并发问题出现后,可以通过加锁来进行解决,可以使用 sync.Metux 来对整个方法进行加锁,就例如下面这样。这种方式是解决了并发的问题,但是锁的粒度比较高,每次调用 GetInstance 方法的时候都需要获得锁才能获得 instance 实例,如果在调用频率比较高的场景下性能就不会很好。那有什么方式可以解决嘛?让我们接着往下看吧

var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ mutex.Lock()defer?mutex.Unlock()if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}Double Check

       ä¸ºäº†è§£å†³é”çš„粒度问题,我们可以使用 Double Check 的方式来进行解决,例如下面的代码,第一次判断 instance == nil 之后需要进行加锁操作,然后再第二次判断 instance == nil 之后才能创建实例。这种方式对比上面的案例来说,锁的粒度更低,因为如果 instance != nil 的情况下是不需要加锁的。但是这种方式实现起来是不是比较麻烦,有没有什么方式可以解决呢?

var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?mutex.Lock()?defer?mutex.Unlock()?if?instance?==?nil?{ ?instance?=?&Singleton{ }?}}return?instance}使用 sync.Once

       å¯ä»¥ä½¿ç”¨ sync.Once 来实现单例的初始化逻辑,因为这个逻辑至多只会跑一次。推荐使用这种方式来进行单例的初始化,当然也可以使用饿汉式。

var?once?sync.Oncevar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ once.Do(func()?{ ?instance?=?&Singleton{ }})return?instance}源码分析

       ä¸‹é¢å°±æ˜¯ sync.Once 包的源码,我删除了注释,代码不多,Once 数据结构主要由 done 和 m 组成,其中 done 是存储 f 函数是否已执行,m 是一个锁实例。

type?Once?struct?{ done?uint?//?f函数是否已执行mMutex?//?锁}func?(o?*Once)?Do(f?func())?{ if?atomic.LoadUint(&o.done)?==?0?{ ?o.doSlow(f)}}func?(o?*Once)?doSlow(f?func())?{ o.m.Lock()defer?o.m.Unlock()if?o.done?==?0?{ ?defer?atomic.StoreUint(&o.done,?1)?f()}}

       Do 方法

       ä¼ å…¥ä¸€ä¸ª function,然后 sync.Once 来保证只执行一次,在 Do 方法中使用 atomic 来读取 done 变量,如果是 0 ,就代码 f 函数没有被执行过,然后就调用 doSlow方法,传入 f 函数

       doShow 方法

       doShow 的第一个步骤就是先加锁,这里加锁的目的是保证同一时刻是能由一个 goruntine 来执行 doSlow 方法,然后再次判断 done 是否是 0 ,这个判断就相当于我们上面说的 DoubleCheck ,因为 doSlow 可能存在并发问题。然后执行 f 方法,然后执行使用 atomic 将 done 保存成 1。使用 DoubleCheck 保证了 f 方法只会被执行一次。

       æŽ¥ç€çœ‹ï¼Œé‚£å¯ä»¥è¿™æ ·å®žçŽ° sync.Once 嘛?

       è¿™æ ·ä¸æ˜¯æ›´ç®€å•ä¸€ç‚¹å˜›ï¼Œä½¿ç”¨åŽŸå­çš„ CAS 操作就可以解决并发问题呀,并发只执行一次 f 方法的问题是可以解决,但是 Do 方法可能并发,第一个调用者将 done 设置成了 1 然后调用 f 方法,如果 f 方法特别耗时间,那么第二个调用者获取到 done 为 1 就直接返回了,此时 f方法是没有执行过第二次,但是此时第二个调用者可以继续执行后面的代码,如果后面的代码中有用到 f 方法创建的实例,但是由于 f 方法还在执行中,所以可能会出现报错问题。所以官方采用的是Lock + DoubleCheck 的方式。

if?atomic.CompareAndSwapUint(&o.done,?0,?1)?{ f()}拓展

       æ‰§è¡Œå¼‚常后可继续执行的Once

       çœ‹æ‡‚了源码之后,我们就可以扩展 sync.Once 包了。例如 f 方法在执行的时候报错了,例如连接初始化失败,怎么办?我们可以实现一个高级版本的 Once 包,具体的 slowDo 代码可以参考下面的实现

func?(o?*Once)?slowDo(f?func()?error)?error?{ o.m.Lock()defer?o.m.Unlock()var?err?errorif?o.done?==?0?{ ?//?Double?Checkerr?=?f()if?err?==?nil?{ ?//?没有异常的时候记录done值atomic.StoreUint(&o.done,?1)}}return?err}

       å¸¦æ‰§è¡Œç»“果的 Once

       ç”±äºŽ Once 是不带执行结果的,我们不知道 Once 什么时候会执行结束,如果存在并发,需要知道是否执行成功的话,可以看下下面的案例,我这里是以 redis 连接的问题来进行说明的。Do 方法执行完毕后将 init 值设置成 1 ,然后其他 goruntine 可以通过 IsConnetion 来获取连接是否建立,然后做后续的操作。

type?RedisConn?struct?{ once?sync.Onceinit?uint}func?(this?*RedisConn)?Init()?{ this.once.Do(func()?{ ?//?do?redis?connection?atomic.StoreUint(&this.init,?1)})}func?(this?*RedisConn)?IsConnect()?bool?{ ?//?另外一个goroutinereturn?atomic.LoadUint(&this.init)?!=?0}

Golang sync.Cond 条件变量源码分析

       sync.Cond 是 Golang 标准库 sync 包中一个关键的条件变量类型,用于在多个goroutine间协调等待特定条件。它常用于生产者-消费者模型等场景,确保在某些条件满足后才能继续执行。本文基于 go-1. 源码,深入解析 sync.Cond 的核心机制与用法。

       sync.Cond 的基本用法包括创建条件变量、等待唤醒与发送信号。使用时,通常涉及到一个互斥锁(Locker)以确保并发安全性。首先,通过`sync.NewCond(l Locker)`创建条件变量。其次,`cond.Wait()`使当前执行的goroutine等待直到被唤醒,期间会释放锁并暂停执行。`cond.Signal()`和`Broadcast()`用于唤醒等待的goroutine,前者唤醒一个,后者唤醒所有。

       在底层实现中,sync.Cond 采用了一种称为 notifyList 的数据结构来管理等待和唤醒过程。notifyList 由一组元素构成,其中`wait`和`notify`表示当前最大ticket值和已唤醒的最大ticket值,而`head`和`tail`则分别代表等待的goroutine链表的头和尾。在`Wait`操作中,每次调用`runtime_notifyListAdd`生成唯一的ticket,并将当前goroutine添加到链表中。当调用`Signal`或`Broadcast`时,会查找并唤醒当前`notify`值对应的等待goroutine,并更新`notify`值。

       信号唤醒过程确保了FIFO的顺序,即最早等待的goroutine会首先被唤醒。这种机制有效地防止了并发操作下列表的乱序,确保了正确的唤醒顺序,尽管在实际执行中,遍历整个列表的过程在大多数情况下效率较高。

       在使用sync.Cond时,需注意避免潜在的死锁风险和错误的唤醒顺序。确保合理管理互斥锁的使用,以及在适当情况下使用`Signal`或`Broadcast`来唤醒等待的goroutine。正确理解和应用sync.Cond,能有效提升并发编程的效率与稳定性。

深度解析sync WaitGroup源码

       waitGroup

       waitGroup 是 Go 语言中并发编程中常用的语法之一,主要用于解决并发和等待问题。它是 sync 包下的一个子组件,特别适用于需要协调多个goroutine执行任务的场景。

       waitGroup 主要用于解决goroutine间的等待关系。例如,goroutineA需要在等待goroutineB和goroutineC这两个子goroutine执行完毕后,才能执行后续的业务逻辑。通过使用waitGroup,goroutineA在执行任务时,会在检查点等待其他goroutine完成,确保所有任务执行完毕后,goroutineA才能继续进行。

       在实现上,waitGroup 通过三个方法来操作:Add、Done 和 Wait。Add方法用于增加计数,Done方法用于减少计数,Wait方法则用于在计数为零时阻塞等待。这些方法通过原子操作实现同步安全。

       waitGroup的源码实现相对简洁,主要涉及数据结构设计和原子操作。数据结构包括了一个 noCopy 的辅助字段以及一个复合意义的 state1 字段。state1 字段的组成根据目标平台的不同(位或位)而有所不同。在位环境下,state1的第一个元素是等待线程数,第二个元素是 waitGroup 计数值,第三个元素是信号量。而在位环境下,如果 state1 的地址不是位对齐的,那么 state1 的第一个元素是信号量,后两个元素分别是等待线程数和计数值。

       waitGroup 的核心方法 Add 和 Wait 的实现原理如下:

       Add方法通过原子操作增加计数值。当执行 Add 方法时,首先将 delta 参数左移位,然后通过原子操作将其添加到计数值上。需要注意的是,delta 的值可正可负,用于在调用 Done 方法时减少计数值。

       Done方法通过调用 Add(-1)来减少计数值。

       Wait方法则持续检查 state 值。当计数值为零时,表示所有子goroutine已完成,调用者无需等待。如果计数值大于零,则调用者会变成等待者,加入等待队列,并阻塞自己,直到所有任务执行完毕。

       通过使用waitGroup,开发者可以轻松地协调和同步并发任务的执行,确保所有任务按预期顺序完成。这在多goroutine协同工作时,尤其重要。掌握waitGroup的使用和源码实现,将有助于提高并发编程的效率和可维护性。

       如果您对并发编程感兴趣,希望持续关注相关技术更新,请通过微信搜索「迈莫coding」,第一时间获取更多深度解析和实战指南。