在golang中有一种高效的计算执行时间的方法吗?

64

我正在寻找在Go语言中计算执行时间的最佳方法。

func main() {
    start := time.Now()

    time.Sleep(time.Second * 2)

    //something doing here

    elapsed := time.Since(start)
    fmt.Printf("page took %s", elapsed)
}

上面的代码运行良好。

但是当我使用模板时,我必须为每个模板函数重新编写它。

有没有一种有效的方法来计算执行时间,包括使用模板?


可能是代码顺序和性能的重复问题。 - icza
Golang的测试包支持https://golang.org/pkg/testing/#hdr-Benchmarks,可以帮助更好地检查代码。 - Muzafar Ali
5个回答

132
如果您要计时整个函数,那么您可以使用defer来消除一些重复的代码。
// timer returns a function that prints the name argument and 
// the elapsed time between the call to timer and the call to
// the returned function. The returned function is intended to
// be used in a defer statement:
//
//   defer timer("sum")()
func timer(name string) func() {
    start := time.Now()
    return func() {
        fmt.Printf("%s took %v\n", name, time.Since(start))
    }
}

func main() {
    defer timer("main")()  // <-- The trailing () is the deferred call
    time.Sleep(time.Second * 2)
}   // prints: main took 2s

在 playground 上运行此示例

关于延迟调用,规范如下:

每次执行“defer”语句时,函数值和调用的参数会像通常一样被计算并重新保存,但实际函数不会被调用。 相反,在包围函数返回之前,将立即调用延迟的函数。

函数值timer(“main”)在延迟语句中进行评估。 timer函数记录当前时间并返回一个匿名函数。 返回的匿名函数在包围函数返回之前立即被调用。 匿名函数计算并打印经过的时间。


使用 runtime.Callers runtime.CallersFrames 自动获取调用函数的名称。

// callerName returns the name of the function skip frames up the call stack.
func callerName(skip int) string {
    const unknown = "unknown"
    pcs := make([]uintptr, 1)
    n := runtime.Callers(skip+2, pcs)
    if n < 1 {
        return unknown
    }
    frame, _ := runtime.CallersFrames(pcs).Next()
    if frame.Function == "" {
        return unknown
    }
    return frame.Function
}

// timer returns a function that prints the name of the calling
// function and the elapsed time between the call to timer and
// the call to the returned function. The returned function is
// intended to be used in a defer statement:
//
//   defer timer()()
func timer() func() {
    name := callerName(1)
    start := time.Now()
    return func() {
        fmt.Printf("%s took %v\n", name, time.Since(start))
    }
}

func main() {
    defer timer()()
    time.Sleep(time.Second * 2)
}   // prints: main.main took 2s

请注意,与在此答案的第一部分中使用字符串字面量相比,获取函数名称会产生运行时成本。为了避免测量获取函数名称的成本,timer 在记录开始时间之前获取名称。

在 playground 上运行示例.


1
延迟调用的参数会立即被评估,但是函数调用直到周围的函数返回才会执行。例如,defer outcall(incall())incall()会立即被评估,而outcall()直到周围的函数返回才会被执行。 - axiqia

29

Cerise Limón 提供的解决方案完美无缺。


此外,如果您不想显式传递函数名称,可以像这样完成:

func SomeFunction(list *[]string) {
    defer TimeTrack(time.Now())
    // Do whatever you want.
}

func TimeTrack(start time.Time) {
    elapsed := time.Since(start)

    // Skip this function, and fetch the PC and file for its parent.
    pc, _, _, _ := runtime.Caller(1)

    // Retrieve a function object this functions parent.
    funcObj := runtime.FuncForPC(pc)

    // Regex to extract just the function name (and not the module path).
    runtimeFunc := regexp.MustCompile(`^.*\.(.*)$`)
    name := runtimeFunc.ReplaceAllString(funcObj.Name(), "$1")

    log.Println(fmt.Sprintf("%s took %s", name, elapsed))
}

结果为:

SomeFunction花费了15.483微秒


更多信息,请参阅此文章:Go函数跟踪

分享知识. :)


5
使用init函数
package main

import (
    "fmt"
    "time"
)

var start time.Time

func init() {
    start = time.Now()
}

func getChars(s string) {
    for _, c := range s {
        fmt.Printf("%c at time %v\n", c, time.Since(start))
        time.Sleep(10 * time.Millisecond)
    }
}

func main() {
    fmt.Println("main execution started at time", time.Since(start))

    getChars("Hello")

    fmt.Println("\nmain execution stopped at time", time.Since(start))
}

3

在golang中高效计算执行时间的方法

您可以使用defer函数轻松地在控制台上获取执行时间

即使代码出现错误,defer函数也会执行,因此您始终可以获得执行时间。

time包用于获取时间差。

func main() {
    now := time.Now()
    defer func() {
        fmt.Println(time.Now().Sub(now))
    }()
        
    // Here you can do whatever you want
}

或者您可以使用以下代码。
func main() {
        now := time.Now()
        defer func() {
            fmt.Println(time.Since(now))
        }()
            
        // Here you can do whatever you want
    }

请查看Playground中的代码以获取更多信息。我添加了一些功能,可以在错误恢复时同时打印执行时间,即使出现恐慌错误也是如此。


在实际编码完成后(即在要计时的代码之后),在封闭函数结束前进行延迟并不是很有用,因为这时您可以直接执行第二个计时和打印而不需要用到 defer,并且具有完全相同的功能。defer 存在的目的是在问题代码(在稍后执行)之前安排一些调用,并在出现 panics 或早期退出时无论如何都将被调用。 - blubberdiblub
基于问题,defer是非常有用的。即使出现panic,它也能正常工作。 - ASHWIN RAJEEV
我并没有质疑defer在一般情况下用于计时的有用性(其他答案展示了一个很好的使用方式)。只是它在这个答案中的使用方式不是很有效。特别是将defer放在函数末尾没有任何意义。 - blubberdiblub
@blubberdiblub 是的,但是defer可以放在代码中的任何位置。它将被添加到延迟堆栈中,并将执行延迟函数的任何位置。所以我认为它更有用,因为我们可以编写懒惰的代码... - ASHWIN RAJEEV
4
这个答案和两年前发布的被采纳的答案没有什么不同。有什么意义呢? - Sergio Tulentsev
显示剩余3条评论

0

使用此作为Golang中平均计算时间的侧面参考(分别针对10、100、1000个元素)。

  • 访问数组中的元素 (1/1/1秒)
  • 二进制/字典搜索 (1/2/3秒)
  • 简单循环/线性搜索 (10/100/1,000秒)
  • 快速排序/归并排序/堆排序/Tim排序 (10/200/3,000秒)
  • 冒泡排序/插入排序/选择排序 (100/10,000/1,000,000秒)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接