lundi 4 octobre 2021

Is there a fast way to uniquely identify a function caller in Go?

As a background, I have a logging facility that wants to output filename and line number, as well as manage some other unique-per-caller information. In c++ this is relatively straightforward using static variables, but I'm having trouble coming up with an equivalent in Go.

I've come across runtime.Caller(), which will get me the PC of the caller (and thus uniquely identify it), but clocking in at ~500ns it's not a cost I want to pay on every invocation of every log statement!

As a really basic example of the behaviour I have

package main

import (
        "fmt"
        "runtime"
)

type Info struct {
        file string
        line int
        // otherData
}

var infoMap = map[uintptr]Info{}

func Log(str string, args ...interface{}) {
        pc, file, line, _ := runtime.Caller(1)
        info, found := infoMap[pc]
        if !found {
                info = Info{file, line}
                infoMap[pc] = info
                // otherData would get generated here too
        }
        fmt.Println(info.file, info.line)
}

// client code
func foo() {
        // do some work
        Log("Here's a message with <> some <> args", "foo", "bar")
        // do more work
        Log("The cow jumped over the moon")
}

func main() {
        foo()
        foo()
}

This outputs

/tmp/foo/main.go 33
/tmp/foo/main.go 35
/tmp/foo/main.go 33
/tmp/foo/main.go 35

Now, runtime.Caller(1) is being evaluated on every call here. Ideally, it is evaluated once per statement.

Something like

func Log(str string, args ...interface{}) {
        uniqueId = doSomethingFasterThanCaller()
        info, found := infoMap[uniqueId]
        if !found {
                _, file, line, _ := runtime.Caller(1)
                info = Info{file, line}
                infoMap[pc] = info
                // otherData would get generated here too
        }
        fmt.Println(info.file, info.line)
}

Or even something done from the caller that allows it to be uniquely identified without hardcoding ids.

func Log(uniqueId int, str string, args ...interface{}) {
        info, found := infoMap[uniqueId]
        if !found {
                _, file, line, _ := runtime.Caller(1)
                info = Info{file, line}
                infoMap[uniqueId] = info
                // otherData would get generated here too
        }
        fmt.Println(info.file, info.line)
}

func Foo() {
   Log( uniqueIdGetter(), "message" )
   Log( uniqueIdGetter(), "another message" )

   // If I could get the offset from the beginning of the function
   // to this line, something like this could work.
   //
   //Log((int)(reflect.ValueOf(Foo).Pointer()) + CODE_OFFSET, "message")
}

Is this something that can be done natively, or am I stuck with the cost of runtime.Caller (which does a bunch of extra work above-and-beyond just getting the pc, which is really all I need)?





Aucun commentaire:

Enregistrer un commentaire