A brief look at the interesting //go: instruction

  Back end, golang, php

image

Original address:A brief look at the interesting //go: instruction

Preface

If you have the habit of reading the source code at ordinary times, you will surely find out. Why, how do some methods always say//go:What about such instructions? What exactly are they doing?

Today we will unveil them together. I will briefly introduce to you what they are responsible for.

go:linkname

//go:linkname localname importpath.name

This instruction instructs the compiler to useimportpath.nameAs declared in the source code aslocalnameThe symbolic name of the target file for the variable or function of. However, due to this pseudo instruction, the type system and package modularization can be destroyed. Therefore, only references have been madeunsafeThe package can only be used

In short, it isimportpath.nameYeslocalnameThe compiler actually calls thelocalname. But the premise is to useunsafeThe package can only be used

Case

time/time.go

...
func now() (sec int64, nsec int32, mono int64)

runtime/timestub.go

import _ "unsafe" // for go:linkname

//go:linkname time_now time.now
func time_now() (sec int64, nsec int32, mono int64) {
    sec, nsec = walltime()
    return sec, nsec, nanotime() - startNano
}

As can be seen in this casetime.now, it has no specific implementation. If you look at it for the first time, you may feel overwhelmed. At this time it is suggested that you search the source code globally, and you will find that nowruntime.time_nowIn

According to the previous usage explanation, we can know that in the runtime package, we declaredtime_nowThe method istime.nowThe symbol alias of. And introduced in the file headerunsafeReach a prerequisite

go:noescape

//go:noescape

This instruction specifies the next function that has a declaration but no body (meaning that the implementation may not be Go) and does not allow the compiler to do escape analysis on it.

In general, this instruction is used for memory allocation optimization. Because the compiler does escape analysis by default, it will determine whether a variable is allocated to the heap or the stack by rules. However, there are accidents in everything. Although some functions escape analysis, they are stored on the heap. But for us, it is special. We can use itgo:noescapeThe instruction forces the compiler to assign it to the function stack

Case

// memmove copies n bytes from "from" to "to".
// in memmove_*.s
//go:noescape
func memmove(to, from unsafe.Pointer, n uintptr)

  • Memmove_*.s: only declaration, no body. Its main body is implemented by the bottom assembly
  • Memmove: function function, processing performance on the stack will be better

go:nosplit

//go:nosplit

This instruction specifies that the next function declared in the file must not contain a stack overflow check. Simply put, this function skips the stack overflow check

Case

//go:nosplit
func key32(p *uintptr) *uint32 {
    return (*uint32)(unsafe.Pointer(p))
}

go:nowritebarrierrec

//go:nowritebarrierrec

This instruction indicates that the compiler generates an error when it encounters a write barrier and allows recursion. That is, other functions called by this function will also report errors if there is a write barrier. In short, it is to deal with the write barrier to prevent its dead cycle.

Case

//go:nowritebarrierrec
func gcFlushBgCredit(scanWork int64) {
    ...
}

go:yeswritebarrierrec

//go:yeswritebarrierrec

This instruction is related togo:nowritebarrierrecIn contrast, in labelinggo:nowritebarrierrecOn the function of the instruction, an error will be generated when a write barrier is encountered. And when the compiler encountersgo:yeswritebarrierrecThe command will stop

Case

//go:yeswritebarrierrec
func gchelper() {
    ...
}

go:noinline

This instruction indicates that the function prohibits inlining

Case

//go:noinline
func unexportedPanicForTesting(b []byte, i int) byte {
    return b[i]
}

Let’s look at this case, is directly through the index value, logic is relatively simple. If you don’t addgo:noinlineThe compiler will perform inline optimization on it

Obviously, there are good and bad things in the inner link. This instruction is to provide this special treatment.

go:norace

//go:norace

This instruction means that race detection is prohibited. Another common form is executed at startupgo run -race, can detect whether there is a two-way data competition in the application. Very useful

Case

//go:norace
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
    ...
}

go:notinheap

//go:notinheap

This instruction is often used for type declaration, which indicates that this type does not allow memory requests from GC heap. At runtime, it is often used as a lower-level internal structure to avoid write barriers in scheduler and memory allocation. Can improve performance

Case

// notInHeap is off-heap memory allocated by a lower-level allocator
// like sysAlloc or persistentAlloc.
//
// In general, it's better to use real types marked as go:notinheap,
// but this serves as a generic type for situations where that isn't
// possible (like in the allocators).
//
//go:notinheap
type notInHeap struct{}

Summary

In this article, we briefly introduce some common instruction sets, and I suggest that they are for understanding only. In general, we can’t use it, because your bottleneck may be more in its own application.

However, understanding these will be more helpful for you to understand the underlying source code and operating mechanism. If you want to go deeper, please read the reference link I gave:)

References