unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: npostavs@users.sourceforge.net
To: Thierry Volpiatto <thierry.volpiatto@gmail.com>
Cc: 25122@debbugs.gnu.org, Boruch Baum <boruch_baum@gmx.com>
Subject: bug#25122: 24.5; function describe-variable hangs on large variables
Date: Sun, 12 Mar 2017 12:07:43 -0400	[thread overview]
Message-ID: <87tw6y7a28.fsf@users.sourceforge.net> (raw)
In-Reply-To: <87k27vtxou.fsf@gmail.com> (Thierry Volpiatto's message of "Sat,  11 Mar 2017 20:34:09 +0100")

[-- Attachment #1: Type: text/plain, Size: 2534 bytes --]

Thierry Volpiatto <thierry.volpiatto@gmail.com> writes:

> npostavs@users.sourceforge.net writes:
>
>> I've been attempting an alternate approach which prettyprints the object
>> while scanning it instead of the current way of printing and then
>> reindenting.  Without optimizing, it's about 3 times as fast as the
>> current pp (it's the pp-prin1 in the benchmarks below), though more than
>> 3 times slower than your mapc pp trick.  On the other hand, it also
>> doesn't yet handle function-specific indentation or any compound
>> structure apart from lists, so I'm not sure if it will end up being much
>> faster.
>>
>> (benchmark 1 '(with-temp-buffer (pp-prin1 long-list (current-buffer)) nil)) "Elapsed time: 3.391232s (0.565806s in 11 GCs)"
>> (benchmark 1 '(progn (pp-to-string long-list) nil))                         "Elapsed time: 9.988515s (0.148034s in 3 GCs)"
>> (benchmark 1 '(progn (with-output-to-string (mapc 'pp long-list)) nil))     "Elapsed time: 0.983493s (0.144424s in 3 GCs)"
>> (benchmark 1 '(progn (cl-prin1-to-string long-list) nil))                   "Elapsed time: 0.511617s (0.152483s in 3 GCs)"
>> (benchmark 1 '(progn (prin1-to-string long-list) nil))                      "Elapsed time: 0.029320s"
>
> Interesting, thanks to work on this.

With a couple of minor optimizations it's down to about 1.8s.  The first
is to reuse the tempbuffer instead of letting cl-prin1-to-string make a
new one each time.  Second is to add

    (eval-when-compile
      (cl-proclaim '(optimize (speed 3) (safety 0))))

This also stops these warnings (I guess they're caused by the "safety" code?):

../../emacs-master/lisp/emacs-lisp/pp.el:159:19:Warning: value returned from
    (aref state 6) is unused
../../emacs-master/lisp/emacs-lisp/pp.el:204:24:Warning: value returned from
    (aref state 10) is unused

New times:

(benchmark 1 '(with-temp-buffer (pp-prin1 long-list (current-buffer)) nil)) "Elapsed time: 1.800146s (0.231706s in 6 GCs)"
(benchmark 1 '(progn (pp-to-string long-list) nil))                         "Elapsed time: 9.950225s (0.154100s in 4 GCs)"
(benchmark 1 '(progn (with-output-to-string (mapc 'pp long-list)) nil))     "Elapsed time: 0.980923s (0.149787s in 4 GCs)"

I foolishly neglected to write down what exactly long-list was before,
starting from emacs -Q this seems to approximate it though:

(progn (require 'pp)
       (require 'dabbrev)
       (require 'edebug)
       (require 'cc-mode)
       (require 'vc)
       (setq long-list load-history)
       (length long-list)) ;=> 142


[-- Attachment #2: patch --]
[-- Type: text/plain, Size: 7289 bytes --]

From e4b1a2ef3b4b11466e81d639a09ff671318e0968 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <npostavs@gmail.com>
Date: Sat, 11 Mar 2017 00:09:36 -0500
Subject: [PATCH v2] New pretty printer (Bug#25122)

* lisp/emacs-lisp/pp.el (pp-state): New struct.
(pp--scan): New function, measures length of sublists (actually
"logical blocks" to allow for more customizable grouping than just by
lists).  Calls pp--print when scanned tokens are too wide to fit on a
single line.
(pp--print): New function, prints tokens horizontally or vertically
depending on whether the sublist can fit within the line.
(pp-prin1): New function, entry point for pp--scan and pp-print.
(pp-print-object): New generic function.
(pp-print-object) <cons , _>: New method, prettyprinter for lists.
---
 lisp/emacs-lisp/pp.el | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 156 insertions(+)

diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el
index 7ef46a48bd..8c2ed24ffd 100644
--- a/lisp/emacs-lisp/pp.el
+++ b/lisp/emacs-lisp/pp.el
@@ -24,6 +24,9 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+(require 'ring)
+
 (defvar font-lock-verbose)
 
 (defgroup pp nil
@@ -121,6 +124,159 @@ pp-display-expression
 	(setq buffer-read-only nil)
 	(set (make-local-variable 'font-lock-verbose) nil)))))
 
+(eval-when-compile
+  ;; FIXME: should we try to restore original settings? (how?)
+  (cl-proclaim '(optimize (speed 3) (safety 0))))
+
+(cl-defstruct (pp-state (:constructor
+                         make-pp-state
+                         (stream
+                          tempbuffer
+                          right-margin
+                          &aux
+                          (left-margin 0)
+                          (indent '(0))
+                          (scan-depth 0)
+                          (print-depth 0)
+                          (print-width 0)
+                          (scan-width 0)
+                          (block-mode (list nil))
+                          (fifo (make-ring 30)))))
+  stream
+  tempbuffer
+  right-margin ; how far we may go.
+  left-margin  ; how far printer has gone
+  print-width ; total width of tokens printed so far.
+  indent ; left-margin, stack per depth.
+  scan-width ; total width of tokens scanned so far.
+  scan-depth
+  print-depth
+  block-widths
+  block-mode ; `:vertical', `:horizontal', nil (undecided); stack per depth.
+  fifo
+  )
+
+(defun pp--print (state)
+  (cl-symbol-macrolet ((stream (pp-state-stream state))
+                       (depth (pp-state-print-depth state))
+                       (scan-depth (pp-state-scan-depth state))
+                       (fifo (pp-state-fifo state))
+                       (left-margin (pp-state-left-margin state))
+                       (width (pp-state-print-width state))
+                       (indent (pp-state-indent state))
+                       (right-margin (pp-state-right-margin state))
+                       (block-mode (pp-state-block-mode state)))
+    (catch 'rescan
+      (while (not (ring-empty-p fifo))
+        (pcase (ring-remove fifo)
+          ((and `(,len . :open-block) token)
+           (if (<= len 0)
+               ;; Not ready to print this yet!
+               (progn (ring-insert-at-beginning fifo token)
+                      (throw 'rescan nil))
+             (cl-incf depth)
+             (push left-margin indent)
+             (push (if (> (+ left-margin len) right-margin)
+                       :vertical :horizontal)
+                   block-mode)))
+          (:close-block (cl-decf depth) (pop indent) (pop block-mode))
+          (:blank
+           (pcase (car block-mode)
+            (:vertical
+             (terpri stream)
+             (princ (make-string (car indent) ?\s) stream)
+             (setf left-margin (car indent)))
+            ((or :horizontal 'nil)
+             (write-char ?\s stream)
+             (cl-incf left-margin))
+            (_ (error "oops")))
+           (cl-incf width))
+          (:eof nil)
+          ((and (pred characterp) char)
+           (write-char char stream)
+           (cl-incf left-margin (char-width char))
+           (cl-incf width (char-width char)))
+          (string
+           (princ string stream)
+           (cl-incf left-margin (string-width string))
+           (cl-incf width (string-width string))))))))
+
+(defun pp--scan (token state)
+  (cl-symbol-macrolet ((stream (pp-state-stream state))
+                       (depth (pp-state-scan-depth state))
+                       (print-depth (pp-state-print-depth state))
+                       (fifo (pp-state-fifo state))
+                       (width (pp-state-scan-width state))
+                       (right-margin (pp-state-right-margin state))
+                       (block-widths (pp-state-block-widths state)))
+    (cl-flet ((scanlen (len) (cl-incf width len)))
+      (cl-assert (> (ring-size fifo) (ring-length fifo)))
+      (ring-insert fifo token)
+      (pcase token
+        (:open-block
+         (cl-incf depth)
+         (let ((block-token (cons (- width) (ring-remove fifo 0))))
+           (push block-token block-widths)
+           (ring-insert fifo block-token)))
+        (:close-block
+         (cl-incf (caar block-widths) width)
+         (when (> (caar block-widths) right-margin)
+           (pp--print state))
+         (cl-decf depth)
+         (pop block-widths))
+        (:blank (scanlen 1))
+        (:eof (pp--print state))
+        ((pred characterp) (scanlen (char-width token)))
+        (_ (scanlen (string-width token)))))
+    (when block-widths
+      (when (> (+ (caar block-widths) width) right-margin)
+        (dolist (block-width block-widths)
+          (setf (car block-width) (+ right-margin 1))))
+      (when (> (caar block-widths) right-margin)
+        (pp--print state)))))
+
+(defvar cl-print-readably) ; cl-print.el
+
+(defun pp-prin1 (object &optional stream right-margin)
+  (unless right-margin
+    (setq right-margin fill-column))
+  (with-temp-buffer
+    (let ((cl-print-readably nil)
+          (state (make-pp-state (or stream standard-output) (current-buffer)
+                                right-margin)))
+      (pp--scan :open-block state)
+      (prog1 (pp-print-object object state)
+        (pp--scan :close-block state)
+        (pp--scan :eof state)))))
+
+
+(cl-defgeneric pp-print-object (object state)
+  ;; Fallback to standard `cl-print-object'.
+  (pp--scan (with-current-buffer (pp-state-tempbuffer state)
+              (cl-prin1 object (current-buffer))
+              (prog1 (buffer-string)
+                (erase-buffer)))
+            state)
+  object)
+
+(cl-defmethod pp-print-object ((list cons) state)
+  (pcase list
+    (`(,head . ,tail)
+     (pp--scan "(" state)
+     (pp--scan :open-block state)
+     (pp-print-object head state)
+     (while (consp tail)
+       (pp--scan :blank state)
+       (pp-print-object (pop tail) state))
+     (when tail
+       (pp--scan :blank state)
+       (pp--scan ?\. state)
+       (pp--scan :blank state)
+       (pp-print-object tail state))
+     (pp--scan :close-block state)
+     (pp--scan ")" state)))
+  list)
+
 ;;;###autoload
 (defun pp-eval-expression (expression)
   "Evaluate EXPRESSION and pretty-print its value.
-- 
2.11.1


  reply	other threads:[~2017-03-12 16:07 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-12-06  2:21 bug#25122: 24.5; function describe-variable hangs on large variables Boruch Baum
2016-12-06  6:41 ` Thierry Volpiatto
2016-12-07  3:50   ` npostavs
2016-12-07  8:58     ` Thierry Volpiatto
2017-03-11  5:40       ` npostavs
2017-03-11 15:33         ` Stefan Monnier
2017-03-11 19:29           ` Thierry Volpiatto
2017-03-11 21:59             ` npostavs
2017-03-11 23:55               ` Drew Adams
2017-03-12  5:57               ` Thierry Volpiatto
2017-03-12 14:07                 ` Stefan Monnier
2017-03-12 14:15                 ` npostavs
2017-03-12 14:59                   ` Drew Adams
2017-03-12 16:29                   ` Stefan Monnier
2017-03-12 16:32                   ` npostavs
2017-03-13  4:47                     ` npostavs
2017-03-13 14:01                       ` npostavs
2017-03-16  2:54                         ` npostavs
2017-04-18  3:53                           ` npostavs
2017-04-22 18:25                             ` npostavs
2017-04-26  3:57                               ` Michael Heerdegen
2017-04-26 10:35                                 ` Michael Heerdegen
2017-03-11 19:34         ` Thierry Volpiatto
2017-03-12 16:07           ` npostavs [this message]
2017-03-11 15:21   ` Stefan Monnier
2017-03-11 15:35     ` npostavs
2017-03-11 19:26     ` Thierry Volpiatto

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87tw6y7a28.fsf@users.sourceforge.net \
    --to=npostavs@users.sourceforge.net \
    --cc=25122@debbugs.gnu.org \
    --cc=boruch_baum@gmx.com \
    --cc=thierry.volpiatto@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).