in reply to Re^2: The most precise second (timer)
in thread The most precise second (timer)

I am running chronyd service, but not running ntpd

Yes, AFAIK the two are mutually exclusive. I updated my node here to suggest some of the chrony configuration directives to look at that might be relevant for clock jumps.

So if sleep is not the right tool for the job, what would it be?

I checked, and interestingly, libev uses nanosleep(2) (or select(2)), which is apparently quite similar to what Time::HiRes's usleep does - so the method actually isn't as fundamentally different from your approach as I initially thought (but Perl's built-in sleep is of course too inaccurate). However, in libev, the calculations for the next event time happen at the C level, so I think it could be a little bit more accurate than if you do the same on the Perl level.

And why does the EV ... seconds diff is greater than the original code (below is simplified version of the original code; on my computer it has diff bellow 0.0004, sometimes the avg is 0.0002)?

The difference might just be from inaccuracies in the calculation of the sleep time, or the time period over which you measured (e.g. there might have been different jitter during the two). I ran a test where I ran my code from here over almost an hour, with the addition that I wrote out the timestamp for each time the event fired. Although my gettimeofday on each event was on average 0.0017s after the whole second (max 0.0021), I could see no signs of a cumulative error - perhaps it would be best if you run your own tests over a longer period of time (like a day) to see if it suits your needs.

Update: Oh, and Average over 3312 deltas: 1.00001844328777194271613548903407496361, I'd hope you'd see this number shrink over an even longer test. Also minor edits.

Replies are listed 'Best First'.
Re^4: The most precise second (timer)
by tukusejssirs (Beadle) on Nov 27, 2019 at 22:08 UTC

    I think you led me to the right direction: nanosecond.

    I used the following script and compared the results with the original code. When averaging differences of 100 seconds (each script), the original comes out a bit better, but when averaging diffs of 300 seconds, the new script is better.

    use strict; use warnings; use Math::BigFloat; use Time::HiRes qw(gettimeofday sleep nanosleep); ... sub old_script { my $max = $_[0]; my @time_test; my $start = time(); for (my $i = 0; $i < $max; $i++) { $start += 1; sleep $start - time(); $time_test[$i] = Math::BigFloat->new(sprintf("%d.%06d", gettim +eofday)); } return @time_test; } sub new_script { my $max = $_[0]; my @time_test; for (my $i = 0; $i < $max; $i++) { nanosleep (1000000000); $time_test[$i] = Math::BigFloat->new(sprintf("%d.%06d", gettim +eofday)); } return @time_test; }

    The averages:

    # 100 s each script old_script_values_avg : 1.000479131313131313131313131313131313131 new_script_values_avg : 1.001016101010101010101010101010101010101 # 300 s each script old_script_values_avg : 0.9974391538461538461538461538461538461538 new_script_values_avg : 1.000457983277591973244147157190635451505

    I believe I cannot get better results without atomic clock. Correct me if I’m wrong.

    Anyway, thanks for the help and your time!

      nanosleep (1000000000);

      I think that with a fixed time like that, you'll probably still see a cumulative error if you run it for a longer time, I don't think 5 minutes is enough.

      However, I only now realized that I overlooked a solution that should have been staring me in the face: clock_nanosleep(2), which is also provided by Time::HiRes. Try running the following over a longer time (several hours):

      use warnings; use strict; use Time::HiRes qw/ clock_gettime clock_nanosleep CLOCK_REALTIME TIMER_ABSTIME /; die "Don't have nanosleep" unless Time::HiRes::d_nanosleep(); my $run = 1; $SIG{INT} = sub { $run = 0 }; while ($run) { my $now_s = int(clock_gettime(CLOCK_REALTIME)); clock_nanosleep(CLOCK_REALTIME, ($now_s+1)*1e9, TIMER_ABSTIME); last unless $run; print clock_gettime(CLOCK_REALTIME), "\n"; }

        Okay, I your point with the fixed times.

        I like the way you made the clock_nanosleep(CLOCK_REALTIME, ($now_s+1)*1e9, TIMER_ABSTIME);. In short tests (like for 10-20 s), the average diff is always 1.00000000000000000000, at least with the 5 decimal places the clock_gettime(CLOCK_REALTIME) outputs.

        Anyway, I am in the middle of 5 h run of the script; I’ll report the results when it is finished.

        Again, thanks for your time!

        Update: I have found out (using printf) that 0001511573791503906250 printf ("%.40f\n\n", clock_gettime(CLOCK_REALTIME)); outputs floats with 21 to 22 decimal places, therefore I’ll print the time out using printf ("%.22f\n", clock_gettime(CLOCK_REALTIME));. This might slow the execution of the script and thus the measurement won’t be that accurate. Or even worse: the extra time (decimal places) are only the time required to execute the extra stuff, I don’t know.

        Update 2: I have the results of the 5-hour run of your script. I have calculated the difference as follows, but my calculations might be wrong, therefore here is the list of 18,000 time values. Note that the machine was not running only this script, because the timer (periodic runner) won’t be run as the only program on the server in production.

        average diff between times : +0.999999998888827179577276638156035915 +0171 average diff from second : +0.000000001111172842935718417798556472 +2552

        This is quite nice. I has already surpassed my assumptions. I’ve read the Time::HiRes readme, so I do not expect clock_nanosleep() to be exact down to one nanosecond, but the average results above tells me that that it is as least _nearly_ that accurate (at least on my system and its configuration).

        hawkex, I can’t thank you enough! You’re great!