英文:
More accurate ticker than time.NewTicker in go on macOS
问题
我正在编写控制外部LED的代码,通过UDP进行控制,并且需要保持尽可能恒定的帧率(例如60Hz)。太多的抖动会看起来很糟糕。我使用time.NewTicker
编写了一个简单的测试,结果并不理想。我想知道是否有一种不同的方法可以在更准确的间隔上执行代码。这个测试在macOS上运行,但也需要在Windows和Linux上运行。值得一提的是,它需要与音频同步,所以也许每个操作系统上都有一个可以与音频同步的音频ticker API呢?
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
"time"
)
var f *os.File
var testTimeSeconds = 30
func appendToCsv(t time.Time) {
w := csv.NewWriter(f)
defer w.Flush()
records := []string{strconv.FormatInt(t.UnixMicro(), 10)}
w.Write(records)
}
func init() {
var err error
f, err = os.Create("newTicker.csv")
if err != nil {
log.Fatal(err)
}
w := csv.NewWriter(f)
defer w.Flush()
records := []string{"timestamps"}
w.Write(records)
}
func main() {
usPerFrame := 16666
ticker := time.NewTicker(time.Duration(usPerFrame) * time.Microsecond)
defer ticker.Stop()
done := make(chan bool)
go func() {
time.Sleep(time.Duration(testTimeSeconds) * time.Second)
done <- true
}()
for {
select {
case <-done:
fmt.Println("Done!")
return
case t := <-ticker.C:
appendToCsv(t)
}
}
}
更新:
我进行了另一个测试,比较了第一种方法和@jochen答案中的方法,但仍然不太准确。
更新:
我进行了另一个测试,比较了第一种方法和@jochen答案中的方法,但仍然不太准确。
英文:
I am writing code to control external LEDs over UDP, and need to keep a frame rate (e.g. 60Hz) that is as constant as possible. Too much jitter will look bad. I've written a simple test using time.NewTicker
and the results are not ideal. I'm wondering if there is a different way to execute code on a more accurate interval. This test was run on macOS, but needs to run on Windows and Linux too. For what it's worth, it needs to sync to audio, so maybe there is an audio ticker API on each OS it could potentially sync with?
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
"time"
)
var f *os.File
var testTimeSeconds = 30
func appendToCsv(t time.Time) {
w := csv.NewWriter(f)
defer w.Flush()
records := []string{strconv.FormatInt(t.UnixMicro(), 10)}
w.Write(records)
}
func init() {
var err error
f, err = os.Create("newTicker.csv")
if err != nil {
log.Fatal(err)
}
w := csv.NewWriter(f)
defer w.Flush()
records := []string{"timestamps"}
w.Write(records)
}
func main() {
usPerFrame := 16666
ticker := time.NewTicker(time.Duration(usPerFrame) * time.Microsecond)
defer ticker.Stop()
done := make(chan bool)
go func() {
time.Sleep(time.Duration(testTimeSeconds) * time.Second)
done <- true
}()
for {
select {
case <-done:
fmt.Println("Done!")
return
case t := <-ticker.C:
appendToCsv(t)
}
}
}
UPDATE:
I ran another test comparing the first method with the method in @jochen's answer, still not very accurate.
答案1
得分: 1
一种想法是使用time.Sleep()
而不是使用ticker。这样可以将通道的发送/接收操作从循环中移除,可能会导致更准确的定时。为了实现这一点,你可以在一个单独的goroutine中运行以下函数:
func ticker(step time.Duration, done <-chan struct{}) {
next := time.Now().Add(step)
for {
time.Sleep(time.Until(next))
appendToCsv(time.Now())
select { // 检查`done`是否已关闭
case <-done:
return
default:
// 无操作
}
next = next.Add(step)
}
}
英文:
One idea would be to just use time.Sleep()
instead of a ticker. This takes the channel send/receive out of the loop and may lead to more accurate timing. To do this, you could run a function like the following in a separate goroutine:
func ticker(step time.Duration, done <-chan struct{}) {
next := time.Now().Add(step)
for {
time.Sleep(time.Until(next))
appendToCsv(time.Now())
select { // check whether `done` was closed
case <-done:
return
default:
// pass
}
next = next.Add(step)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论