Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

utils: speed up timer by embedding the time.Timer #4504

Closed
wants to merge 1 commit into from

Conversation

marten-seemann
Copy link
Member

Note a huge speedup, but we're resetting timers a lot since we're using it to pace packets.

name      old time/op  new time/op  delta
Timer-16  77.9ns ± 4%  75.8ns ± 3%  -2.59%  (p=0.000 n=50+47)

@marten-seemann
Copy link
Member Author

Oops, looks like CI really hates this change. But why?

@onee-only
Copy link

Seems like (*time.Timer).Reset does not work for timer as value for some reason.

Since the timer is initialized with math.MaxInt64 duration, and is not reset due to the problem above,
the tests are being timed out waiting for receiving from the timer's channel.

I've tested behavior with code snippet below.

using timer as pointer:

func main() {
	now := time.Now()
	t := time.NewTimer(time.Second)
	t.Reset(time.Second * 2)

	<-t.C
	log.Println(time.Since(now))
	// 2024/05/19 17:09:14 2.000662625s
}

using timer as value:

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

	t := *time.NewTimer(time.Second)
	t.Reset(time.Second * 2)

	<-t.C
	log.Println(time.Since(now))
	// 2024/05/19 17:07:54 1.001052084s
}

I'm not sure if there is a workaround.

@sukunrt
Copy link
Collaborator

sukunrt commented May 19, 2024

Can you try embedding a pointer to the timer?
But that will probably not give the speedup at all

@marten-seemann
Copy link
Member Author

Can you try embedding a pointer to the timer?

That's what we currently have, or am I misunderstanding what you're suggesting?

@sukunrt
Copy link
Collaborator

sukunrt commented May 19, 2024

You aren't. Embedding the timer is what we have currently. I think this is what's happening.

You cannot copy a time.Timer{}. Currently, we create a new timer using time.NewTimer which returns a pointer to the created timer. Now we dereference this pointer and copy the underlying timer value into our struct. This also copies the time.Timer struct's underlying runtimeTimer. This copying is illegal. The way runtime tracks these timers is by their address which it stores when creating the timer with time.NewTimer, the address of our struct is different because we copied this.

Now when we call timer.Reset on the copied timer the implementation goes through the reset steps, but this new value will never be used because the runtime isn't tracking this copied timer at all. Specifically this slice doesn't have the copied timer:
https://github.com/golang/go/blob/go1.22.3/src/runtime/proc.go#L3974

@marten-seemann
Copy link
Member Author

@sukunrt Thanks for researching this! Your explanation makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants