Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • -timeit- now on SSC: A same-line timer

    Dear all

    -timeit- is now available on SSC, thanks as usual to Kit Baum. It is a simple wrapper program that replaces
    Code:
    timer on 1
    reg y x [note: any command here works, this is just an example]
    timer off 1
    by

    Code:
    timeit 1: reg y x
    In my opinion its main advantage is that your code can still look clean even with timers - the timer on/off lines made everything look super messy in my opinion, making it harder to debug or share code.

    Currently the timer will keep running if your command returns an error. I will hopefully fix this in a future version, though suggestions on how to do this are welcome.

  • #2
    How about

    Code:
    *! version 1.0.1 15aug2016
    program define timeit
        version 10
        
        gettoken theTimer 0 : 0 , parse(":")
        gettoken theColon 0 : 0 , parse(":")
        if ("`theTimer'" == ":") | ("`theColon'" != ":") {
            error 198
        }
        
        nobreak {
            timer on `theTimer'
            capture noisily `0'
            timer off `theTimer'
        }
        
        exit _rc
    end
    Note that the previous code would choke on

    Code:
    timeit 2 :reg y x
    if there was no blank space after the colon.

    You could add more features, like checking if a specified timer number is already in use or letting the user specify a (scalar) name, where the results are to be stored, or support multiple commands, or ... Whatever you find convenient. Just a few thoughts.

    Best
    Daniel

    Comment


    • #3
      Originally posted by daniel klein View Post
      How about

      Code:
      *! version 1.0.1 15aug2016
      program define timeit
      version 10
      
      gettoken theTimer 0 : 0 , parse(":")
      gettoken theColon 0 : 0 , parse(":")
      if ("`theTimer'" == ":") | ("`theColon'" != ":") {
      error 198
      }
      
      nobreak {
      timer on `theTimer'
      capture noisily `0'
      timer off `theTimer'
      }
      
      exit _rc
      end
      Note that the previous code would choke on

      Code:
      timeit 2 :reg y x
      if there was no blank space after the colon.

      You could add more features, like checking if a specified timer number is already in use or letting the user specify a (scalar) name, where the results are to be stored, or support multiple commands, or ... Whatever you find convenient. Just a few thoughts.

      Best
      Daniel
      First of all, thanks for the feedback!

      1. -capture noisily- seems to be what I was looking for (I was aware of capture, but without the noisily it suppresses output).
      2.
      Code:
          if ("`theTimer'" == ":") | ("`theColon'" != ":") {
              error 198
          }
      In which situation would ("`theTimer'" == ":") be true? The second condition flags when the user didn't type a colon, right?

      3. I think the option to store the timer results is an interesting one. I suppose the easiest way to do this is to allow something along the lines of
      Code:
      timeit 5, name("fifthTimer"): cmd
      Code-wise that complicates things, but it's not insurmountable.

      4. I'm not entirely sure what you mean by multiple commands? You can prefix the same timeit command (e.g. timeit 2) to as many lines as you want, it will keep counting.

      5. I'm not sure how to check if a timer is already running - timer off never returns an error and I can't think of another way to check.

      6. What's the use of the nobreak? I looked it up in the help files and it seems to stop the "BREAK" command from working. It would nice to make the timer stop on -break-, but that doesn't seem to be happening with this code?

      Thanks again!

      Comment


      • #4
        In which situation would ("`theTimer'" == ":") be true? The second condition flags when the user didn't type a colon, right?
        If the user types

        Code:
        timeit : whatever
        omitting the timer number. Of course, the timer command later in the code will choke on this, so this check is not vital. One could think about allowing this syntax and merely listing the time instead of storing it in a timer. Something like

        Code:
        set rmsg on
        whatever
        set rmsg off
        3. I think the option to store the timer results is an interesting one. I suppose the easiest way to do this is to allow something along the lines of
        Code:
        timeit 5, name("fifthTimer"): cmd
        Code-wise that complicates things, but it's not insurmountable.
        Options seem a good idea, especially if you also want the user to explicitly specify to override a timer, that is already in use, or likewise to continue running this timer. An optional name could also be parsed as an optional second argument

        Code:
        timeit # [ name ] : cmd
        which would be easier/quicker to parse.

        4. I'm not entirely sure what you mean by multiple commands? You can prefix the same timeit command (e.g. timeit 2) to as many lines as you want, it will keep counting.
        I was thinking into the direction of, e.g., mi xeq, with syntax like

        Code:
        timeit # : cmd1 ; cmd2 [...]
        5. I'm not sure how to check if a timer is already running - timer off never returns an error and I can't think of another way to check.
        This is indeed a little effort. Here is how I would do it

        Code:
        tempname r_results
        _return hold `r_results'
        quietly timer list `theTimer'
        if (r(t`theTimer') != .) {
            display as text "timer `theTimer' already in use"
        }
        _return restore `r_results'
        6. What's the use of the nobreak? I looked it up in the help files and it seems to stop the "BREAK" command from working. It would nice to make the timer stop on -break-, but that doesn't seem to be happening with this code?
        Actually, this is bug. I want Stata to stop the timer no matter what. If I code

        Code:
        capture noisily ...
        timer off #
        I am not a hundred percent sure that Stata will run the second line of code, if the break key is pressed. That is why I enclose the commands in nobreak. However, I want to still allow the user to break the command. Therefore I should have coded

        Code:
        nobreak {
            timer on `theTimer'
                capture noisily break `0'
            timer off `theTimer'
        }
        Note the break after noisily, which temporarily turns the break key on. Even better would be capturing the return code, so I could add more stuff, including other capture prefixes, before the program ends.

        Code:
        nobreak {
            timer on `theTimer'
                capture noisily break `0'
                local Rc = _rc
            timer off `theTimer'
        }
        
        exit `Rc'
        Of course, this is, strictly speaking, no longer equivalent to

        Code:
        timer on #
        cmd
        timer off #
        because Stata needs to assign the return code to local Rc, which will take a minimum amount of time.

        Best
        Daniel
        Last edited by daniel klein; 15 Aug 2016, 09:10.

        Comment


        • #5
          I've tried to incorporate all the suggestions.

          1. You can now specify a name as in timeit # name: cmd. The timer will be stored in scalar name.
          2. Extensive syntax checking, with automated suggestions covering most cases (if I missed any, please let me know)
          3. Any return results present will now persist through the timeit (some of the checks flush r() )
          4. The program will give a warning (but continue) if a timer is already running
          I do this by first checking the timer (e.g. r(t#) from timer list) and then checking it again after a timer off # command. If the timer was active, these times will diverge.
          5. The program will not respond to using a timer that was used before. This imo should be possible. E.g. using this in a loop.
          6. The timer will stop if the command leads to an error, or if the user presses break.

          Any more suggestions? Always welcome!

          PS: daniel klein I'm not entirely sure what you mean with the last point (capturing the rc code).
          Attached Files

          Comment


          • #6
            Updates above are now on ssc, thanks as usual to the relentless Kit Baum!

            Comment


            • #7
              Bug fix: -timeit- no longer returns an error when you use factor variable syntax (i. and so on).

              PS: If some user programmer reads this to figure out why their program is throwing errors on i. and so on, in my case it was due to the version statement, which messes with the factor variables if set to something lower than version 11.
              Last edited by Jesse Wursten; 27 Dec 2016, 14:53.

              Comment


              • #8
                A new version of timeit is now available on ssc. There were three major changes.
                1. You can now calculate incremental timers. By default, timer list only gives you the total runtime of a particular timer, the timeit command also produces the runtime of the most recent iteration.
                2. Timeit now adds the relevant runtime values to r(), that is, if you type timeit 1 somename: cmd, r() will now contain the results from cmd, as well as r(t1), r(somename) and r(delta_t1). This means you can immediately process the runtimes in whichever fashion you desire.
                3. Bugfixes. The timeit command was having issues with quotes in the cmd and struggling to retain the right results in r(). The latter is surprisingly difficult. Now however, r() will contain whatever was in there before if your command did not alter r(), and whatever your command put in r() if it did do so. (As well as the timer results).
                If you use this command and get an unexpected result, please let me know. I have tried to subject it to very ludicrous commands and so far it's handled all of them, but the list is obviously not complete (e.g. I missed factor notations initially).

                Comment


                • #9
                  Hi Jesse Wursten . Thanks very much for your program.
                  It just occured to me that it would also be really helpful if one could use timeit before running external do files
                  (e.g. timeit 1 dofile1 : do foo.do).

                  Comment


                  • #10
                    Originally posted by Santiago Cantillo View Post
                    Hi Jesse Wursten . Thanks very much for your program.
                    It just occured to me that it would also be really helpful if one could use timeit before running external do files
                    (e.g. timeit 1 dofile1 : do foo.do).
                    Code:
                    timeit 1: do sleep2000.do
                    That worked for me?

                    Comment


                    • #11
                      Your right, I tried that and it worked. But if the name of the do file has spaces it won't work.
                      Code:
                       
                       timeit 1: do "sleep 2000.do"
                      produces type mismatch "r(109)".
                      I also received this error message when trying to merge a file that had spaces in the name.
                      Perhaps the issue occurs when doing any operation involving file names with spaces?

                      Comment


                      • #12
                        Hmm, you seem to be correct. I should be able to fix this.

                        EDIT: I think I fixed it. Updated version is en route to SSC, also attached below.
                        Attached Files
                        Last edited by Jesse Wursten; 27 Nov 2018, 03:19.

                        Comment


                        • #13
                          -timeit- returns an error when there is empty space before the semicolon.

                          E.g.,

                          Code:
                          . clear
                          
                          . timer clear
                          
                          . set obs 10
                          number of observations (_N) was 0, now 10
                          
                          . timeit 1 : gen u = runiform()
                          = invalid name
                          r(198);
                          
                          .
                          This is not an end-of-the-world problem, but it is a bit inconsistent with usual Stata behaviour where additional empty spaces typically do not cause issues. I do use empty spaces a lot to improve readability of code.

                          Comment


                          • #14
                            Nice catch. This should be fixed in the version on its way to SSC now. I've also attached it here.
                            Attached Files

                            Comment


                            • #15
                              I’m interested in using timeit before running a do file, but I’m getting an error and I think the wildcard asterisk is causing the issue. Here are two simplified examples

                              Code:
                              clear all
                               
                              program define my_program
                                          sysuse "auto.dta"
                                          ren weigh* weigh
                              end
                               
                              program define my_program_alt
                                          sysuse "auto.dta"
                                          rename *, lower
                              end
                               
                               
                               
                              timeit 1: my_program
                              timeit 2: my_program_alt
                              Code:
                              . timeit 1: my_program
                              (1978 Automobile Data)
                              * invalid name
                              r(198);
                               
                              . timeit 2: my_program_alt
                              (1978 Automobile Data)
                              * invalid name
                              r(198);
                              Is there a workaround?

                              Comment

                              Working...
                              X