Go语言高效的5个特性

原文地址:https://www.jianshu.com/p/d70e6f6b13c8程序员

本文只是对原文的简单总结,查看详细信息请移步原文算法


前言

为何选择GO语言

最多见的回答:缓存

  • Concurrency (并发)
  • Ease of deployment (部署容易)
  • Performance (性能)

下面咱们来讨论下对Go语言性能有所帮助的5个特性数据结构


1. 变量的处理和存储

  • 存储:以 int32 为例,Go中占4字节,Python中占24字节,Java虽然int类型也占用4字节,可是在List或者Map这样的集合里存储int的话,编译器会把它转换成Integer对象(32位JVM上占16字节,64位JVM上占24字节)
  • Go语言让你定义很是紧凑的数据结构,避免了无谓的指针跳转。紧凑的数据结构可以使缓存更加有效。高效的缓存最终给咱们带来了更高的效率。

2. 函数调用不是免费的

函数调用的三个步骤:并发

  • 建立一个新的堆栈框(stack frame)并把调用者的详细信息记录下来。
  • 把任何会被被调用函数用到的寄存器内容保存到堆栈。
  • 计算被调用函数的地址,并执行跳转指令到那个新的地址。

内联(inlining): Go语言的实现很是简单。当一个包(package)被编译的时候,任何适合内联的小函数都被标记而且按正常状况编译。而后将源代码和编译后的二进制同时保存下来。 Go编译器可以自动在多个文件或者包(package)之间实现函数内联。若是某些代码调用了来自标准库的可内联函数,Go编译器一样能够将这些函数内联进来。函数


3. 垃圾回收机制(Garbage Collection)

  • 分配在堆(Heap)上的须要垃圾回收,分配在栈(Stack)上的不须要垃圾回收;性能

  • 不管垃圾回收机制多么高效,栈的分配老是比堆的分配要快;操作系统

  • 逃逸分析(Escape Analysis)技术能自动判断是否须要在堆(Heap)上分配空间,若是不须要的话就分配在栈(Stack)上,即便是使用new,make等建立的线程

  • 三色标记算法:go 1.5 版本开始采用 “非分代的、非移动的、并发的、三色的标记清除垃圾收集器”指针


4. Goroutines

开销大小:进程 > 线程(共享内存) > 协程

每一个Go进程只须要少许的操做系统线程。Go的运行环境来负责将可运行的goroutine分配到空闲的操做系统线程上


5. Goroutine的栈管理

  • 保护页机制:Go语言没有使用保护页机制。Go的编译器会在每一个函数调用的时候插入一段代码来检查是否有足够的栈空间来运行被调用的函数。若是空间不足,Go的运行环境就会分配更多的栈空间。由于有了这个检查机制,一个goroutine的初始栈能够很小。这样Go程序员就能够把goroutine做为相对廉价的资源来使用。

  • 热分裂问题(hot split problem):当G调用H的时候,没有足够的栈空间来让H运行,这时候Go运行环境就会从堆里分配一个新的栈内存块去让H运行。在H返回到G以前,新分配的内存块被释放回堆。这种管理栈的方法通常都工做得很好。但对有些代码,特别是递归调用,它会形成程序不停地分配和释放新的内存空间。举个例子,在一个程序里,函数G会在一个循环里调用不少次H函数。每次调用都会分配一块新的内存空间。这就是热分裂问题(hot split problem)。

  • 解决方法:Go 1.3采用了新的栈管理方法。若是goroutine的栈过小了,它会去分配一块新的更大的栈,而不是分配和释法额外的内存空间。老的栈里的内容被复制到新的栈里,goroutine会在新的栈上继续执行。在第一次调用H函数以后,将会有足够大的栈空间,这样之后的栈空间大小检查都不会有问题了。这就解决了热分裂问题。


总结:

这些特性个个都颇有效,他们之间还相互依赖。好比,若是没有了可衍生的栈,运行环境将多个goroutine复用到线程上面就不会颇有效。内联在把多个小函数合并成大函数的时候也避免了栈大小的检查开销。逃逸分析用栈代替堆来存储局部变量,这样也减小来垃圾回收机制的压力。逃逸分析还提高了缓存的性能(cache locality)。没有可衍生的栈,逃逸分析又会对栈形成很大的压力。

相关文章
相关标签/搜索