posix-clocks.scm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. ;; POSIX clocks
  2. ;;;; Copyright (C) 2016 Andy Wingo <wingo@pobox.com>
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 3 of the License, or (at your option) any later version.
  8. ;;;;
  9. ;;;; This library is distributed in the hope that it will be useful,
  10. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;;; Lesser General Public License for more details.
  13. ;;;;
  14. ;;;; You should have received a copy of the GNU Lesser General Public
  15. ;;;; License along with this library; if not, write to the Free Software
  16. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Fibers uses POSIX clocks to be able to preempt schedulers running
  18. ;;; in other threads after regular timeouts in terms of thread CPU time.
  19. (define-module (fibers posix-clocks)
  20. #:use-module (system foreign)
  21. #:use-module (ice-9 match)
  22. #:use-module (rnrs bytevectors)
  23. #:export (CLOCK_REALTIME
  24. CLOCK_MONOTONIC
  25. CLOCK_PROCESS_CPUTIME_ID
  26. CLOCK_THREAD_CPUTIME_ID
  27. CLOCK_MONOTONIC_RAW
  28. CLOCK_REALTIME_COARSE
  29. CLOCK_MONOTONIC_COARSE
  30. clock-getcpuclockid
  31. pthread-getcpuclockid
  32. pthread-self
  33. clock-getres
  34. clock-gettime
  35. clock-nanosleep))
  36. (define exe (dynamic-link))
  37. (define clockid-t int32)
  38. (define time-t long)
  39. (define pid-t int)
  40. (define pthread-t unsigned-long)
  41. (define struct-timespec (list time-t long))
  42. (define TIMER_ABSTIME 1)
  43. (define CLOCK_REALTIME 0)
  44. (define CLOCK_MONOTONIC 1)
  45. (define CLOCK_PROCESS_CPUTIME_ID 2)
  46. (define CLOCK_THREAD_CPUTIME_ID 3)
  47. (define CLOCK_MONOTONIC_RAW 4)
  48. (define CLOCK_REALTIME_COARSE 5)
  49. (define CLOCK_MONOTONIC_COARSE 6)
  50. (define clock-getcpuclockid
  51. (let* ((ptr (dynamic-pointer "clock_getcpuclockid" exe))
  52. (proc (pointer->procedure int ptr (list pid-t '*)
  53. #:return-errno? #t)))
  54. (lambda* (pid #:optional (buf (make-bytevector (sizeof clockid-t))))
  55. (call-with-values (lambda () (proc pid (bytevector->pointer buf)))
  56. (lambda (ret errno)
  57. (unless (zero? ret) (error (strerror errno)))
  58. (bytevector-s32-native-ref buf 0))))))
  59. (define pthread-self
  60. (let* ((ptr (dynamic-pointer "pthread_self" exe))
  61. (proc (pointer->procedure pthread-t ptr '())))
  62. (lambda ()
  63. (proc))))
  64. (define pthread-getcpuclockid
  65. (let* ((ptr (dynamic-pointer "pthread_getcpuclockid" exe))
  66. (proc (pointer->procedure int ptr (list pthread-t '*)
  67. #:return-errno? #t)))
  68. (lambda* (pthread #:optional (buf (make-bytevector (sizeof clockid-t))))
  69. (call-with-values (lambda () (proc pthread (bytevector->pointer buf)))
  70. (lambda (ret errno)
  71. (unless (zero? ret) (error (strerror errno)))
  72. (bytevector-s32-native-ref buf 0))))))
  73. (define clock-getres
  74. (let* ((ptr (dynamic-pointer "clock_getres" exe))
  75. (proc (pointer->procedure int ptr (list clockid-t '*)
  76. #:return-errno? #t)))
  77. (lambda* (clockid #:optional (buf (nsec->timespec 0)))
  78. (call-with-values (lambda () (proc clockid buf))
  79. (lambda (ret errno)
  80. (unless (zero? ret) (error (strerror errno)))
  81. (timespec->nsec buf))))))
  82. (define clock-gettime
  83. (let* ((ptr (dynamic-pointer "clock_gettime" exe))
  84. (proc (pointer->procedure int ptr (list clockid-t '*)
  85. #:return-errno? #t)))
  86. (lambda* (clockid #:optional (buf (nsec->timespec 0)))
  87. (call-with-values (lambda () (proc clockid buf))
  88. (lambda (ret errno)
  89. (unless (zero? ret) (error (strerror errno)))
  90. (timespec->nsec buf))))))
  91. (define (nsec->timespec nsec)
  92. (make-c-struct struct-timespec
  93. (list (quotient nsec #e1e9) (modulo nsec #e1e9))))
  94. (define (timespec->nsec ts)
  95. (match (parse-c-struct ts struct-timespec)
  96. ((sec nsec)
  97. (+ (* sec #e1e9) nsec))))
  98. (define clock-nanosleep
  99. (let* ((ptr (dynamic-pointer "clock_nanosleep" exe))
  100. (proc (pointer->procedure int ptr (list clockid-t int '* '*))))
  101. (lambda* (clockid nsec #:key absolute? (buf (nsec->timespec nsec)))
  102. (let* ((flags (if absolute? TIMER_ABSTIME 0))
  103. (ret (proc clockid flags buf buf)))
  104. (cond
  105. ((zero? ret) (values #t 0))
  106. ((eqv? ret EINTR) (values #f (timespec->nsec buf)))
  107. (else (error (strerror ret))))))))
  108. ;; Quick little test to determine the resolution of clock-nanosleep on
  109. ;; different clock types, and how much CPU that takes. Results on
  110. ;; this 2-core, 2-thread-per-core skylake laptop:
  111. ;;
  112. ;; Clock type | Applied Hz | Actual Hz | CPU time overhead (%)
  113. ;; ---------------------------------------------------------------
  114. ;; MONOTONIC 100 98 0.4
  115. ;; MONOTONIC 1000 873 4.4
  116. ;; MONOTONIC 10000 6242 6.4
  117. ;; MONOTONIC 100000 14479 13.6
  118. ;; REALTIME 100 98 0.5
  119. ;; REALTIME 1000 872 4.5
  120. ;; REALTIME 10000 6238 6.5
  121. ;; REALTIME 100000 14590 12.2
  122. ;; PROCESS_CPUTIME 100 84 1.0
  123. ;; PROCESS_CPUTIME 10000 250 1.0
  124. ;; PROCESS_CPUTIME 100000 250 1.0
  125. ;; pthread cputime 100 84 0.9
  126. ;; pthread cputime 10000 250 0.6
  127. ;;
  128. ;; The cputime benchmarks were run with a background thread in a busy
  129. ;; loop to allow that clock to advance at the same rate as a wall
  130. ;; clock.
  131. ;;
  132. ;; Conclusions: The monotonic and realtime clocks are relatively
  133. ;; high-precision, allowing for sub-millisecond scheduling quanta, but
  134. ;; they have to be actively managed (explicitly deactivated while
  135. ;; waiting on FD events). The cputime clocks don't have to be
  136. ;; actively managed, but they aren't as high-precision either.
  137. (define (test clock period)
  138. (let ((start (clock-gettime CLOCK_PROCESS_CPUTIME_ID))
  139. (until (+ (clock-gettime clock) #e1e9)))
  140. (let lp ((n 0))
  141. (if (< (clock-gettime clock) until)
  142. (begin
  143. (clock-nanosleep clock period)
  144. (lp (1+ n)))
  145. (values n (/ (- (clock-gettime CLOCK_PROCESS_CPUTIME_ID) start)
  146. 1e9))))))