Controlling the Go Runtime

GopherCon 2019
Patrick Hawley - Controlling the go runtime

Overview

概述

Sometimes we need to tell the go runtime what to do. This talk explains how to control the runtime, why we should, and describes three new ways to control it better.

本文将展示为什么需要控制以及如何控制runtime,并讨论三种更好的控制runtime的方法。

What is the Go runtime?

runtime包能做什么?

It’s a library used by Go programs while they are executing. It controls things like:

  • the garbage collector, managing memory
  • the goroutine scheduler

runtime包可以用于控制:

  • 垃圾回收、内存管理;
  • Goroutine调度器。

It is a helpful assistant. It is a Go package (https://golang.org/pkg/runtime/). It is not an interpreter (e.g. Python, Ruby) or full runtime environment such as the Java virtual machine. It is useful for profiling and observing what happens during program execution.

runtime包既不是一个解释器(在Python、Ruby中)也不是一个完整的运行时(JVM)。runtime包是一个工具集合,可以用于监督和分析系统运行状态。

Some functions that can be used to control the garbage collector:

  • GC() - start garbage collection now (blocking operation)
  • KeepAlive(obj) - obj is needed, at least until now
  • SetFinalizer(obj, f) - call f when obj is no longer needed

runtime包有以下函数可用于控制垃圾回收:

  • GC() - 立即阻塞,开始垃圾回收;
  • KeepAlive(obj) - object在该函数执行前,不会被释放;
  • SetFinalizer(obj, f) - 在object释放时,调用函数f。

Some functions that can be used to control the scheduler:

  • GOMAXPROCS(n) - set max simultaneously executing CPU’s
  • Goexit() - terminate the calling goroutine
  • Gosched() - yield processor allowing other goroutines to run
  • LockOSThread()/UnlockOSThread() - wire/unwire goroutine to current OS thread

runtime包有以下函数可用于控制调度器:

  • GOMAXPROCS(n) - 设置最大CPU逻辑核数; // better: https://github.com/uber-go/automaxprocs
  • Goexit() - 退出当前Goroutine;
  • Gosched() - 中断当前Goroutine,让出CPU执行权;
  • LockOSThread()/UnlockOSThread() - 绑定/解绑 当前Goroutine与当前系统线程。

Some more controls, in package runtime/debug:

  • func FreeOSMemory()
  • func SetMaxStack(int) int
  • func SetGCPercent(int32) int32
  • func SetPanicOnFault(bool) bool
  • func SetMaxThreads(int) int

env vars: GOMAXPROCS (max executing operating system threads), GOGC (turn off garbage collector)

在 (https://golang.org/pkg/runtime/debug/) 包还提供了以下函数:

  • func FreeOSMemory() - 强制进行垃圾回收,尽可能多的向系统返还内存;
  • func SetMaxStack(int) int - 设置单个Goroutine最大可占用栈,默认在32位系统上为250MB,在64位系统上为1GB;
  • func SetGCPercent(int32) int32 - 设置触发垃圾回收的阈值,当(新获得的内存 / 上次GC后仍占用的内存 * 100 >= 该阈值)时,触发GC。 默认值为100,当该值为负时,将禁用垃圾回收;
  • func SetPanicOnFault(bool) bool - 设置当发生Panic时,是否触发main崩溃,该设置只对当前调用者Goroutine有效;
  • func SetMaxThreads(int) int - 设置当前Go系统进程最大系统线程数,超出则触发崩溃,默认值为10000。

环境变量: GOMAXPROCS (最大并行数), GOGC (垃圾回收 开/关)

You can get indirect control of the runtime via context

ctx, cancel := context.WithCancel(ctx)

The above requests that all goroutines with ctx, stop. This is scheduler control

还可以通过context.Context间接控制runtime(不做赘述)

ctx, cancel := context.WithCancel(ctx)

So … Why Control the Runtime?

所以…为什么要控制运行时?

Performance reasons. Testing and benchmarks.

runtime包在提升性能、测试和基准测试中很有帮助。

go test -cpu 1,2,4

Jaeger tracing uses runtime control to match container CPU quota changes.

We use Goexit() all the time via t.Fatal("some test failed")

Gosched() yields processor to allow other goroutines to run. This can be put in a tight loop to make sure the CPU isn’t hogged.

LockOSThread()/UnlockOSThread()

Thread local state is used by some libs eg Cocoa, OpenGL, libSDL.

GC() is useful for testing, improving performance. For example checking the performance of allocating a slice of integers vs a slice of pointers to int. There are garbage collection implications to handling of values vs pointers so runtime directives can be used to benchmark them.

KeepAlive() holds off garbage collection and there are certain situations where it can be used to stop premature cleanup of e.g. file descriptors.

SetFinalizer() as mentioned sets a function to be called when an obj is no longer needed and can be garbage collected. An example was shown which closes an os.File when it goes out of scope.

go test -cpu 1,2,4

在测试中,可以使用-cpu来指定CPU核数。

t.Fatal("some test failed")

在测试中,Fatal函数会执行Goexit()。

Gosched()可以让出CPU执行权,确保其它Goroutine也会被执行,而不是执行权被当前Goroutine独占。

在Cocoa, OpenGL, libSDL库中,均使用了LockOSThread()/UnlockOSThread()。

在测试中使用GC(),可以帮忙比较程序性能差异,从而优化程序性能,比如可以通过基准测试比较出回收一个“整数切片”和一个“整数指针切片”的性能差异。

KeepAlive()可以确保某资源不会在使用前被释放,比如文件句柄。

SetFinalizer()可以确保某资源被释放时,执行指定的函数钩子。

1
2
3
4
5
6
7
8
9
10
type File struct { d int }
d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0)
// ... do something if err != nil ...
p := &File{d}
runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) })
var buf [10]byte
n, err := syscall.Read(p.d, buf[:])
// Ensure p is not finalized until Read returns.
runtime.KeepAlive(p)
// No more uses of p after this point.

Other Uses For Go Runtime

runtime包还可以这样用

Not just performance! Testing, Correctness, Profiling

So now that we are convinced that using the Go runtime is good!

不仅仅在测试和性能分析,runtime包也可以在程序准确性判断和程序状态分析上提供帮助。
So 愉快地使用runtime包吧!

New Ways to Control the Runtime

更好的控制runtime的方法

These are a thought experiment, they do NOT exist (yet)!

以下是脑洞环节,均未实现!

Used the analogy of a “color” to describe goroutines of a related type

goroutineID - 描述一个goroutine的唯一标识,特指一个goroutine
goroutineColor - 描述一类goroutines,the kind of goroutines

Patrick 认为应该使用color描述goroutines,应关注于goroutines的功能和目的,而不是goroutine本身。

GoSchedNext(goroutineColor) - Instruct runtime what to run next. Not by goroutine ids, but goroutines of a certain type.

GoSchedNext(goroutineColor) - 开始调度一类Goroutines。

GoAffinity() - requires or suggests that all goroutines of a certain “color” execute on the same CPU.

Reason:

  • avoid CPU L1/L2 cache misses
  • NUMA architecture
  • isolation, keeping code of a certain type separate from other types (e.g. math operations vs http)

GoCancel() - cancel all goroutines of a particular “color”
GoSend() - Communicate with goroutines of a particular “color”

GoAffinity() - 将某一类Goroutines绑定在同一个CPU核上。

将某一类Goroutines绑定在同一个CPU核上的优势:

GoCancel() - 取消/关闭 某一类Goroutines
GoSend() - 向某一类Goroutines发送Msg (CSP/Actor模型)

Why is there no goroutine ID?

为什么我们没有GoroutineID?

https://golang.org/doc/faq#no_goroutine_id

虽然你说了很多,但是我还是想要 🤣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"runtime"
)

func main() {
buf := make([]byte, 32)
runtime.Stack(buf[:], false)
offset := len("goroutine ")
bytesGoID := []byte{}
for _, r := range buf[offset:] {
if r < '0' {
break
}
if r > '9' {
break
}
bytesGoID = append(bytesGoID, r)
}
goID := string(bytesGoID)
fmt.Println(goID)
}