Subject: Re: bug#79074: 31.0.50; Increase gc-cons-percentage in Makefiles

From: Stefan Monnier <[email-masked]>
Date: Mon, 28 Jul 2025 13:10:34 -0400

>> Here are the stats for this Gnus process:
>>
>>     M-x gc--opportunistic-score RET
>>     ((jit 1 0 0) (opportunistic-gcs . 2744) (cmd 104 11 28) (earlier-gcs
>> . 69) (noncmd 18 6 16) (commands . 369311))
> I suppose you collect this data in pre/post-command-hooks?

Mostly, yes.  See code below.

        Stefan

;;;; Opportunistic GC

;; Scores so far (single-gc-cmds / opportunistic / total gcs):
;; 0 / 0 / 80  (Gnus)
;; 0 / 0 / 100 (X with Org)
;; 41 / 0 / 228 (Gnus)
;; 32 / 178 / 450      (X)
;; 3427 / 6911 / 11388 (Gnus)

;; Without opportunistic GC:
;;   X: ((multi-gcs . 128) (multi-gc-cmds . 25) (single-gc-cmds . 357) (commands . 128966))
;;   Gnus: ((single-gc-cmds . 961) (multi-gcs . 1004) (multi-gc-cmds . 334) (commands . 122678))
;; With opportunistic GC:
;;   ((multi-gcs . 103) (multi-gc-cmds . 28) (single-gc-cmds . 333) (noncmds-gcs . 4485) (noncmds . 4369) (earlier-gcs . 52) (opportunistic-gcs . 1557) (commands . 156917))

(defvar gc--opportunistic-last-gcs gcs-done)
(defvar gc--opportunistic-state 'noncmd)
(defvar gc--opportunistic-counters nil)

(defun gc--opportunistic-record (nextstate)
  (let ((counts (alist-get gc--opportunistic-state gc--opportunistic-counters)))
    (unless counts
      (setf (alist-get gc--opportunistic-state gc--opportunistic-counters)
            (setq counts (list 0 0 0))))
    (pcase (prog1 (- gcs-done gc--opportunistic-last-gcs)
             (setq gc--opportunistic-last-gcs gcs-done))
      ((pred (>= 0)) nil)
      (1 (cl-incf (nth 0 counts)))
      (gcs (cl-incf (nth 1 counts))
           (cl-incf (nth 2 counts) gcs))))
  (setq gc--opportunistic-state nextstate))

(defun gc--opportunistic-postch ()
  (cl-incf (alist-get 'commands gc--opportunistic-counters 0))
  (gc--opportunistic-record 'noncmd))

(defun gc--opportunistic-prech ()
  (cl-callf identity
      (alist-get 'earlier-gcs gc--opportunistic-counters gcs-done))
  (gc--opportunistic-record 'cmd))

(defun gc--opportunistic-jitlock (orig-fun start)
  (if (eq gc--opportunistic-state 'cmd)
      ;; Count jit-lock execution which happens during a command as
      ;; being part of command execution rather than as part of jit-lock!
      (funcall orig-fun start)
    (let ((gc--opportunistic-state gc--opportunistic-state))
      (gc--opportunistic-record 'jit)
      (unwind-protect
          (funcall orig-fun start)
        (gc--opportunistic-record 'postjit)))))

(add-hook 'pre-command-hook #'gc--opportunistic-prech)
(add-hook 'post-command-hook #'gc--opportunistic-postch)
(advice-add 'jit-lock-function :around #'gc--opportunistic-jitlock)

(defun gc--opportunistic ()
  "Run the GC during idle time."
  ;; This is good for two reasons:
  ;; - It reduces the number of times we have to GC in the middle of
  ;;   an operation.
  ;; - It means we GC when the C stack is short, reducing the risk of false
  ;;   positives from the conservative stack scanning.
  (unless (> gc-cons-percentage 0.1)
    (setq gc-cons-percentage 0.5))
  (when (garbage-collect-maybe 10)
    (cl-incf (alist-get 'opportunistic-gcs gc--opportunistic-counters 0))
    ;; Don't double count this GC in other categories.
    (cl-incf gc--opportunistic-last-gcs)
    ;; Recalibrate the timer.
    (cancel-function-timers #'gc--opportunistic)
    (run-with-idle-timer
     ;; FIXME: Magic formula!
     (+ 1 (* 10 (/ gc-elapsed gcs-done))) t #'gc--opportunistic)))

(defun gc--opportunistic-score ()
  "Show the current counters's that keep track of GC behavior."
  (interactive)
  (message "%S" gc--opportunistic-counters))