Allocation-free time code?

479 views
Skip to first unread message

Graham Miller

unread,
May 18, 2011, 12:28:06 PM5/18/11
to golang-nuts
Until we have escape analysis and stackification in the gc compilers,
all the methods that get the current time (time.Seconds,
time.Nanoseconds, time.UTC, os.Time) cause a heap allocation. The
culprit is the '&' on line 16 of this file:
http://golang.org/src/pkg/os/time.go

This is a problem (for me), when trying to get timing information in
the inner loop of my application. Having done some profiling, I'm
seeing that these time allocations are responsible for 14% of the
memory allocated in my application (the inner loop runs a lot). I
looked a bit at hacking a native C function into src/pkg/time, but it
required introducing native code to that package which formerly had
none (which I'm assuming would be frowned upon). Any other
suggestions?

graham

Rob 'Commander' Pike

unread,
May 18, 2011, 1:30:13 PM5/18/11
to Graham Miller, golang-nuts

It seems to me the long-term solution lies in package syscall, not package os, by invoking a different helper.

However, you've all but described your own short-term solution. For now, call syscall.Gettimeofday yourself.

-rob

Russ Cox

unread,
May 18, 2011, 1:30:39 PM5/18/11
to Graham Miller, golang-nuts
You can always cp -R $GOROOT/src/pkg/time your/time
and then import "your/time" and change them to be value methods.
They'll be a little slower to execute but won't allocate.

Russ

Ian Lance Taylor

unread,
May 18, 2011, 1:39:31 PM5/18/11
to Graham Miller, golang-nuts
Graham Miller <graham...@gmail.com> writes:

We could change syscall.Gettimeofday to return three values (sec, usec,
err). Actually then the error code would be unnecessary, so it could
just return two values. But we would have to write it in assembler for
most targets.

Ian

Russ Cox

unread,
May 18, 2011, 1:41:38 PM5/18/11
to Ian Lance Taylor, Graham Miller, golang-nuts
> We could change syscall.Gettimeofday to return three values (sec, usec,
> err).  Actually then the error code would be unnecessary, so it could
> just return two values.  But we would have to write it in assembler for
> most targets.

Ah. I looked at the wrong place in the package.
This is just a bug in syscall.
Package runtime already does this.

Russ

Brad Fitzpatrick

unread,
May 18, 2011, 1:42:04 PM5/18/11
to Rob 'Commander' Pike, Graham Miller, golang-nuts
I think that would just move the allocation problem elsewhere.  Most of the syscall Gettimeofday code looks like:

func Gettimeofday(tv *Timeval) (errno int) {
        _, _, e1 := RawSyscall(SYS_GETTIMEOFDAY, uintptr(unsafe.Pointer(tv)), 0, 0)
        errno = int(e1)
        return
}

Even if we stack-allocated the Timeval struct in that function, we'd still need to take its address for unsafe.Pointer, causing it to be heap-allocated there.

Ian just said the same I think.

Graham Miller

unread,
May 18, 2011, 1:50:00 PM5/18/11
to Russ Cox, Ian Lance Taylor, golang-nuts
Indeed it does. I'll build my temporary solution on that. Let me
know if you want me to file a bug, or work on a more permanent fix.

Thanks.

graham

Kyle Lemons

unread,
May 18, 2011, 1:54:51 PM5/18/11
to Brad Fitzpatrick, Rob 'Commander' Pike, Graham Miller, golang-nuts
>> However, you've all but described your own short-term solution.  For now,
>> call syscall.Gettimeofday yourself.
>
> I think that would just move the allocation problem elsewhere.  Most of the
> syscall Gettimeofday code looks like:
> func Gettimeofday(tv *Timeval) (errno int) {
>         _, _, e1 := RawSyscall(SYS_GETTIMEOFDAY,
> uintptr(unsafe.Pointer(tv)), 0, 0)
>         errno = int(e1)
>         return
> }

If he was calling this with his own Timeval that he only allocates
once, doesn't that solve his allocation problem?

Rob 'Commander' Pike

unread,
May 18, 2011, 2:07:56 PM5/18/11
to Kyle Lemons, Brad Fitzpatrick, Graham Miller, golang-nuts

That's what I was suggesting, just less clearly.

-rob

unread,
May 18, 2011, 2:44:08 PM5/18/11
to golang-nuts
Maybe the "unsafe" package (which is already used by package
"syscall", using it from "os" shouldn't cause any problems) should
gain support for allocating memory on the stack. The "os.Time"
function could use it to get the space for the result of function
"syscall.Gettimeofday". One option is to introduce a function like
"unsafe.AddressOf(v ArbitraryType) Pointer", which would simply return
the memory address of 'v'. The 8g/6g/5g compilers would implement
AddressOf by emitting a couple of instructions. The main difference
between "&v" and "AddressOf(v)" is that the latter will not force the
variable to be heap-allocated:

func Time() (sec int64, nsec int64, err Error) {
var tv syscall.Timeval
if e := syscall.Gettimeofday( unsafe.AddressOf(tv) ); iserror(e) {
return 0, 0, NewSyscallError("gettimeofday", e)
}
return int64(tv.Sec), int64(tv.Usec) * 1000, err

Rob 'Commander' Pike

unread,
May 18, 2011, 2:47:29 PM5/18/11
to ⚛, golang-nuts

On May 18, 2011, at 11:44 AM, ⚛ wrote:

> Maybe the "unsafe" package (which is already used by package
> "syscall", using it from "os" shouldn't cause any problems) should
> gain support for allocating memory on the stack. The "os.Time"
> function could use it to get the space for the result of function
> "syscall.Gettimeofday". One option is to introduce a function like
> "unsafe.AddressOf(v ArbitraryType) Pointer", which would simply return
> the memory address of 'v'. The 8g/6g/5g compilers would implement
> AddressOf by emitting a couple of instructions. The main difference
> between "&v" and "AddressOf(v)" is that the latter will not force the
> variable to be heap-allocated:
>
> func Time() (sec int64, nsec int64, err Error) {
> var tv syscall.Timeval
> if e := syscall.Gettimeofday( unsafe.AddressOf(tv) ); iserror(e) {
> return 0, 0, NewSyscallError("gettimeofday", e)
> }
> return int64(tv.Sec), int64(tv.Usec) * 1000, err
> }

Besides all the other consequences of this mixing of allocation models, this sort of optimization is what compilers are for. See the mention of 'escape analysis' earlier in this thread.

-rob

unread,
May 19, 2011, 3:50:41 AM5/19/11
to golang-nuts
I think you are wrong about the fact that a compiler can do escape
analysis in the case of the code we are talking about. If you think
escape analysis is possible here, then I would like you to describe
the course of actions that would lead a Go compiler to the conclusion
that variable "tv" in function "os.Time" does not need to be allocated
on the heap. I think such a sequence of actions simply does not exist,
so escape analysis is *impossible* here.

The only way for "tv" not to be heap-allocated is for the programmer
to explicitly tell the compiler what the function
"syscall.Gettimeofday" is doing with its arguments. Obviously, the
explicitness also implies that it is unsafe (i.e: the programmer is
providing the compiler with a piece of information that the compiler
has no way of verifying, and so the compiler has to work under the
assumption (blind belief) that the provided information is correct).

Brad Fitzpatrick

unread,
May 19, 2011, 9:30:41 AM5/19/11
to ⚛, golang-nuts
unsafe.Pointer does complicate things.
 

Ian Lance Taylor

unread,
May 19, 2011, 10:56:13 AM5/19/11
to ⚛, golang-nuts
⚛ <0xe2.0x...@gmail.com> writes:

> I think you are wrong about the fact that a compiler can do escape
> analysis in the case of the code we are talking about. If you think
> escape analysis is possible here, then I would like you to describe
> the course of actions that would lead a Go compiler to the conclusion
> that variable "tv" in function "os.Time" does not need to be allocated
> on the heap. I think such a sequence of actions simply does not exist,
> so escape analysis is *impossible* here.
>
> The only way for "tv" not to be heap-allocated is for the programmer
> to explicitly tell the compiler what the function
> "syscall.Gettimeofday" is doing with its arguments. Obviously, the
> explicitness also implies that it is unsafe (i.e: the programmer is
> providing the compiler with a piece of information that the compiler
> has no way of verifying, and so the compiler has to work under the
> assumption (blind belief) that the provided information is correct).

In general we have to annotate every function written in assembler which
takes a pointer argument. There is nothing wrong with that, as
functions written in assembler are unsafe no matter how you look at
them. In this case we would be annotating syscall.Gettimeofday on some
systems and syscall.Syscall on others, whichever is written in assembler
(as far as I can see, no pointer passed to a system call ever escapes).
Once we've done those annotations, though, the compiler can do escape
analysis building on that. (It's important to note that we would
annotate syscall.Syscall where it is defined in the syscall package; we
would not be annotating syscall.Gettimeofday at the point where os.Time
calls it.)

Ian

unread,
May 19, 2011, 11:41:03 AM5/19/11
to golang-nuts
Ok. Are you talking about user-visible language specification changes,
or about compiler-specific annotations?

On May 19, 4:56 pm, Ian Lance Taylor <i...@google.com> wrote:

Ian Lance Taylor

unread,
May 19, 2011, 1:02:58 PM5/19/11
to ⚛, golang-nuts
⚛ <0xe2.0x...@gmail.com> writes:

> Ok. Are you talking about user-visible language specification changes,
> or about compiler-specific annotations?

Compiler-specific annotations.

No Go language specification change, but the assembler and possibly the
C compiler (6c/8c) would need to support some sort of pragma or other
mechanism for adding the annotations.

Ian

Reply all
Reply to author
Forward
0 new messages