Some notes on Go's runtime.KeepAlive()
function
I was recently reading go101's "Type-Unsafe Pointers" (via)
and ran across a usage of an interesting new runtime
package function, runtime.KeepAlive()
. I was initially puzzled
by how it was used, and then me being me I had to poke into how it
worked.
What runtime.KeepAlive()
does is that it keeps a variable 'alive',
which means that it (and what it refers to) will not be garbage
collected and any finalizers it has won't be run.
The documentation has
an example of its use. My initial confusion was why the use of
runtime.KeepAlive()
was so late in the code; I had sort of expected
it to be used early, like finalizers are set, but then I realized
what it is really doing. In short, runtime.KeepAlive()
is using
the variable. A variable is obviously alive right up to the end of
its last use, so if you use a variable late, Go must keep it alive
all the way there.
At one level, there's nothing magical about runtime.KeepAlive
;
any use of the variable would do to keep it alive. At another level
there is an important bit of magic about runtime.KeepAlive
, which
is that Go guarantees that this use of your variable will not be
cleverly optimized away because the compiler can see that nothing
actually really depends on your 'use'. There are various other ways
of using a variable, but even reasonably clever ones are vulnerable
to compiler optimization and aggressively clever ones have the
downside that they may accidentally defeat Go's reasonably clever
escape analysis, forcing what would otherwise
be a local stack variable to be allocated on the heap instead.
The other special magic trick in runtime.KeepAlive()
is in how
it's implemented, which is that it doesn't actually do anything.
In particular, it doesn't make a function call. Instead, much
like unsafe.Pointer
, it's a compiler
builtin, set up in ssa.go.
When your code uses runtime.KeepAlive()
, the Go compiler just
sets up a OpKeepAlive
SSA thing and then the rest of the compiler
knows that this is a use of the variable and keeps it alive through
to that point.
(Reading this ssa.go initialization function was interesting.
Unsurprisingly, it turns out that there are a number of nominal
package function calls that are mapped directly to instructions
that will be placed inline in your code, like math.Sqrt
. Some
of these are platform-dependent, including a bunch of math.bits
.)
That runtime.KeepAlive
is special magic has one direct consequence,
which is that you can't take its address. If you try, Go will report:
./tst.go:20:22: cannot take the address of runtime.KeepAlive
I don't know if Go will too-cleverly optimize away a function that
only exists to call runtime.KeepAlive
, but hopefully you're never
going to need to call runtime.KeepAlive
indirectly.
PS: Although it's tempting to say that one should never need to
call runtime.KeepAlive
on a stack allocated local variable
(including arguments) because the stack isn't cleaned up until the
function returns, I think that this is a dangerous assumption. The
compiler could be sufficiently clever to either reuse a stack slot
for two different variables with non-overlapping lifetimes or simply
tell garbage collection that it's done with something (for example
by overwriting the pointer to the object with nil
).
Comments on this page:
|
|