Skip to content

Commit 12d9e1e

Browse files
committed
Improve quality of our ANSI C clock() function
It now works most excellently across all supported operating sytsems (earlier it didn't work on NT and XNU). Demo code is available in examples/clock.c and this change also adds some of the newer ANSI C time functions like timespec_get(), plus timespec_getres() which hasn't even come out yet as it's C23
1 parent 7ff0ea8 commit 12d9e1e

24 files changed

+254
-76
lines changed

examples/clock.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#if 0
2+
/*─────────────────────────────────────────────────────────────────╗
3+
│ To the extent possible under law, Justine Tunney has waived │
4+
│ all copyright and related or neighboring rights to this file, │
5+
│ as it is written in the following disclaimers: │
6+
│ • http://unlicense.org/ │
7+
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8+
╚─────────────────────────────────────────────────────────────────*/
9+
#endif
10+
#include "libc/calls/struct/timespec.h"
11+
#include "libc/stdio/stdio.h"
12+
#include "libc/time/time.h"
13+
14+
/**
15+
* @fileoverview clock() function demo
16+
*/
17+
18+
int main(int argc, char *argv[]) {
19+
unsigned long i;
20+
volatile unsigned long x;
21+
struct timespec now, start, next, interval;
22+
printf("hammering the cpu...\n");
23+
next = start = _timespec_mono();
24+
interval = _timespec_frommillis(500);
25+
next = _timespec_add(next, interval);
26+
for (;;) {
27+
for (i = 0;; ++i) {
28+
x *= 7;
29+
if (!(i % 256)) {
30+
now = _timespec_mono();
31+
if (_timespec_gte(now, next)) {
32+
break;
33+
}
34+
}
35+
}
36+
next = _timespec_add(next, interval);
37+
printf("consumed %10g seconds monotonic time and %10g seconds cpu time\n",
38+
_timespec_tonanos(_timespec_sub(now, start)) / 1000000000.,
39+
(double)clock() / CLOCKS_PER_SEC);
40+
}
41+
}

examples/hostname.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
╚─────────────────────────────────────────────────────────────────*/
99
#endif
1010
#include "libc/calls/calls.h"
11+
#include "libc/intrin/kprintf.h"
1112
#include "libc/log/check.h"
12-
#include "libc/stdio/stdio.h"
1313

1414
int main(int argc, char *argv[]) {
1515
char name[254];
16-
CHECK_NE(-1, gethostname(name, sizeof(name)));
17-
printf("gethostname() → %`'s\n", name);
18-
CHECK_NE(-1, getdomainname(name, sizeof(name)));
19-
printf("getdomainname() → %`'s\n", name);
16+
gethostname(name, sizeof(name));
17+
kprintf("gethostname() → %#s\n", name);
18+
getdomainname(name, sizeof(name));
19+
kprintf("getdomainname() → %#s\n", name);
2020
return 0;
2121
}

libc/bits/atomic.h

Whitespace-only changes.

libc/calls/_timespec_fromnanos.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
2+
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
3+
╞══════════════════════════════════════════════════════════════════════════════╡
4+
│ Copyright 2022 Justine Alexandra Roberts Tunney │
5+
│ │
6+
│ Permission to use, copy, modify, and/or distribute this software for │
7+
│ any purpose with or without fee is hereby granted, provided that the │
8+
│ above copyright notice and this permission notice appear in all copies. │
9+
│ │
10+
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
11+
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
12+
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
13+
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
14+
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
15+
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
16+
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
17+
│ PERFORMANCE OF THIS SOFTWARE. │
18+
╚─────────────────────────────────────────────────────────────────────────────*/
19+
#include "libc/calls/struct/timespec.h"
20+
21+
/**
22+
* Converts timespec interval from nanoseconds.
23+
*/
24+
struct timespec _timespec_fromnanos(int64_t x) {
25+
struct timespec ts;
26+
ts.tv_sec = x / 1000000000;
27+
ts.tv_nsec = x % 1000000000;
28+
return ts;
29+
}
File renamed without changes.

libc/calls/calls.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,11 @@ o//libc/calls/statfs2cosmo.o: private \
184184

185185
# we always want -O2 because:
186186
# division is expensive if not optimized
187+
o/$(MODE)/libc/calls/clock.o \
187188
o/$(MODE)/libc/calls/_timespec_tomillis.o \
188189
o/$(MODE)/libc/calls/_timespec_tomicros.o \
189190
o/$(MODE)/libc/calls/_timespec_totimeval.o \
191+
o/$(MODE)/libc/calls/_timespec_fromnanos.o \
190192
o/$(MODE)/libc/calls/_timespec_frommillis.o \
191193
o/$(MODE)/libc/calls/_timespec_frommicros.o: private \
192194
OVERRIDE_CFLAGS += \

libc/calls/clock.c

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,51 @@
1616
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
19+
#include "libc/calls/struct/rusage.h"
1920
#include "libc/calls/struct/timespec.h"
20-
#include "libc/dce.h"
21-
#include "libc/fmt/conv.h"
22-
#include "libc/nt/accounting.h"
23-
#include "libc/nt/runtime.h"
24-
#include "libc/nt/synchronization.h"
21+
#include "libc/calls/struct/timeval.h"
22+
#include "libc/errno.h"
2523
#include "libc/sysv/consts/clock.h"
24+
#include "libc/sysv/consts/rusage.h"
2625
#include "libc/time/time.h"
2726

2827
/**
29-
* Returns how much CPU program has consumed on time-sharing system.
28+
* Returns sum of CPU time consumed by current process since birth.
3029
*
31-
* @return value that can be divided by CLOCKS_PER_SEC, or -1 w/ errno
32-
* @see clock_gettime()
30+
* This function provides a basic idea of how computationally expensive
31+
* your program is, in terms of both the userspace and kernel processor
32+
* resources it's hitherto consumed. Here's an example of how you might
33+
* display this information:
34+
*
35+
* printf("consumed %g seconds of cpu time\n",
36+
* (double)clock() / CLOCKS_PER_SEC);
37+
*
38+
* This function offers at best microsecond accuracy on all supported
39+
* platforms. Please note the reported values might be a bit chunkier
40+
* depending on the kernel scheduler sampling interval see `CLK_TCK`.
41+
*
42+
* @return units of CPU time consumed, where each unit's time length
43+
* should be `1./CLOCKS_PER_SEC` seconds; Cosmopolitan currently
44+
* returns the unit count in microseconds, i.e. `CLOCKS_PER_SEC`
45+
* is hard-coded as 1000000. On failure this returns -1 / errno.
46+
* @raise ENOSYS should be returned currently if run on Bare Metal
47+
* @see clock_gettime() which polyfills this on Linux and BSDs
48+
* @see getrusage() which polyfills this on XNU and NT
3349
*/
3450
int64_t clock(void) {
51+
int e;
52+
struct rusage ru;
3553
struct timespec ts;
36-
struct NtFileTime creation_time, exit_time, kernel_time, user_time;
37-
int64_t proc, total;
38-
// polyfill on Windows where CLOCK_PROCESS_CPUTIME_ID may be not available
39-
if (IsWindows() && CLOCK_PROCESS_CPUTIME_ID == -1) {
40-
proc = GetCurrentProcess();
41-
if (!GetProcessTimes(proc, &creation_time, &exit_time, &kernel_time,
42-
&user_time))
54+
e = errno;
55+
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == -1) {
56+
errno = e;
57+
if (getrusage(RUSAGE_SELF, &ru) != -1) {
58+
ts = _timeval_totimespec(_timeval_add(ru.ru_utime, ru.ru_stime));
59+
} else {
4360
return -1;
44-
total = ReadFileTime(kernel_time) + ReadFileTime(user_time);
45-
ts = WindowsDurationToTimeSpec(total);
46-
} else if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == -1) {
47-
return -1;
61+
}
4862
}
49-
return ts.tv_sec * CLOCKS_PER_SEC +
50-
ts.tv_nsec / (1000000000 / CLOCKS_PER_SEC);
63+
// convert nanoseconds to microseconds w/ ceil rounding
64+
// this would need roughly ~7,019,309 years to overflow
65+
return ts.tv_sec * 1000000 + (ts.tv_nsec + 999) / 1000;
5166
}

libc/calls/clock_getres.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
#include "libc/sysv/errfuns.h"
2626
#include "libc/time/time.h"
2727

28-
int sys_clock_getres(int, struct timespec *) hidden;
29-
3028
static int sys_clock_getres_poly(int clock, struct timespec *ts, int64_t real) {
3129
if (clock == CLOCK_REALTIME) {
3230
ts->tv_sec = 0;
@@ -65,6 +63,7 @@ int clock_getres(int clock, struct timespec *ts) {
6563
} else {
6664
rc = sys_clock_getres(clock, ts);
6765
}
68-
STRACE("clock_getres(%d, [%s]) → %d% m", clock, DescribeTimespec(rc, ts), rc);
66+
STRACE("clock_getres(%s, [%s]) → %d% m", DescribeClockName(clock),
67+
DescribeTimespec(rc, ts), rc);
6968
return rc;
7069
}

libc/calls/clock_gettime-mono.c

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,33 @@
1616
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
19-
#include "libc/calls/clock_gettime.internal.h"
2019
#include "libc/calls/struct/timespec.h"
2120
#include "libc/intrin/pthread.h"
2221
#include "libc/nexgen32e/rdtsc.h"
2322
#include "libc/nexgen32e/x86feature.h"
2423
#include "libc/sysv/consts/clock.h"
2524
#include "libc/sysv/errfuns.h"
26-
#include "libc/time/clockstonanos.internal.h"
2725

2826
static struct {
2927
pthread_once_t once;
30-
uint64_t base;
31-
struct timespec mono;
28+
struct timespec base_wall;
29+
uint64_t base_tick;
3230
} g_mono;
3331

3432
static void sys_clock_gettime_mono_init(void) {
35-
clock_gettime(CLOCK_REALTIME, &g_mono.mono);
36-
g_mono.base = rdtsc();
37-
g_mono.once = true;
33+
g_mono.base_wall = _timespec_real();
34+
g_mono.base_tick = rdtsc();
3835
}
3936

40-
int sys_clock_gettime_mono(struct timespec *ts) {
41-
// this routine stops being monotonic after 194 years of uptime
37+
int sys_clock_gettime_mono(struct timespec *time) {
4238
uint64_t nanos;
39+
uint64_t cycles;
4340
struct timespec res;
4441
if (X86_HAVE(INVTSC)) {
4542
pthread_once(&g_mono.once, sys_clock_gettime_mono_init);
46-
nanos = ClocksToNanos(rdtsc(), g_mono.base);
47-
res = g_mono.mono;
48-
res.tv_sec += nanos / 1000000000;
49-
res.tv_nsec += nanos % 1000000000;
50-
*ts = res;
43+
cycles = rdtsc() - g_mono.base_tick;
44+
nanos = cycles / 3;
45+
*time = _timespec_add(g_mono.base_wall, _timespec_fromnanos(nanos));
5146
return 0;
5247
} else {
5348
return einval();

libc/calls/clock_gettime.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ int clock_gettime(int clock, struct timespec *ts) {
6666
}
6767
#if SYSDEBUG
6868
if (!__time_critical) {
69-
STRACE("clock_gettime(%d, [%s]) → %d% m", clock, DescribeTimespec(rc, ts),
70-
rc);
69+
STRACE("clock_gettime(%s, [%s]) → %d% m", DescribeClockName(clock),
70+
DescribeTimespec(rc, ts), rc);
7171
}
7272
#endif
7373
return rc;

0 commit comments

Comments
 (0)