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

time.Sleep() on amd64 linux can't sleep <10ms #260

Closed
gopherbot opened this issue Nov 18, 2009 · 8 comments
Closed

time.Sleep() on amd64 linux can't sleep <10ms #260

gopherbot opened this issue Nov 18, 2009 · 8 comments

Comments

@gopherbot
Copy link

by wmundt42:

What steps will reproduce the problem?
1. Run the following program with -duration=5000000

package main

import "time"
import "fmt"
import "flag"

var Duration = flag.Int64("duration", 10000, "time to sleep for")
var Count = flag.Int64("count", 100, "times to sleep")

func main() {
        flag.Parse();

        t1 := time.Nanoseconds();
        for i := int64(0); i < *Count; i++ {
                time.Sleep(*Duration)
        }
        t2 := time.Nanoseconds();

        delta := (t2 - t1) / *Count;
        off := delta - *Duration;
        offpct := float(off) / float(*Duration) * 100;

        fmt.Println("asked to sleep for", *Duration, "-- actually slept
for", t2-t1);
        fmt.Println("off by ", off, "ns, or", offpct, "percent");
}

What is the expected output? What do you see instead?

Expected, more oe less: 
asked to sleep for 5000000 -- actually slept for 500000000
off by 0 ns, or 0.000 percent

Actual:
asked to sleep for 5000000 -- actually slept for 1022879000
off by  5228790 ns, or 104.5758 percent

What is your $GOOS?  $GOARCH?

linux amd64

Which revision are you sync'ed to?  (hg log -l 1)

4130:e1069e80ef83

Please provide any additional information below.

It appears that time.Sleep rounds up to the nearest 10ms, at least when
called repeatedly.  Since time.Ticker is implemented in terms of Sleep,
this means Tickers -- at least on this platform -- can't seem to tick more
often than every 10ms.

It may be relevant that the machine in question is a VM running under
VMWare ESXI; uname -a says "Linux wmtest2 2.6.24-24-server #1 SMP Tue Jun
30 20:24:57 UTC 2009 x86_64 GNU/Linux"

If this is expected behavior, these limitations should be documented on
both time.Sleep and time.Ticker.

Workaround for ticker use case: it is possible to process ticks more often
by setting up multiple 10ms Tickers and a goroutine to mux the channels.
@gopherbot
Copy link
Author

Comment 1 by wjosephson:

The default quantum under Linux is 10ms so this is expected behavior and is a property
of Linux, not go.

@gopherbot
Copy link
Author

Comment 2 by wmundt42:

If that's so, I still think it would help to document the range of resolutions the
various platforms support somewhere, and that the platform's Sleep() resolution will
affect go's time.Ticker().  After all, as the workaround indicates, it's possible to
implement a more elaborate ticker that can deliver ticks faster than this.

@gopherbot
Copy link
Author

Comment 3 by wmundt42:

Also, note that modern Linux nanosleep() calls can have much smaller relolutions than
the "quantum" or jiffy.  It looks like go Sleep is actually based on a select() call,
though.

@dhobsd
Copy link
Contributor

dhobsd commented Nov 20, 2009

Comment 4:

Perhaps we need to make a way for the sleep-in-kernel sleeps used in locks / notes to
work here as well? This doesn't seem too difficult, but I'm not sure how that would
work specifically on Darwin, since we just use semaphores to sleep.
It's also worth noting that all OS sleep routines typically state that they are
guaranteed to sleep for `at least' the specified time. I don't disagree that if one
should be able to sleep for 5ms, it should be supported. However, the sleep routines
are typically not real-time and do not make real-time guarantees.
--dho

@gopherbot
Copy link
Author

Comment 5 by wmundt42:

I did some further testing.  The syscall package on Linux already has a binding for
Nanosleep; I changed the Sleep() call to use that instead of Select().  At least on
my test machine, it appears to be the case that this can indeed sleep for <10ms, but
there is a consistent 1.5-2ms overhead to the call.  That seems like a lot to me, but
tests resulted in the sleep taking more than 1.5ms and less than 2ms longer than
requested, regardless of how long the sleep was supposed to be for.  Note that this
is as measured by the above tester program, so any flaws in my code would affect that
result.
This works for my purposes, but I am not sure aliasing Sleep to Nanosleep is a good
general policy.  Both are available in the syscall package anyway.  I do still
support documenting the specifics of Sleep's current behavior as well as any
long-term guarantees it makes.  Even if the former does not belong in the official
API documentation, it's still good information to have around somewhere.

@dhobsd
Copy link
Contributor

dhobsd commented Nov 20, 2009

Comment 6:

Right; as I said, sleep() calls are not real time. They only guarantee to return at
least at the time you specified. There is probably some measurable overhead in the
enter / exit syscall routines called by the syscall wrappers in the go runtime.
I don't think aliasing Sleep to Nanosleep is a good policy, and I agree that the best
solution may be to simply document the behavior, and make it clear that there is no
real-time enforcement (which would imply that it would return at latest the amount of
time you requested).
--dho

@rsc
Copy link
Contributor

rsc commented Dec 2, 2009

Comment 7:

Labels changed: added documentation.

@robpike
Copy link
Contributor

robpike commented Dec 24, 2009

Comment 8:

This issue was closed by revision 1245e93.

Status changed to Fixed.

Merged into issue #-.

@golang golang locked and limited conversation to collaborators Jun 24, 2016
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants