Feature #8640

Add Time#elapsed to return nanoseconds since creation

Added by Aaron Patterson 9 months ago. Updated 9 months ago.

[ruby-core:56027]
Status:Open
Priority:Normal
Assignee:-
Category:-
Target version:-

Description

Hi,

We time many things in Rails (and so does lots of other code), and I've found that we spend lots of time allocating and subtracting time objects. For example:

start = Time.now

do stuff we want to time

finish = Time.now - start

It would be nice if we could just create one time object and grab the nanoseconds elapsed since the time object was created like so:

start = Time.now

do stuff we want to time

finished = start.elapsed # => nanoseconds elapsed.

I've attached a patch that implements this feature.

elapsed.patch Magnifier (1.95 KB) Aaron Patterson, 07/16/2013 03:39 AM


Related issues

Related to ruby-trunk - Feature #8658: Process.clock_gettime Closed 07/19/2013
Related to CommonRuby - Feature #8096: introduce Time.current_timestamp Feedback 03/15/2013

History

#1 Updated by Rodrigo Rosenfeld Rosas 9 months ago

+1

#2 Updated by Martin Dürst 9 months ago

I think this is a good idea. But since
duration = Time.now - start
is in seconds, I'd either keep seconds as units, or make the use of nanoseconds explicit, e.g. like this:
duration = start.elapsed_nanoseconds

#3 Updated by Matthew Kerwin 9 months ago

+1

duerst (Martin Dürst) wrote:

I think this is a good idea. But since
duration = Time.now - start
is in seconds, I'd either keep seconds as units, or make the use of nanoseconds explicit, e.g. like this:
duration = start.elapsed_nanoseconds

Since this is a new feature I think we don't need to worry as much about existing functionality. Personally I prefer the function to return a higher-precision integer over a float.

I wouldn't complain if there was a kwarg, although I'm not sure which way it should switch: timeobj.elapsed(float: true) or timeobj.elapsed(nanoseconds: true)

#4 Updated by Martin Dürst 9 months ago

phluid61 (Matthew Kerwin) wrote:

duerst (Martin Dürst) wrote:

duration = start.elapsed_nanoseconds

Since this is a new feature I think we don't need to worry as much about existing functionality.

It's going to be very confusing to have different methods on Time use different units. In some years, nobody will remember which method is new and which is old.

#5 Updated by Nobuyoshi Nakada 9 months ago

phluid61 (Matthew Kerwin) wrote:

I wouldn't complain if there was a kwarg, although I'm not sure which way it should switch: timeobj.elapsed(float: true) or timeobj.elapsed(nanoseconds: true)

time_obj.elapsed(in: :nanoseconds) ?

#6 Updated by Matthew Kerwin 9 months ago

duerst (Martin Dürst) wrote:

It's going to be very confusing to have different methods on Time use different units. In some years, nobody will remember which method is new and which is old.

You're right. It makes sense to exactly mimic time - other_time, and return the number of seconds in a float.

nobu (Nobuyoshi Nakada) wrote:

phluid61 (Matthew Kerwin) wrote:

I wouldn't complain if there was a kwarg, although I'm not sure which way it should switch: timeobj.elapsed(float: true) or timeobj.elapsed(nanoseconds: true)

time_obj.elapsed(in: :nanoseconds) ?

I had considered that, but I wonder if people aren't going to ask for (or expect) support for :milliseconds, :hours, :days, :aztecgreatcycles, etc. The rules about which symbols are valid seems a little arbitrary. And then I suppose the question could become: why are nanoseconds an integer, but seconds a float? And why doesn't Time#- accept the same kwarg? And so on with the bike-shedding. I don't know a good answer for most of these hypothetical questions, so I guess the short-term solution would be to make #elapsed be a short-cut for Time.now - self, and return a float number of seconds.

Also, I feel obliged to mention that this feature is related to #8096, FWIW.

#7 Updated by Charlie Somerville 9 months ago

I'll echo what everyone else has said above by saying that I'm strongly against #elapsed returning nanoseconds. I'm happy with #elapsed_nanoseconds returning nanoseconds as an Integer, and #elapsed returning seconds as a Float though.

#8 Updated by Anonymous 9 months ago

On Wed, Jul 17, 2013 at 04:04:42AM +0900, charliesome (Charlie Somerville) wrote:

Issue #8640 has been updated by charliesome (Charlie Somerville).

I'll echo what everyone else has said above by saying that I'm strongly against #elapsed returning nanoseconds. I'm happy with #elapsed_nanoseconds returning nanoseconds as an Integer, and #elapsed returning seconds as a Float though.

elapsed_nanoseconds is fine with me. I will change the ticket title
(if I can).

--
Aaron Patterson
http://tenderlovemaking.com/

#9 Updated by Martin Dürst 9 months ago

Hello Aaron,

On 2013/07/17 1:46, Aaron Patterson wrote:

Hi Martin,

On Tue, Jul 16, 2013 at 03:24:36PM +0900, duerst (Martin Dürst) wrote:

Issue #8640 has been updated by duerst (Martin Dürst).

I think this is a good idea. But since
duration = Time.now - start
is in seconds, I'd either keep seconds as units, or make the use of nanoseconds explicit, e.g. like this:
duration = start.elapsed_nanoseconds

I chose nanoseconds because it can be represented as an integer,

On a 32-bit machine, that works up to about 0.5 seconds. (On a 64-bit
machine, it's about 73 years.) After that, it will be a Bignum.

Regards, Martin.

so we
don't need to allocate Ruby objects (plus all the other benefits
integers buy us). So I'd really like to stick with nanoseconds as the
unit.

I'll propose both elapsed and elapsed_nanoseconds to matz. :-)

#10 Updated by Nobuyoshi Nakada 9 months ago

(13/07/16 20:37), phluid61 (Matthew Kerwin) wrote:

nobu (Nobuyoshi Nakada) wrote:

phluid61 (Matthew Kerwin) wrote:

I wouldn't complain if there was a kwarg, although I'm not sure which way it should switch: timeobj.elapsed(float: true) or timeobj.elapsed(nanoseconds: true)

time_obj.elapsed(in: :nanoseconds) ?

I had considered that, but I wonder if people aren't going to ask for (or expect) support for :milliseconds, :hours, :days, :aztecgreatcycles, etc. The rules about which symbols are valid seems a little arbitrary.

Your points are all true for {nanoseconds: true} too.

And if support for milliseconds is implemented, what would you expect from elapsed(nanoseconds: true, milliseconds: true)?

And then I suppose the question could become: why are nanoseconds an integer, but seconds a float?

Ditto.

And why doesn't Time#- accept the same kwarg?

Ditto.

And it's because of the operator syntax.

And so on with the bike-shedding. I don't know a good answer for most of these hypothetical questions, so I guess the short-term solution would be to make #elapsed be a short-cut for Time.now - self, and return a float number of seconds.

You propose the method not to take any arguments?
I had supposed that you'd wanted the keyward argument.

#11 Updated by Matthew Kerwin 9 months ago

nobu (Nobuyoshi Nakada) wrote:

You propose the method not to take any arguments?
I had supposed that you'd wanted the keyward argument.

Since the discussion has moved towards defining two separate methods {#elapsed => (float)s and #elapsed_nanoseconds => (int)ns} I do prefer a keyword argument {in: :nanoseconds}. Sorry for wavering back and forth on the issue so noisily.

#12 Updated by Aaron Patterson 9 months ago

On Wed, Jul 17, 2013 at 01:04:37PM +0900, phluid61 (Matthew Kerwin) wrote:

Issue #8640 has been updated by phluid61 (Matthew Kerwin).

nobu (Nobuyoshi Nakada) wrote:

You propose the method not to take any arguments?
I had supposed that you'd wanted the keyward argument.

Since the discussion has moved towards defining two separate methods {#elapsed => (float)s and #elapsed_nanoseconds => (int)ns} I do prefer a keyword argument {in: :nanoseconds}. Sorry for wavering back and forth on the issue so noisily.

I don't care if a method exists that has kw args, but I would not use
it. The things we need to time in Rails are fairly fast (say 200ms on
the slow side) and happen frequently, which means that object
allocations matter. A kwarg method will end up allocating a hash on
every call.

If someone else wants to make a method with kwargs, I think that's
great, but it's not what I'm pushing for here. My point is to reduce
object allocations. :-)

--
Aaron Patterson
http://tenderlovemaking.com/

#13 Updated by Matthew Kerwin 9 months ago

tenderlovemaking (Aaron Patterson) wrote:

On Wed, Jul 17, 2013 at 01:04:37PM +0900, phluid61 (Matthew Kerwin) wrote:

Since the discussion has moved towards defining two separate methods {#elapsed => (float)s and #elapsed_nanoseconds => (int)ns} I do prefer a keyword argument {in: :nanoseconds}. Sorry for wavering back and forth on the issue so noisily.

I don't care if a method exists that has kw args, but I would not use
it. The things we need to time in Rails are fairly fast (say 200ms on
the slow side) and happen frequently, which means that object
allocations matter. A kwarg method will end up allocating a hash on
every call.

If someone else wants to make a method with kwargs, I think that's
great, but it's not what I'm pushing for here. My point is to reduce
object allocations. :-)

In that case, would #8096 be a better proposal? Since that one doesn't even allocate a Time object.

#14 Updated by Aaron Patterson 9 months ago

On Thu, Jul 18, 2013 at 07:59:34AM +0900, phluid61 (Matthew Kerwin) wrote:

Issue #8640 has been updated by phluid61 (Matthew Kerwin).

tenderlovemaking (Aaron Patterson) wrote:

On Wed, Jul 17, 2013 at 01:04:37PM +0900, phluid61 (Matthew Kerwin) wrote:

Since the discussion has moved towards defining two separate methods {#elapsed => (float)s and #elapsed_nanoseconds => (int)ns} I do prefer a keyword argument {in: :nanoseconds}. Sorry for wavering back and forth on the issue so noisily.

I don't care if a method exists that has kw args, but I would not use
it. The things we need to time in Rails are fairly fast (say 200ms on
the slow side) and happen frequently, which means that object
allocations matter. A kwarg method will end up allocating a hash on
every call.

If someone else wants to make a method with kwargs, I think that's
great, but it's not what I'm pushing for here. My point is to reduce
object allocations. :-)

In that case, would #8096 be a better proposal? Since that one doesn't even allocate a Time object.

I don't think so. We need subsecond resolution, which (if we used
#8096) would require a possible bignum allocation (from what I gather in
the ticket).

--
Aaron Patterson
http://tenderlovemaking.com/

#15 Updated by Rodrigo Rosenfeld Rosas 9 months ago

Em 18-07-2013 03:03, Aaron Patterson escreveu:

In that case, would #8096 be a better proposal? Since that one doesn't even allocate a Time object.
I don't think so. We need subsecond resolution, which (if we used
#8096) would require a possible bignum allocation (from what I gather in
the ticket).

But I believe he has a point, Aaron. Maybe we should avoid allocating a
time object (Time.now) and instead introduce something like Java's
System.currentTimeMillis():

start = Benchmark.start
operation
report start.sincebeginning
other
operation
report start.sincebeginning # from start= on
report start.since
last_report # since operation

This new object would be lighter than Time as it would only contain the
start absolute nanoseconds or millis since epoch, maybe.

I haven't given the names much of a thought, but that would be the idea.
Makes sense?

#16 Updated by Rodrigo Rosenfeld Rosas 9 months ago

Also, as a side effect, this new class could have methods like
nanosecondsellapsed, and secondsellapsed and the like.

Also, it could bookmark some parts of the code. Example:

timerecording = Benchmark.start
some
code
timerecording.bookmark(:a)
more
code
report timerecording.secondsellapsed_since(:a)

#17 Updated by Matthew Kerwin 9 months ago

rosenfeld (Rodrigo Rosenfeld Rosas) wrote:

Em 18-07-2013 03:03, Aaron Patterson escreveu:

In that case, would #8096 be a better proposal? Since that one doesn't even allocate a Time object.
I don't think so. We need subsecond resolution, which (if we used
#8096) would require a possible bignum allocation (from what I gather in
the ticket).

But I believe he has a point, Aaron. Maybe we should avoid allocating a
time object (Time.now) and instead introduce something like Java's
System.currentTimeMillis():

start = Benchmark.start
operation
report start.sincebeginning
other
operation
report start.sincebeginning # from start= on
report start.since
last_report # since operation

This new object would be lighter than Time as it would only contain the
start absolute nanoseconds or millis since epoch, maybe.

I haven't given the names much of a thought, but that would be the idea.
Makes sense?

Actually the supplied patch uses clockgettime(CLOCKREALTIME), which is already the C equivalent of System.currentTimeMillis(). The only issue is that it allocates a Time object (although it doesn't matter because that happens before the benchmarking commences). Since the getting of the final time and the calculation of the elapsed duration all happen in C-land, there's very little cost involved, even when the numbers are very large.

However, one of the threads of discussion on #8096 suggested using clockgettime(CLOCKMONOTONIC)* or System.nanoTime(), which count from an arbitrary epoch (so the number would usually be smaller and thus more likely to fit into a Fixnum). It would be quite light to do:

start = Time.timestamp # clock_gettime(CLOCK_MONOTONIC) => Fixnum
# ... stuff ...
delta = start - Time.timestamp # => Fixnum

..even if you have to do more of the logic yourself.

I'm +0 for the original proposal, just because of the naming/kwarg issue, otherwise I'd be +1.

  • or CLOCKPROCESSCPUTIMEID or CLOCKTHREADCPUTIMEID if they're available, and that's what you need.

#18 Updated by Akira Tanaka 9 months ago

2013/7/19 phluid61 (Matthew Kerwin) matthew@kerwin.net.au:

Actually the supplied patch uses clockgettime(CLOCKREALTIME), which is already the C equivalent of System.currentTimeMillis(). The only issue is that it allocates a Time object (although it doesn't matter because that happens before the benchmarking commences). Since the getting of the final time and the calculation of the elapsed duration all happen in C-land, there's very little cost involved, even when the numbers are very large.

I feel what we need is Process.clock_gettime, as a primitive.
--
Tanaka Akira

#19 Updated by Rodrigo Rosenfeld Rosas 9 months ago

You're right, Matthew, although I'd appreciate some bookmark capabilities... But I understand it would be more costly since you'd need to malloc and free the struct everytime a bookmark is created...

Tanaka, I believe most people are interested in elapsed time rather than absolute time (Process.clock_gettime, System.currentTimeMillis(), etc). Having to process the elapsed time in the Ruby side is probably slower than doing the math on the C side, right?

#20 Updated by Matthew Kerwin 9 months ago

rosenfeld (Rodrigo Rosenfeld Rosas) wrote:

You're right, Matthew, although I'd appreciate some bookmark capabilities... But I understand it would be more costly since you'd need to malloc and free the struct everytime a bookmark is created...

Tanaka, I believe most people are interested in elapsed time rather than absolute time (Process.clock_gettime, System.currentTimeMillis(), etc). Having to process the elapsed time in the Ruby side is probably slower than doing the math on the C side, right?

clock_gettime allows you to choose which clock to get (on modern Linux, there are four), and if your relative timestamps are both Fixnums the difference between Ruby-land and C-land arithmetic is negligible. See: https://github.com/phluid61/ruby-experiments/tree/master/gettime

#21 Updated by Vipul Amler 9 months ago

+1

Should save a lot of Time object allocations.

Also available in: Atom PDF