|
| 1 | +;;; first-hot-loop.el --- first calls through hot loops -*- lexical-binding: t; -*- |
| 2 | +(require 'bytecomp) |
| 3 | +(require 'json) |
| 4 | + |
| 5 | +;; One operation is a fresh function's first call. Byte compilation and GC are |
| 6 | +;; setup; interpreter heat-up, OSR compilation, execution and result collection |
| 7 | +;; are timed together. No function is called during preparation. |
| 8 | +(defconst neomacs-perf-first-hot-loop--inner-iterations 65536) |
| 9 | + |
| 10 | +(defvar neomacs-perf-first-hot-loop--profile-gate-process nil) |
| 11 | +(defvar neomacs-perf-first-hot-loop--profile-gate-response "") |
| 12 | + |
| 13 | +(defun neomacs-perf-first-hot-loop--profile-gate-filter (_process output) |
| 14 | + (setq neomacs-perf-first-hot-loop--profile-gate-response |
| 15 | + (concat neomacs-perf-first-hot-loop--profile-gate-response output))) |
| 16 | + |
| 17 | +(defun neomacs-perf-first-hot-loop--profile-gate-connect () |
| 18 | + (let* ((port-text (getenv "NEOMACS_PERF_GATE_PORT")) |
| 19 | + (port (and port-text (string-to-number port-text)))) |
| 20 | + (when (and port-text (not (> port 0))) |
| 21 | + (error "invalid first-hot-loop profile gate port %S" port-text)) |
| 22 | + (when (and port-text |
| 23 | + (not (process-live-p |
| 24 | + neomacs-perf-first-hot-loop--profile-gate-process))) |
| 25 | + (setq neomacs-perf-first-hot-loop--profile-gate-process |
| 26 | + (make-network-process |
| 27 | + :name "neomacs-perf-first-hot-loop-gate" |
| 28 | + :family 'ipv4 |
| 29 | + :host "127.0.0.1" |
| 30 | + :service port |
| 31 | + :coding 'binary |
| 32 | + :noquery t |
| 33 | + :filter #'neomacs-perf-first-hot-loop--profile-gate-filter))) |
| 34 | + neomacs-perf-first-hot-loop--profile-gate-process)) |
| 35 | + |
| 36 | +(defun neomacs-perf-first-hot-loop--sampling-command (command) |
| 37 | + (let ((process (neomacs-perf-first-hot-loop--profile-gate-connect))) |
| 38 | + (when process |
| 39 | + (setq neomacs-perf-first-hot-loop--profile-gate-response "") |
| 40 | + (process-send-string process (concat command "\n")) |
| 41 | + (let ((deadline (+ (float-time) 30.0))) |
| 42 | + (while (and |
| 43 | + (not (and |
| 44 | + (> (length |
| 45 | + neomacs-perf-first-hot-loop--profile-gate-response) |
| 46 | + 0) |
| 47 | + (= (aref |
| 48 | + neomacs-perf-first-hot-loop--profile-gate-response |
| 49 | + (1- (length |
| 50 | + neomacs-perf-first-hot-loop--profile-gate-response))) |
| 51 | + ?\n))) |
| 52 | + (< (float-time) deadline)) |
| 53 | + (unless (process-live-p process) |
| 54 | + (error "first-hot-loop profile gate disconnected during %s" command)) |
| 55 | + (accept-process-output process 0.05)) |
| 56 | + (unless (equal neomacs-perf-first-hot-loop--profile-gate-response |
| 57 | + "ack\n") |
| 58 | + (error "first-hot-loop profile gate rejected %s: %S" |
| 59 | + command |
| 60 | + neomacs-perf-first-hot-loop--profile-gate-response)))))) |
| 61 | + |
| 62 | +(defun neomacs-perf-first-hot-loop--close-profile-gate () |
| 63 | + (when (processp neomacs-perf-first-hot-loop--profile-gate-process) |
| 64 | + (delete-process neomacs-perf-first-hot-loop--profile-gate-process) |
| 65 | + (setq neomacs-perf-first-hot-loop--profile-gate-process nil))) |
| 66 | + |
| 67 | +(defun neomacs-perf-first-hot-loop--run () |
| 68 | + (let* ((scenario (getenv "NEOMACS_PERF_WORKLOAD")) |
| 69 | + (iterations (string-to-number (or (getenv "NEOMACS_PERF_ITERATIONS") "0"))) |
| 70 | + (functions nil) (results nil) (index 0) (compiled t) |
| 71 | + (prepared 0) (completed 0) (elapsed-us 0) (wall-us 0) |
| 72 | + (status "error") (error-message nil) (exit-code 2)) |
| 73 | + (condition-case err |
| 74 | + (progn |
| 75 | + (unless (and (> iterations 0) (equal scenario "first-hot-loop")) |
| 76 | + (error "Invalid first-hot-loop input")) |
| 77 | + (while (< index iterations) |
| 78 | + ;; Fresh compiler outputs and distinct constants prevent accidental |
| 79 | + ;; reuse of one function's native cache for the entire sample. |
| 80 | + (let ((function |
| 81 | + (byte-compile |
| 82 | + `(lambda (n) |
| 83 | + (let ((held ,index) (i 0) (sum 0)) |
| 84 | + (while (< i n) |
| 85 | + (setq sum (+ sum i) i (1+ i))) |
| 86 | + (list i sum held)))))) |
| 87 | + (unless (byte-code-function-p function) |
| 88 | + (setq compiled nil) |
| 89 | + (error "First-hot-loop function %d is not bytecode" index)) |
| 90 | + (push function functions)) |
| 91 | + (setq index (1+ index))) |
| 92 | + (setq functions (nreverse functions) prepared (length functions)) |
| 93 | + (garbage-collect) |
| 94 | + (neomacs-perf-first-hot-loop--sampling-command "enable") |
| 95 | + (unwind-protect |
| 96 | + (let ((cpu-start (car (current-cpu-time))) |
| 97 | + (wall-start (float-time))) |
| 98 | + (dolist (function functions) |
| 99 | + (push (funcall function neomacs-perf-first-hot-loop--inner-iterations) |
| 100 | + results)) |
| 101 | + (setq elapsed-us (- (car (current-cpu-time)) cpu-start) |
| 102 | + wall-us (round (* 1000000 (- (float-time) wall-start))))) |
| 103 | + (neomacs-perf-first-hot-loop--sampling-command "disable")) |
| 104 | + ;; Keep every result for independent host validation after timing. |
| 105 | + (setq results (nreverse results) completed (length results) |
| 106 | + status "ok" exit-code 0)) |
| 107 | + (error (setq error-message (error-message-string err)))) |
| 108 | + (neomacs-perf-first-hot-loop--close-profile-gate) |
| 109 | + (with-temp-file (getenv "NEOMACS_PERF_RESULT") |
| 110 | + (insert |
| 111 | + (json-serialize |
| 112 | + `((schema_version . 1) (scenario . ,scenario) (status . ,status) |
| 113 | + (iterations . ,iterations) |
| 114 | + (inner_iterations . ,neomacs-perf-first-hot-loop--inner-iterations) |
| 115 | + (prepared_functions . ,prepared) |
| 116 | + (bytecode_compiled . ,(if compiled t :json-false)) |
| 117 | + (completed_operations . ,completed) |
| 118 | + (results . ,(vconcat (mapcar #'vconcat results))) |
| 119 | + (elapsed_us . ,elapsed-us) (elapsed_wall_us . ,wall-us) |
| 120 | + (error . ,error-message)) |
| 121 | + :false-object :json-false :null-object nil))) |
| 122 | + (write-region "done\n" nil (getenv "SENTINEL") nil 'silent) |
| 123 | + (kill-emacs exit-code))) |
| 124 | + |
| 125 | +(if noninteractive |
| 126 | + (neomacs-perf-first-hot-loop--run) |
| 127 | + (run-at-time 0 nil #'neomacs-perf-first-hot-loop--run)) |
| 128 | + |
| 129 | +;;; first-hot-loop.el ends here |
0 commit comments