Re: [PATCH v2 1/7] Make keys of notmuch-tag-formats regexps and use caching
authorAustin Clements <amdragon@MIT.EDU>
Tue, 11 Mar 2014 02:06:59 +0000 (22:06 +2000)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 18:00:36 +0000 (10:00 -0800)
8b/ddae8b0f5549f32a6b1921955973f3506d6c3a [new file with mode: 0644]

diff --git a/8b/ddae8b0f5549f32a6b1921955973f3506d6c3a b/8b/ddae8b0f5549f32a6b1921955973f3506d6c3a
new file mode 100644 (file)
index 0000000..f58ba60
--- /dev/null
@@ -0,0 +1,306 @@
+Return-Path: <amdragon@mit.edu>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 83C6A431FBD\r
+       for <notmuch@notmuchmail.org>; Mon, 10 Mar 2014 19:07:08 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.7\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
+       tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id cuG6QWbMUVIK for <notmuch@notmuchmail.org>;\r
+       Mon, 10 Mar 2014 19:07:04 -0700 (PDT)\r
+Received: from dmz-mailsec-scanner-4.mit.edu (dmz-mailsec-scanner-4.mit.edu\r
+       [18.9.25.15])\r
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r
+       (No client certificate requested)\r
+       by olra.theworths.org (Postfix) with ESMTPS id 25D0B431FBC\r
+       for <notmuch@notmuchmail.org>; Mon, 10 Mar 2014 19:07:04 -0700 (PDT)\r
+X-AuditID: 1209190f-f790b6d000000c3a-93-531e6fc74afc\r
+Received: from mailhub-auth-3.mit.edu ( [18.9.21.43])\r
+       (using TLS with cipher AES256-SHA (256/256 bits))\r
+       (Client did not present a certificate)\r
+       by dmz-mailsec-scanner-4.mit.edu (Symantec Messaging Gateway) with SMTP\r
+       id F2.BA.03130.7CF6E135; Mon, 10 Mar 2014 22:07:03 -0400 (EDT)\r
+Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11])\r
+       by mailhub-auth-3.mit.edu (8.13.8/8.9.2) with ESMTP id s2B271aO002954; \r
+       Mon, 10 Mar 2014 22:07:02 -0400\r
+Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
+       (authenticated bits=0)\r
+       (User authenticated as amdragon@ATHENA.MIT.EDU)\r
+       by outgoing.mit.edu (8.13.8/8.12.4) with ESMTP id s2B26xxj008042\r
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT);\r
+       Mon, 10 Mar 2014 22:07:00 -0400\r
+Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.80)\r
+       (envelope-from <amdragon@mit.edu>)\r
+       id 1WNC5r-0008IW-Gs; Mon, 10 Mar 2014 22:06:59 -0400\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: Mark Walters <markwalters1009@gmail.com>, notmuch@notmuchmail.org\r
+Subject: Re: [PATCH v2 1/7] Make keys of notmuch-tag-formats regexps and use\r
+       caching\r
+In-Reply-To: <1392841212-8494-2-git-send-email-markwalters1009@gmail.com>\r
+References: <1392841212-8494-1-git-send-email-markwalters1009@gmail.com>\r
+       <1392841212-8494-2-git-send-email-markwalters1009@gmail.com>\r
+User-Agent: Notmuch/0.17~rc2+14~g06f47e0 (http://notmuchmail.org) Emacs/23.4.1\r
+       (i486-pc-linux-gnu)\r
+Date: Mon, 10 Mar 2014 22:06:59 -0400\r
+Message-ID: <87eh2932v0.fsf@awakening.csail.mit.edu>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=us-ascii\r
+X-Brightmail-Tracker:\r
+ H4sIAAAAAAAAA+NgFnrEIsWRmVeSWpSXmKPExsUixCmqrXs8Xy7YYPt8NovVc3ksrt+cyezA\r
+       5LFz1l12j2erbjEHMEVx2aSk5mSWpRbp2yVwZdx++out4KVXxdFLm9gbGE9bdTFyckgImEgs\r
+       OTKdCcIWk7hwbz1bFyMXh5DAbCaJHyc/skA4Gxklzs08AZU5zSQx5ck3ZghnCaPEzeXP2UH6\r
+       2QQ0JLbtX84IYosIuEo8/faZGcQWFgiReHZ5DVgNp4CnxNmpfYwQza2MEheWXmEBSYgKJEmc\r
+       nvqbFcRmEVCV+HhhLlicF+jAXZP2M0HYghInZz4BizMLaEnc+PeSaQKjwCwkqVlIUgsYmVYx\r
+       yqbkVunmJmbmFKcm6xYnJ+blpRbpmujlZpbopaaUbmIEB6Uk/w7GbweVDjEKcDAq8fAG+MoF\r
+       C7EmlhVX5h5ilORgUhLlXZcJFOJLyk+pzEgszogvKs1JLT7EKMHBrCTCe9oCKMebklhZlVqU\r
+       D5OS5mBREuftOysRLCSQnliSmp2aWpBaBJOV4eBQkuB9kQfUKFiUmp5akZaZU4KQZuLgBBnO\r
+       AzScA6SGt7ggMbc4Mx0if4pRUUqcVx4kIQCSyCjNg+uFJY1XjOJArwjzXgCp4gEmHLjuV0CD\r
+       mYAGNx+XAhlckoiQkmpgjHl77Sdj94TfAcoLVsfXGrb5lQsV/5sb3KEqci3fK27z1PW/lilE\r
+       tGyvUTbwrD9x9r9Vj+qW9/s1XXZMTLubefynw944wydhy9XyDoodKZRl1LBQPcsULfmBVXxO\r
+       iPRil/Tf9Z/7dgv73H47+Z983QTvKR7ROUW/F56Szzn/JvrlozuWnx6dVmIpzkg01GIuKk4E\r
+       ADs20GL1AgAA\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Tue, 11 Mar 2014 02:07:08 -0000\r
+\r
+On Wed, 19 Feb 2014, Mark Walters <markwalters1009@gmail.com> wrote:\r
+> From: Austin Clements <amdragon@MIT.EDU>\r
+>\r
+> This patch switches notmuch-tag-formats to use regexps with caching\r
+> for performance.\r
+>\r
+> We have to clear the cache somehow on changes to notmuch-tag-formats.\r
+> This version takes the simplest approach: search/show/tree all clear\r
+> the cache whenever they start loading.\r
+>\r
+> We cannot use assoc-default since there's no way to distinguish a\r
+> missing key from a present key with a null cdr: thus, we use assoc*\r
+> from cl instead.\r
+>\r
+> Performance-wise, the caching of regexp lookup makes this at least as\r
+> fast as the previous code using assoc (see\r
+> id:1392226351-31440-1-git-send-email-amdragon@mit.edu for timing\r
+> details).\r
+\r
+How about this for a commit message?\r
+\r
+- 8< -\r
+\r
+This modifies `notmuch-tag-format-tag' to treat the keys of\r
+`notmuch-tag-formats' as (anchored) regexps, rather than literal\r
+strings.  This is clearly more flexible, as it allows for prefix\r
+matching, defining a fallback format, etc.  This may cause compatibility\r
+problems if people have customized `notmuch-tag-formats' to match tags\r
+that contain regexp specials, but this seems unlikely.\r
+\r
+Regular expression matching has quite a performance hit over string\r
+lookup, so this also introduces a simple cache from exact tags to\r
+formatted strings.  The number of unique tags is likely to be quite\r
+small, so this cache should have a high hit rate.  In addition to\r
+eliminating the regexp lookup in the common case, this cache stores\r
+fully formatted tags, eliminating the repeated evaluation of potentially\r
+expensive, user-specified formatting code.  This makes regexp lookup at\r
+least as fast as assoc for unformatted tags (e.g., inbox) and *faster*\r
+than the current code for formatted tags (e.g., unread):\r
+\r
+                    inbox (usec)   unread (usec)\r
+    assoc:              0.4            2.8\r
+    regexp:             3.2            7.2\r
+    regexp+caching:     0.4            0.4\r
+\r
+(Though even at 7.2 usec, tag formatting is not our top bottleneck.)\r
+\r
+This cache must be explicitly cleared to keep it coherent, so this adds\r
+the appropriate clearing calls.\r
+\r
+- >8 -\r
+\r
+> ---\r
+>  emacs/notmuch-show.el |    1 +\r
+>  emacs/notmuch-tag.el  |   70 +++++++++++++++++++++++++++++++++---------------\r
+>  emacs/notmuch-tree.el |    1 +\r
+>  emacs/notmuch.el      |    1 +\r
+>  4 files changed, 51 insertions(+), 22 deletions(-)\r
+>\r
+> diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
+> index 1ac80ca..4bddf6c 100644\r
+> --- a/emacs/notmuch-show.el\r
+> +++ b/emacs/notmuch-show.el\r
+> @@ -1145,6 +1145,7 @@ function is used."\r
+>      ;; Don't track undo information for this buffer\r
+>      (set 'buffer-undo-list t)\r
+>  \r
+> +    (notmuch-tag-clear-cache)\r
+>      (erase-buffer)\r
+>      (goto-char (point-min))\r
+>      (save-excursion\r
+> diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el\r
+> index 908e7ad..47e0205 100644\r
+> --- a/emacs/notmuch-tag.el\r
+> +++ b/emacs/notmuch-tag.el\r
+> @@ -28,23 +28,39 @@\r
+>  (require 'crm)\r
+>  (require 'notmuch-lib)\r
+>  \r
+> +;; (notmuch-tag-clear-cache will be called by the defcustom\r
+> +;; notmuch-tag-formats, so it has to be defined first.)\r
+\r
+This is no longer true, so this comment can be removed and the following\r
+two defs can be moved to a more natural location further down in the\r
+file.\r
+\r
+> +\r
+> +(defvar notmuch-tag--format-cache (make-hash-table :test 'equal)\r
+> +  "Cache of tag format lookup.  Internal to `notmuch-tag-format-tag'.")\r
+> +\r
+> +(defun notmuch-tag-clear-cache ()\r
+> +  "Clear the internal cache of tag formats.\r
+> +\r
+> +This must be called after changes to `notmuch-tag-formats'."\r
+\r
+Likewise, this sentence is out of date.  I would just omit it and add\r
+the doc I suggest below.\r
+\r
+> +  (clrhash notmuch-tag--format-cache))\r
+> +\r
+>  (defcustom notmuch-tag-formats\r
+>    '(("unread" (propertize tag 'face '(:foreground "red")))\r
+>      ("flagged" (propertize tag 'face '(:foreground "blue"))\r
+>       (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))))\r
+>    "Custom formats for individual tags.\r
+>  \r
+> -This gives a list that maps from tag names to lists of formatting\r
+> -expressions.  The car of each element gives a tag name and the\r
+> -cdr gives a list of Elisp expressions that modify the tag.  If\r
+> -the list is empty, the tag will simply be hidden.  Otherwise,\r
+> -each expression will be evaluated in order: for the first\r
+> -expression, the variable `tag' will be bound to the tag name; for\r
+> -each later expression, the variable `tag' will be bound to the\r
+> -result of the previous expression.  In this way, each expression\r
+> -can build on the formatting performed by the previous expression.\r
+> -The result of the last expression will displayed in place of the\r
+> -tag.\r
+> +This is an association list that maps from tag name regexps to\r
+> +lists of formatting expressions.  The first entry whose car\r
+> +regexp-matches a tag will be used to format that tag.  The regexp\r
+> +is implicitly anchored, so to match a literal tag name, just use\r
+> +that tag name (if it contains special regexp characters like\r
+> +\".\" or \"*\", these have to be escaped).  The cdr of the\r
+> +matching entry gives a list of Elisp expressions that modify the\r
+> +tag.  If the list is empty, the tag will simply be hidden.\r
+> +Otherwise, each expression will be evaluated in order: for the\r
+> +first expression, the variable `tag' will be bound to the tag\r
+> +name; for each later expression, the variable `tag' will be bound\r
+> +to the result of the previous expression.  In this way, each\r
+> +expression can build on the formatting performed by the previous\r
+> +expression.  The result of the last expression will displayed in\r
+> +place of the tag.\r
+>  \r
+>  For example, to replace a tag with another string, simply use\r
+>  that string as a formatting expression.  To change the foreground\r
+> @@ -56,7 +72,7 @@ with images."\r
+>  \r
+>    :group 'notmuch-search\r
+>    :group 'notmuch-show\r
+> -  :type '(alist :key-type (string :tag "Tag")\r
+> +  :type '(alist :key-type (regexp :tag "Tag")\r
+>              :extra-offset -3\r
+>              :value-type\r
+>              (radio :format "%v"\r
+> @@ -137,16 +153,26 @@ This can be used with `notmuch-tag-format-image-data'."\r
+>  \r
+>  (defun notmuch-tag-format-tag (tag)\r
+>    "Format TAG by looking into `notmuch-tag-formats'."\r
+\r
+This would be a great place to mention that modes need to call\r
+`notmuch-tag-clear-cache' if they intent to use formatted tags.\r
+\r
+  "Format TAG according to `notmuch-tag-formats'.\r
+\r
+Callers must ensure that the tag format cache has been recently cleared\r
+via `notmuch-tag-clear-cache' before using this function.  For example,\r
+it would be appropriate to clear the cache just prior to filling a\r
+buffer that uses formatted tags."\r
+\r
+> -  (let ((formats (assoc tag notmuch-tag-formats)))\r
+> -    (cond\r
+> -     ((null formats)                ;; - Tag not in `notmuch-tag-formats',\r
+> -      tag)                  ;;   the format is the tag itself.\r
+> -     ((null (cdr formats))  ;; - Tag was deliberately hidden,\r
+> -      nil)                  ;;   no format must be returned\r
+> -     (t                             ;; - Tag was found and has formats,\r
+> -      (let ((tag tag))              ;;   we must apply all the formats.\r
+> -    (dolist (format (cdr formats) tag)\r
+> -      (setq tag (eval format))))))))\r
+> +  (let ((formatted (gethash tag notmuch-tag--format-cache 'missing)))\r
+> +    (when (eq formatted 'missing)\r
+> +      (let* ((formats\r
+> +          (save-match-data\r
+\r
+This is a better place for the comment about assoc* that's currently in\r
+the commit message.\r
+\r
+;; Don't use assoc-default since there's no way to distinguish a missing\r
+;; key from a present key with a null cdr:.\r
+\r
+> +            (assoc* tag notmuch-tag-formats\r
+> +                    :test (lambda (tag key)\r
+> +                            (and (eq (string-match key tag) 0)\r
+> +                                 (= (match-end 0) (length tag))))))))\r
+> +    (setq formatted\r
+> +          (cond\r
+> +           ((null formats)          ;; - Tag not in `notmuch-tag-formats',\r
+> +            tag)                    ;;   the format is the tag itself.\r
+> +           ((null (cdr formats))    ;; - Tag was deliberately hidden,\r
+> +            nil)                    ;;   no format must be returned\r
+> +           (t                       ;; - Tag was found and has formats,\r
+> +            (let ((tag tag))        ;;   we must apply all the formats.\r
+> +              (dolist (format (cdr formats) tag)\r
+> +                (setq tag (eval format)))))))\r
+> +    (puthash tag formatted notmuch-tag--format-cache)))\r
+> +    formatted))\r
+>  \r
+>  (defun notmuch-tag-format-tags (tags &optional face)\r
+>    "Return a string representing formatted TAGS."\r
+> diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el\r
+> index 4f2ac02..a106e09 100644\r
+> --- a/emacs/notmuch-tree.el\r
+> +++ b/emacs/notmuch-tree.el\r
+> @@ -881,6 +881,7 @@ the same as for the function notmuch-tree."\r
+>       (message-arg "--entire-thread"))\r
+>      (if (equal (car (process-lines notmuch-command "count" search-args)) "0")\r
+>      (setq search-args basic-query))\r
+> +    (notmuch-tag-clear-cache)\r
+>      (let ((proc (notmuch-start-notmuch\r
+>               "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel\r
+>               "show" "--body=false" "--format=sexp"\r
+> diff --git a/emacs/notmuch.el b/emacs/notmuch.el\r
+> index 0471750..0c767f7 100644\r
+> --- a/emacs/notmuch.el\r
+> +++ b/emacs/notmuch.el\r
+> @@ -888,6 +888,7 @@ the configured default sort order."\r
+>      (set 'notmuch-search-oldest-first oldest-first)\r
+>      (set 'notmuch-search-target-thread target-thread)\r
+>      (set 'notmuch-search-target-line target-line)\r
+> +    (notmuch-tag-clear-cache)\r
+>      (let ((proc (get-buffer-process (current-buffer)))\r
+>        (inhibit-read-only t))\r
+>        (if proc\r
+> -- \r
+> 1.7.9.1\r