Skip to content

time: optimize time.Time.Sub and time.Since #17858

Open
@dsnet

Description

@dsnet

According to a profiling data from a large number of servers at Google, time.Time.Sub is within the top 150 functions by CPU time. The current implementation of Sub is not inlineable. The logic for Sub is not particularly complicated and I believe it can be made to be inlined with some love.

Activity

added this to the Go1.9 milestone on Nov 8, 2016
changed the title [-]time: optimize time.Time.Sub[/-] [+]time: optimize time.Time.Sub and time.Time.Since[/+] on Nov 8, 2016
dsnet

dsnet commented on Nov 8, 2016

@dsnet
MemberAuthor

time.Since is also a hot function. It uses Sub internally. Unfortunately, it can't be inlined since it makes a call to runtime.now.

changed the title [-]time: optimize time.Time.Sub and time.Time.Since[/-] [+]time: optimize time.Time.Sub and time.Since[/+] on Nov 8, 2016
josharian

josharian commented on Nov 9, 2016

@josharian
Contributor

Oddly, looking at the output of -gcflags="-m -m", I don't see Time.Sub listed at all, either as inlineable or non-inlineable.

gopherbot

gopherbot commented on Nov 9, 2016

@gopherbot
Contributor

CL https://golang.org/cl/32971 mentions this issue.

josharian

josharian commented on Nov 9, 2016

@josharian
Contributor

Time.Sub currently has an inlining cost of 116, well above the current inlining threshold of 80. Some minor fiddling around (below) brings it down to 111. I don't see this happening without some compiler fixes; that is #17566. (Note that Time.Sub currently compiles into 227 bytes of code, about the same as two appends.)

// Add returns the time t+d.
func (t Time) Add(d Duration) Time {
    t.sec += int64(d / 1e9)
    t.nsec += int32(d % 1e9)
    if t.nsec >= 1e9 {
        t.sec++
        t.nsec -= 1e9
    } else if t.nsec < 0 {
        t.sec--
        t.nsec += 1e9
    }
    return t
}

// Sub returns the duration t-u. If the result exceeds the maximum (or minimum)
// value that can be stored in a Duration, the maximum (or minimum) duration
// will be returned.
// To compute t-d for a duration d, use t.Add(-d).
func (t Time) Sub(u Time) Duration {
    d := Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
    // Check for overflow or underflow.
    switch {
    case u.Add(d).Equal(t):
        return d // d is correct
    case t.Before(u):
        return minDuration // t - u is negative out of range
    }
    return maxDuration // t - u is positive out of range
}
dsnet

dsnet commented on Nov 9, 2016

@dsnet
MemberAuthor

I looked at this earlier today, and I am suspicious of the current overflow checking logic. Sub does overflow checking by checking that u.Add(d).Equal(t), which initially seems legit, but it's kinda strange since the Add method itself does not do overflow checking. Thus, I find it strange that overflow checking is depending on a function that is not overflow safe.

This is a version of Sub I came up with that is inlineable:

func (t Time) SubC(u Time) Duration {
    sec := t.sec - u.sec
    nsec := t.nsec - u.nsec // Cannot overflow; in range of [-999999999, 999999999]
    d1 := Duration(sec) * Second
    d2 := d1 + Duration(nsec) // Overflows only when nsec < 0

    overflow := (sec < u.sec) != (t.sec > 0) || // Subtraction overflow?
        (d1/Second) != Duration(sec) || // Multiplication overflow?
        (d2 < d1) != (nsec < 0) // Addition overflow?
    if !overflow {
        return d2
    }

    // Overflow can only occur if number of seconds apart is large enough.
    // Thus, we do not need to compare the nanoseconds.
    if t.sec < u.sec {
        return minDuration
    } else {
        return maxDuration
    }
}

On my machine, this version runs 3.2x faster.

self-assigned this
on Dec 9, 2016
modified the milestones: Go1.10, Go1.9 on May 23, 2017
bradfitz

bradfitz commented on May 23, 2017

@bradfitz
Contributor

Sadly bumping this to Go 1.10.

16 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @bradfitz@josharian@mvdan@dsnet@gopherbot

        Issue actions

          time: optimize time.Time.Sub and time.Since · Issue #17858 · golang/go