golang数据分析?

我请教一下,golang数据分析?
最新回答
夏迟归

2024-09-25 01:39:24

(十一)golang内存分析

编写过C语言程序的肯定知道通过malloc()方法动态申请内存,其中内存分配器使用的是glibc提供的ptmalloc2。除了glibc,业界比较出名的内存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免内存碎片和性能上均比glic有比较大的优势,在多线程环境中效果更明显。

Golang中也实现了内存分配器,原理与tcmalloc类似,简单的说就是维护一块大的全局内存,每个线程(Golang中为P)维护一块小的私有内存,私有内存不足再从全局申请。另外,内存分配与GC(垃圾回收)关系密切,所以了解GC前有必要了解内存分配的原理。

为了方便自主管理内存,做法便是先向系统申请一块内存,然后将内存切割成小块,通过一定的内存分配算法管理内存。以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示:

预申请的内存划分为spans、bitmap、arena三部分。其中arena即为所谓的堆区,应用中需要的内存从这里分配。其中spans和bitmap是为了管理arena区而存在的。

arena的大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;

spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB)乘以指针大小8byte=512M

bitmap区域大小也是通过arena计算出来,不过主要用于GC。

span是用于管理arena页的关键数据结构,每个span中包含1个或多个连续页,为了满足小对象分配,span中的一页会划分更小的粒度,而对于大对象比如超过页大小,则通过多页实现。

根据对象大小,划分了一系列class,每个class都代表一个固定大小的对象,以及每个span的大小。如下表所示:

上表中每列含义如下:

class:classID,每个span结构中都有一个classID,表示该span可处理的对象类型

bytes/obj:该class代表对象的字节数

bytes/span:每个span占用堆的字节数,也即页数乘以页大小

objects:每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)waste

bytes:每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)上表可见最大的对象是32K大小,超过32K大小的由特殊的class表示,该classID为0,每个class只包含一个对象。

span是内存管理的基本单位,每个span用于管理特定的class对象,跟据对象大小,span将一个或多个页拆分成多个块进行管理。src/runtime/mheap.go:mspan定义了其数据结构:

以class10为例,span和管理的内存如下图所示:

spanclass为10,参照class表可得出npages=1,nelems=56,elemsize为144。其中startAddr是在span初始化时就指定了某个页的地址。allocBits指向一个位图,每位代表一个块是否被分配,本例中有两个块已经被分配,其allocCount也为2。next和prev用于将多个span链接起来,这有利于管理多个span,接下来会进行说明。

有了管理内存的基本单位span,还要有个数据结构来管理span,这个数据结构叫mcentral,各线程需要内存时从mcentral管理的span中申请内存,为了避免多线程申请内存时不断的加锁,Golang为每个线程分配了span的缓存,这个缓存即是cache。src/runtime/mcache.go:mcache定义了cache的数据结构

alloc为mspan的指针数组,数组大小为class总数的2倍。数组中每个元素代表了一种class类型的span列表,每种class类型都有两组span列表,第一组列表中所表示的对象中包含了指针,第二组列表中所表示的对象不含有指针,这么做是为了提高GC扫描性能,对于不包含指针的span列表,没必要去扫描。根据对象是否包含指针,将对象分为noscan和scan两类,其中noscan代表没有指针,而scan则代表有指针,需要GC进行扫描。mcache和span的对应关系如下图所示:

mchache在初始化时是没有任何span的,在使用过程中会动态的从central中获取并缓存下来,跟据使用情况,每种class的span个数也不相同。上图所示,class0的span数比class1的要多,说明本线程中分配的小对象要多一些。

cache作为线程的私有资源为单个线程服务,而central则是全局资源,为多个线程服务,当某个线程内存不足时会向central申请,当某个线程释放内存时又会回收进central。src/runtime/mcentral.go:mcentral定义了central数据结构:

lock:线程间互斥锁,防止多线程读写冲突

spanclass:每个mcentral管理着一组有相同class的span列表

nonempty:指还有内存可用的span列表

empty:指没有内存可用的span列表

nmalloc:指累计分配的对象个数线程从central获取span步骤如下:

将span归还步骤如下:

从mcentral数据结构可见,每个mcentral对象只管理特定的class规格的span。事实上每种class都会对应一个mcentral,这个mcentral的集合存放于mheap数据结构中。src/runtime/mheap.go:mheap定义了heap的数据结构:

lock:互斥锁

spans:指向spans区域,用于映射span和page的关系

bitmap:bitmap的起始地址

arena_start:arena区域首地址

arena_used:当前arena已使用区域的最大地址

central:每种class对应的两个mcentral

从数据结构可见,mheap管理着全部的内存,事实上Golang就是通过一个mheap类型的全局变量进行内存管理的。mheap内存管理示意图如下:

系统预分配的内存分为spans、bitmap、arean三个区域,通过mheap管理起来。接下来看内存分配过程。

针对待分配对象的大小不同有不同的分配逻辑:

(0,16B)且不包含指针的对象:Tiny分配

(0,16B)包含指针的对象:正常分配

[16B,32KB]:正常分配

(32KB,-):大对象分配其中Tiny分配和大对象分配都属于内存管理的优化范畴,这里暂时仅关注一般的分配方法。

以申请size为n的内存为例,分配步骤如下:

Golang内存分配是个相当复杂的过程,其中还掺杂了GC的处理,这里仅仅对其关键数据结构进行了说明,了解其原理而又不至于深陷实现细节。1、Golang程序启动时申请一大块内存并划分成spans、bitmap、arena区域

2、arena区域按页划分成一个个小块。

3、span管理一个或多个页。

4、mcentral管理多个span供线程申请使用

5、mcache作为线程私有资源,资源来源于mcentral。

golang变量(二)——map和slice详解

衍生类型,interface{},map,[],struct等

map类似于java的hashmap,python的dict,php的hasharray。

常规的for循环,可以用fork,v:=rangem{}.但在下面清空有一个坑注意:

著名的map[string]*struct副本问题

结果:

Go中不存在引用传递,所有的参数传递都是值传递,而map是等同于指针类型的,所以在把map变量传递给函数时,函数对map的修改,也会实质改变map的值。

slice类似于其他语言的数组(list,array),slice初始化和map一样,这里不在重复

除了Pointer数组外,len表示使用长度,cap是总容量,make([]int,len,cap)可以预申请比较大的容量,这样可以减少容量拓展的消耗,前提是要用到。

cap是计算切片容量,len是计算变量长度的,两者不一样。具体例子如下:

结果:

分析:cap是计算当前slice已分配的容量大小,采用的是预分配的伙伴算法(当容量满时,拓展分配一倍的容量)。

append是slice非常常用的函数,用于添加数据到slice中,但如果使用不好,会有下面的问题:

预期是[12345678910],[123456789101112],但实际结果是:

注意slice是值传递,修改一下:

输出如下:

==只能用于判断常规数据类型,无法使用用于slice和map判断,用于判断map和slice可以使用reflect.DeepEqual,这个函数用了递归来判断每层的k,v是否一致。

当然还有其他方式,比如转换成json,但小心有一些异常的bug,比如html编码,具体这个json问题,待后面在分析。

Golangdatabase/sql源码分析

Gorm是Go语言开发用的比较多的一个ORM。它的功能比较全:

但是这篇文章中并不会直接看Gorm的源码,我们会先从database/sql分析。原因是Gorm也是基于这个包来封装的一些功能。所以只有先了解了database/sql包才能更加好的理解Gorm源码。

database/sql其实也是一个对于mysql驱动的上层封装。”github.com/go-sql-driver/mysql”就是一个对于mysql的驱动,database/sql就是在这个基础上做的基本封装包含连接池的使用

下面这个是最基本的增删改查操作

操作分下面几个步骤:

因为Gorm的连接池就是使用database/sql包中的连接池,所以这里我们需要学习一下包里的连接池的源码实现。其实所有连接池最重要的就是连接池对象、获取函数、释放函数下面来看一下database/sql中的连接池。

DB对象

获取方法

释放连接方法

连接池的实现有很多方法,在database/sql包中使用的是chan阻塞使用map记录等待列表,等到有连接释放的时候再把连接传入等待列表中的chan不在阻塞返回连接。

之前我们看到的Redigo是使用一个chan来阻塞,然后释放的时候放入空闲列表,在往这一个chan中传入struct{}{},让程序继续获取的时候再从空闲列表中获取。并且使用的是链表的结构来存储空闲列表。

database/sql是对于mysql驱动的封装,然而Gorm则是对于database/sql的再次封装。让我们可以更加简单的实现对于mysql数据库的操作。

nodejsvsgolang哪个好

从大型工程的角度来说,go是完爆Node

的。性能,内存开销,静态类型,对异步的处理,异常的处理,可调试性,系统稳定度,工具链的成熟度,等等,都是go占优。但是Node

在依赖处理方面更好,npm对依赖版本的管理(go的依赖不能指定版本,还需要借助非官方的工具)非常优秀,另外Node社区的活跃度要比

go好,如果你想要快速做一个东西,npm上各种现成的包可以用。

Node在做网站这方面,尤其是需要直接面向用户的http

服务器这方面,有一个天然的优势,那就是前后端的语言统一。一方面可以减少切换语言的脑力成本,提高开发者的效率,另一方面可以实现前后端共享模板,从而

实现首屏服务器渲染,局部更新浏览器渲染的架构,提高首屏的加载速度。

如果你做的网站后台就是个简单的CRUD应用,或者是做产品原型,或者你同时还想兼顾前端,Node会是更好的选择;但如果你的后台很复杂,需要区分各种服务,搞数据分析,分布式集群等等,那绝对是选go。

最后,其实两者结合起来用也是完全可以的,把Node作为面向用户的大前端的一部分,专门负责页面的生成,后端用go或者其他语言实现,前后端之间通过规范的数据接口通信。

六星教育:Python和go语言都很火,我要怎么选?

python和go语言有区别:1、Python语法使用缩进来指示代码块;Go语法基于打开和关闭括号;2、Python是基于面向对象编程的多范式语言;Go是基于并发编程范式的过程编程语言。3、Python是动态类型语言,Go是静态类型语言。

Go语言(又称Golang)是Google的RobertGriesemer,RobPike及KenThompson开发的一种静态强类型、编译型语言。Go语言语法与C相近,但功能上有:内存安全,GC(垃圾回收),结构形态以及CSP-style并发计算。

python是一种广泛使用的具有动态语义的解释型,面向对象的高级编程语言。

Python是一种面向对象的高级编程语言,具有集成的动态语义,主要用于Web和应用程序开发。它在快速应用程序开发领域极具吸引力,因为它提供动态类型和动态绑定选项。

Python是一种解释型语言,这意味着用Python编写的程序不需要事先编译就可以运行,从而可以轻松地测试小段代码并使用Python编写的代码更容易在平台之间移动。

python和go语言的区别:

1、语法

Python的语法使用缩进来指示代码块。Go的语法基于打开和关闭括号。

2、范例

Python是一种基于面向对象编程的多范式,命令式和函数式编程语言。它坚持这样一种观点,即如果一种语言在某些情境中表现出某种特定的方式,理想情况下它应该在所有情境中都有相似的作用。但是,它又不是纯粹的OOP语言,它不支持强封装,这是OOP的主要原则之一。

Go是一种基于并发编程范式的过程编程语言,它与C具有表面相似性。实际上,Go更像是C的更新版本。

3、并发

Python没有提供内置的并发机制,而Go有内置的并发机制。

4、类型化

Python是动态类型语言,而Go是一种静态类型语言,它实际上有助于在编译时捕获错误,这可以进一步减少生产后期的严重错误。

5、安全性

Python是一种强类型语言,它是经过编译的,因此增加了一层安全性。Go具有分配给每个变量的类型,因此,它提供了安全性。但是,如果发生任何错误,用户需要自己运行整个代码。

6、管理内存

Go允许程序员在很大程度上管理内存。而,Python中的内存管理完全自动化并由PythonVM管理;它不允许程序员对内存管理负责。

7、库

与Go相比,Python提供的库数量要大得多。然而,Go仍然是新的,并且还没有取得很大进展。

8、速度:

Go的速度远远超过Python。

Python与Golang对比:

1、特点:

Golang

①静态强类型、编译型、并发型

静态类型语言,但是有动态语言的感觉。(静态类型的语言就是可以在编译的时候检查出来隐藏的大多数问题,动态语言的感觉就是有很多的包可以使用,写起来的效率很高)

可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。

语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分地利用多核,很容易地使用并发。

②垃圾回收机制

内置runtime,支持垃圾回收,这属于动态语言的特性之一吧,虽然目前来说GC(内存垃圾回收机制)不算完美,但是足以应付我们所能遇到的大多数情况,特别是Go1.1之后的GC。

③支持面向对象编程

有接口类型和实现类型的概念,但是用嵌入替代了继承。

④丰富的标准库

Go目前已经内置了大量的库,特别是网络库非常强大。

⑤内嵌C支持

Go里面也可以直接包含C代码,利用现有的丰富的C库

Python

①解释型语言

程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。

②动态数据类型

支持重载运算符,也支持泛型设计。(运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。泛型设计就是定义的时候不需要指定类型,在客户端使用的时候再去指定类型)

③完全面向对象的语言

函数,模块,数字,字符串都是对象,在Python中,一切接对象

完全支持继承,重载,多重继承

④拥有强大的标准库

Python语言的核心只包含数字,字符串,列表,元祖,字典,集合,文件等常见类型和函数,而由Python标准库提供了系统管理,网络通信,文本处理,数据库接口,图形系统,XML处理等额外的功能。

⑤社区提供了大量第三方库

Python社区提供了大量的第三方模块,使用方式与标准库类似。它们的功能覆盖科学计算、人工智能、机器学习、Web开发、数据库接口、图形系统多个领域。

2、应用

Python

①网络编程

web应用,网络爬虫

②数据分析和机器学习

③自动化测试

④自动化运维

Golang

①服务器编程

处理日志、数据打包、虚拟机处理、文件系统等。

②分布式系统,数据库代理器等

③网络编程

这一块目前应用最广,包括Web应用、API应用、下载应用。

④内存数据库

如google开发的groupcache,couchbase的部分组件。

⑥云平台

Go语言和Python学哪个好?

Python可以很好地集成到企业级应用中,可用于机器语言和AI应用。Go语言的特点表明它具备轻量级线程实现(Goroutine)、智能标准库、强大的内置安全性,且可使用最简语法进行编程。Go在大部分案例中领先,被认为是Python的有效替代方案。开发者在选择编程语言时,应考虑开发项目的性质和规模,以及所需的技能组合。

放下个人偏见和喜好,从优点和功能的角度来评价两种语言。不管选择了哪种语言,Go和Python都在持续演进。尽管在大多数情况下Golang可能是更好的选择,但Python语言也是不断更新迭代的。以上就是本次分享的全部内容,如果你也想学习一门编程语言,可以考虑下六星教育,这里的课程体系,师资团队以及售后服务,一定不会让你失望!

golangmap源码浅析

golang中map的实现结构为:哈希表+链表。其中链表,作用是当发生hash冲突时,拉链法生成的结点。

可以看到,[]bmap是一个hashtable,每一个bmap是我们常说的“桶”。经过hash函数计算出来相同的hash值,放到相同的桶中。一个bmap中可以存放8个元素,如果多出8个,则生成新的结点,尾接到队尾。

以上是只是静态文件src/runtime/map.go中的定义。实际上编译期间会给它加料,动态地创建一个新的结构:

上图就是bmap的内存模型,HOBHash指的就是tophash。注意到key和value是各自放在一起的,并不是key/value/key/value/...这样的形式。源码里说明这样的好处是在某些情况下可以省略掉padding字段,节省内存空间。

每个bmap设计成最多只能放8个key-value对,如果有第9个key-value落入当前的bmap,那就需要再构建一个bmap,通过overflow指针连接起来。

map创建方法:

我们实际上是通过调用的makemap,来创建map的。实际工作只是初始化了hmap中的各种字段,如:设置B的大小,设置hash种子hash0.

注意:

makemap返回是*hmap指针,即map是引用对象,对map的操作会影响到结构体内部。

使用方式

对应的是下面两种方法

map的key的类型,实现了自己的hash方式。每种类型实现hash函数方式不一样。

key经过哈希计算后得到hash值,共64个bit位。其中后B个bit位置,用来定位当前元素落在哪一个桶里,高8个bit为当前hash值的tophash。实际上定位key的过程是一个双重循环的过程,外层循环遍历所有的overflow,内层循环遍历当前bmap中的8个元素。

举例说明:如果当前B的值为5,那么buckets的长度为2^5=32。假设有个key经过hash函数计算后,得到的hash结果为:

外层遍历bucket中的链表

内层循环遍历bmap中的8个cell

建议先不看此部分内容,看完后续修改map中元素-扩容操作后再回头看此部分内容。

扩容前的数据:

等量扩容后的数据:

等量扩容后,查找方式和原本相同,不多做赘述。

两倍扩容后的数据

两倍扩容后,oldbuckets的元素,可能被分配成了两部分。查找顺序如下:

此处只分析mapaccess1,。mapaccess2相比mapaccess1多添加了是否找到的bool值,有兴趣可自行看一下。

使用方式:

步骤如下:

扩容条件:

扩容的标识:h.oldbuckets!=nil

假设当前定位到了新的buckets的3号桶中,首先会判断oldbuckets中的对应的桶有没有被搬迁过。如果搬迁过了,不需要看原来的桶了,直接遍历新的buckets的3号桶。

扩容前:

等量扩容结果

双倍扩容会将oldbuckets上的元素分配到x,y两个部key1B==0分配到x部分,key1B==1分配到y部分

注意:当前只对双倍扩容描述,等量扩容只是重新填充了一下元素,相对位置没有改变。

假设当前map的B==5,原本元素经过hash函数计算的hash值为:

因为双倍扩容之后B=B+1,此时B==6。key1B==1,即当前元素rehash到高位,新b