Skip to content

Simplify $&time and increase its flexibility #210

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

jpco
Copy link
Collaborator

@jpco jpco commented May 4, 2025

This PR changes the $&time primitive and the time function. It fixes #154.

Previously, the basic steps of $&time were: (1) measure real time and usage times; (2) run the given command; (3) measure real times and usage again; (4) take the difference and print the result.

We now shrink the scope of $&time so that takes an optional previous set of times as arguments, and it (1) measures the real and usage times, (2) subtracts the argument-times if given, and (3) returns the result. Like this:

; (str times) = <=$&time
; echo $str    # a pre-formatted version for printing
     3.210r     0.005u     0.001s
; echo $times  # raw microsecond-valued real-, user-, and sys-time measurements
3210345 5317 1266
; (str times) = <={$&time $times}
; echo $str
     1.245r     0.000u     0.000s
; echo $times
1245379 478 320

The built-in time function is constructed from this new $&time as:

fn time cmd {
	$&collect                     # for timing consistency; the old `$&time` did this
	let ((str times) = <=$&time)  # first measurement
	unwind-protect {              # protect against exceptions and use $cmd's return value
		$cmd                  # run the command
	} {
		(str times) = <={$&time $times}  # second measurement, subtracting the first
		echo >[1=2] $str^\t^$^cmd        # print the output and the command
	}
}

Benefits of this new setup:

  • Better precision -- time now reports millisecond precision timing, instead of second/tenths-of-seconds. On those few systems which don't support more precise timing methods, we fall back gracefully to the worse but very standard functions.
  • time uses straightforward es control flow and does not fork. Because of this, time can be used "transparently"; time {a = b}; echo $a now does the expected thing. A user could wrap one part of a script within a time {} without screwing up the semantics of the timed section.
  • However, if a user really wants time to fork, that can be done by changing the time function.
  • The way that measured times are reported or used can be changed. Users could format the raw microsecond times as they see fit, or they can use timing information other ways. For example, a raw let ((s _) = <=$&time) echo $s approximates the times command in other shells.

We can also do some, err, mild hackery to measure "has it been more than $X?" This can be used for testing performance of certain constructs (e.g., on #213), or for something like

fn timeout-after ms cmd {
  let (result = (); (_ start) = <={$&time -$ms^000 0 0})  # get the time $ms in the future
  forever {
    let ((_ acc) = <={$&time $start}) if {!~ $acc(1) -*} {  # test whether we're in the future yet
      return $result
    }
    result = <=$cmd
  }
}
timeout-after 4500 {echo looping; sleep 0.`{shuf -i 1-1000 -n 1}}

In theory, the same thing could be done with date +%s, but that would potentially add a lot of fork-exec overhead to gather and do math on all the timestamps. This way also works around es' lack of general arithmetic support, and supports quite a bit of precision.

One limitation of the new $&time and time: Across es invocations, results get wacky with the timing measurements, and across processes (like fork {}) results get wacky with usage times specifically.

; local ((_ t) = <=$&time) ./es -c 'let ((_ t) = <={$&time $t}) echo $t'
-38480695 -22102 -14995
; let ((_ t) = <=$&time) fork {let ((_ t) = <={$&time $t}) echo $t}
454 -27563 -24990

jpco added 6 commits April 28, 2025 08:22
 - Clean up time code to be much more legible
 - Use intmax_t for timestamps to be more overflow-resistant
 - Add fallback for time functions
 - Add error handling
This uses the (AFAICT) previously-unused 'dotconv' for a new purpose.
If an  integer (for example, 8675309) is printed with "%.3d", the output
is 8675.309, putting three decimal places after the point.  This is a
bit weird, but it avoids any complexity around non-integer numbers.
@jpco
Copy link
Collaborator Author

jpco commented Jun 19, 2025

I fixed the negative-number-printing issue. Yay! Now I feel good about actually proposing this for merging.

However, a possible nitpick now is on the question of how to pad times accounting for the extra digits. Currently the new shell (es.new) formats times like the following, to keep everything lined up with the current shell (es.old) when possible:

; es.new -c 'time sleep 1'
 1.002r   0.000u   0.001s	sleep 1
; es.old -c 'time sleep 1'
     1r     0.0u     0.0s	sleep 1

However, this sure isn't very much padding anymore, so it might be reasonable to bump the padding values up by a couple characters.

@jpco jpco marked this pull request as ready for review June 19, 2025 21:23
jpco added a commit to jpco/es-shell that referenced this pull request Jun 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

time could be improved
1 participant