Subject: Re: bug#30735: 25.3; slow comment c++-mode

From: Alan Mackenzie <[email-masked]>
Date: 11 Mar 2018 13:01:38 -0000

Hello, Nil.

In article <[email-masked]> you wrote:
> 1. Open the following file (make sure you have write privilege) 

> https://raw.githubusercontent.com/opencog/atomspace/master/tests/unify/
> UnifyUTest.cxxtest

> 2. Enable c++-mode (M-x c++-mode)

> 3. Mark a large region of that file (say l.175 to the end)

> 4. Comment out that region (M-;)

> 5. Meditate in the church of Emacs for a few minutes

> If c++-mode is disabled (using fundamental-mode for instance)
> commenting that same region is instantaneous. After running some
> profiling it seems most of the CPU resources is spent in
> c-syntactic-skip-backward.

Thanks for taking the trouble to report this bug.

Commenting out 2300 lines in fundamental mode is going to be
instantaneous.  However, in C++ Mode, the mode has to check whether the
syntax (i.e. whether in a string/comment, and particularly, whether any
< or > characters are template delimiters) has changed, at any buffer
modification.  It also has to check whether any "starting position" of a
(possibly nested) declaration or function has changed.  This all takes
time.

However, it is taking too much time.  The problem is in a routine which
determines a backward search limit n non-literal characters back (where
"literal" means string or comment).  The lines being commented contain
no such non-literals at all, so each backward limit was being determined
to be n characters before line 175.  Thus the time taken was quadratic
with the size of the section to be commented out.

I've amended the two critical parts of the code so that there is an
absolute limit to how far back this backward search limit may be.  The
code is now significantly faster.

The timings for commenting out those lines with the latest version of CC
Mode running on the unreleased Emacs-26.0.91 are:
  o - (without patch) 622.9 seconds;
  o - (with patch) 40.3 seconds.

40 seconds may still feel too long, but we are talking about over 2000
lines.

> The same slowness occurs with Emacs 25.1. I couldn't try older versions
> due to temacs Segfault compiling issue (unresolved by disabling
> randomize_va_space).

I've adapted the patch to apply to Emacs 25.3.  Would you please try
applying it, recompiling the two changed files (which are in
.../emacs/lisp/progmodes/) and let me know whether the result is
satisfactory.  (If you want any help applying the patch or byte
compiling the lisp files, feel free to ask me by private mail.)

> In GNU Emacs 25.3.1 (x86_64-redhat-linux-gnu, GTK+ Version 3.22.19)
>  of 2017-09-15 built on buildvm-31.phx2.fedoraproject.org

Here's the patch:

--- cc-engine.el~	2018-03-11 10:11:17.488821748 +0000
+++ cc-engine.el	2018-03-11 09:59:50.592847587 +0000
@@ -4677,10 +4677,10 @@
 	      (t 'c)))			; Assuming the range is valid.
     range))
 
-(defsubst c-determine-limit-get-base (start try-size)
+(defsubst c-determine-limit-get-base (start try-size &optional abs-limit)
   ;; Get a "safe place" approximately TRY-SIZE characters before START.
-  ;; This doesn't preserve point.
-  (let* ((pos (max (- start try-size) (point-min)))
+  ;; This defsubst doesn't preserve point.
+  (let* ((pos (max (- start try-size) (point-min) (or abs-limit 0)))
 	 (base (c-state-semi-safe-place pos))
 	 (s (save-restriction
 	      (widen)
@@ -4688,18 +4688,18 @@
 	 (cand (if (or (nth 4 s) (nth 3 s)) ; comment or string
 		   (nth 8 s)
 		 pos)))
-    (if (>= cand (point-min))
+    (if (>= cand (max (point-min) (or abs-limit 0)))
 	cand
       (parse-partial-sexp pos start nil nil s 'syntax-table)
       (point))))
 
-(defun c-determine-limit (how-far-back &optional start try-size)
+(defun c-determine-limit (how-far-back &optional abs-limit start try-size)
   ;; Return a buffer position HOW-FAR-BACK non-literal characters from
   ;; START (default point).  The starting position, either point or
   ;; START may not be in a comment or string.
   ;;
-  ;; The position found will not be before POINT-MIN and won't be in a
-  ;; literal.
+  ;; The position found will not be before POINT-MIN, won't be before
+  ;; ABS-LIMIT, and won't be in a literal.
   ;;
   ;; We start searching for the sought position TRY-SIZE (default
   ;; twice HOW-FAR-BACK) bytes back from START.
@@ -4708,7 +4708,7 @@
   (save-excursion
     (let* ((start (or start (point)))
 	   (try-size (or try-size (* 2 how-far-back)))
-	   (base (c-determine-limit-get-base start try-size))
+	   (base (c-determine-limit-get-base start try-size abs-limit))
 	   (pos base)
 
 	   (s (parse-partial-sexp pos pos)) ; null state.
@@ -4760,10 +4760,11 @@
 	(+ (car elt) (- count how-far-back)))
        ((eq base (point-min))
 	(point-min))
-       ((> base (- start try-size)) ; Can only happen if we hit point-min.
-	(car elt))
+       ((> base (- start try-size)) ; Can only happen if we hit
+				    ; point-min or ABS-LIMIT.
+	(or (car elt) base))
        (t
-	(c-determine-limit (- how-far-back count) base try-size))))))
+	(c-determine-limit (- how-far-back count) abs-limit base try-size))))))
 
 (defun c-determine-+ve-limit (how-far &optional start-pos)
   ;; Return a buffer position about HOW-FAR non-literal characters forward
@@ -5621,7 +5622,7 @@
       ;; Locate the earliest < after the barrier before the changed region,
       ;; which isn't already marked as a paren.
       (goto-char  (if beg-lit-limits (car beg-lit-limits) beg))
-      (setq beg-limit (c-determine-limit 512))
+      (setq beg-limit (c-determine-limit 512 (- (point) 1024)))
 
       ;; Remove the syntax-table/category properties from each pertinent <...>
       ;; pair.  Firstly, the ones with the < before beg and > after beg....
--- cc-mode.el~	2018-03-11 10:12:03.419820020 +0000
+++ cc-mode.el	2018-03-11 10:02:01.447842665 +0000
@@ -1195,7 +1195,7 @@
     (goto-char (c-point 'bol new-pos))
     (when lit-limits			; Comment or string.
       (goto-char (car lit-limits)))
-    (setq bod-lim (c-determine-limit 500))
+    (setq bod-lim (c-determine-limit 500 (- (point) 1000)))
 
     (while
 	;; Go to a less nested declaration each time round this loop.

-- 
Alan Mackenzie (Nuremberg, Germany).