From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier Newsgroups: gmane.emacs.devel Subject: Opportunistic GC Date: Sun, 07 Mar 2021 22:35:57 -0500 Message-ID: Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="33073"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Mar 08 04:36:38 2021 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lJ6hB-0008Wa-Tz for ged-emacs-devel@m.gmane-mx.org; Mon, 08 Mar 2021 04:36:38 +0100 Original-Received: from localhost ([::1]:45914 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lJ6hA-00036U-VL for ged-emacs-devel@m.gmane-mx.org; Sun, 07 Mar 2021 22:36:36 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:56414) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lJ6gf-0002hO-BF for emacs-devel@gnu.org; Sun, 07 Mar 2021 22:36:05 -0500 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:63549) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lJ6gb-0008Ci-KE for emacs-devel@gnu.org; Sun, 07 Mar 2021 22:36:04 -0500 Original-Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 1150B4409AD; Sun, 7 Mar 2021 22:36:00 -0500 (EST) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 27E38440975; Sun, 7 Mar 2021 22:35:58 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1615174558; bh=cTBtflVutdhaYJvqexBeEpwAT9h/Q1IiRePU5pn1h4E=; h=From:To:Subject:Date:From; b=Pz4+4O5G1MKzDKVJgyIWAztYUTIPexT8cXBTJp5C6TQf/1/K1xhPCB8ciNJOIa4xC QRV1gMkDPLq+S+qZ+/7A9tbNfBAPoINHEQK87fmHxW24NNUYmHS2JlnXVtGhx2KRuP OBXu49ADE/4cbs4r2J2fqsClBoJGZbHOFPpum7WaWgxhzf9rtQNOv3fdq+vNbg7m+e 5knnaZHgC0WjCGYlirBwOHM4GNdyBMX8TGsavRCUBqCrgy5gVr3ZAcgYmk4AMBCvx6 6uFB+S61A9pEsY4UMMpbjRcXm3OcubSQtRNqewG6/7P9p2FJRLF4hlMg2XJEfgROPw OdxyC7tQRJ6HA== Original-Received: from alfajor (unknown [216.154.43.249]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 21500120310; Sun, 7 Mar 2021 22:35:58 -0500 (EST) Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:266157 Archived-At: I've been running with the code below recently to try and see if opportunistic GC can be worth the trouble. I've mostly focused on measuring the GC behavior rather than on tuning the opportunistic parameters, so there's no doubt we can do better, but here's what I've gotten so far: - Gnus-focused session: ((opportunistic-gcs . 848) (jit 6 0 0) (cmd 526 241 707) (noncmd 92 1 2) (commands . 65745)) - General session: ((opportunistic-gcs . 1332) (jit 115 0 0) (cmd 239 19 89) (noncmd 1749 18 38) (commands . 176431)) The precise meaning of those numbers can be seen in the code, but here's the explanation in words: `commands` is the number of commands that were run. `opportunistic-gcs` is the number of times we performed GC opportunistically `jit`, `cmd`, and `noncmd` are the number of times we GC'd non-opportunistically during a `cmd`, between commands (for `noncmd`) or during jit-lock. The first number is the number of times a single GC took place during/between the command, the second number counts the number of times several GCs took place, and the third is to sum of those "multiple GCs". The middle and third numbers are those for which opportunistic GC fundamentally can't help very much: the command (or the work done between commands) was just sufficiently long-running that it's hard to avoid running a GC during this period. [ Note that the way we measure for jit-lock it's really unlikely we'll ever get "multiple GCs" because it would require >1 GC performed during a single jit-lock chunk. ] Note that because my opportunistic GC calls (garbage-collect-maybe 3), every opportunistic GC avoids only 1/3 non-opportunistic GC. So, while these numbers don't look completely hopeless, they seem to indicate that opportunistic GCs aren't very useful at least with the tuning parameters I used. To be more useful, maybe we'd need to increase the non-opportunistic `gc-cons-percentage` and/or reduce the idle-time before opportunistic GC (see "magic formula" below). There's also a question of where those `noncmd` GCs come from. (timers? process filters? non-jit part of redisplay? ...?) Stefan (defvar gc--opportunistic-last-gcs gcs-done) (defvar gc--opportunistic-state 'noncmd) (defvar gc--opportunistic-counters nil) (defun gc--check () (let ((gcs-counted (+ (alist-get 'multi-gcs gc--opportunistic-counters 0) (alist-get 'earlier-gcs gc--opportunistic-counters 0) (alist-get 'single-gc-cmds gc--opportunistic-counters 0) (alist-get 'noncmds-gcs gc--opportunistic-counters 0) (alist-get 'opportunistic-gcs gc--opportunistic-counters 0) (or (car (alist-get 'error-gcs gc--opportunistic-counters)) 0)))) (unless (= gcs-done gcs-counted) (push (+ (- gcs-done gcs-counted) (or (car (alist-get 'error-gcs gc--opportunistic-counters)) 0)) (alist-get 'error-gcs gc--opportunistic-counters))))) (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) ;; (gc--check) ) (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. (when (garbage-collect-maybe 3) (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))