m00natic pushed a commit to branch master in repository elpa. commit 9c4e6a53214392f76aac93e20bfc9d6671d5b070 Author: Andrey Kotlarski <m00nati...@gmail.com> Date: Sat Sep 20 19:06:29 2014 +0300
* packages/vlf: Version 1.6. Automatically tune batch size to improve user experience and performance for batch operations. * vlf-tune.el: New profile and tune module. * vlf.el (vlf-next-batch, vlf-prev-batch, vlf-set-batch-size) (vlf-beginning-of-file, vlf-end-of-file, vlf-jump-to-chunk): Auto tune batch size. * vlf-write.el (vlf-write): Profile, tune batch size and time save when adjusting file content. (vlf-file-shift-back, vlf-shift-batch, vlf-file-shift-forward) (vlf-shift-batches): Profile and tune batch size. * vlf-search.el (vlf-re-search): Auto tune batch size and use outside progress reporter if provided. (vlf-goto-match): Time search. (vlf-re-search-forward, vlf-re-search-backward): Minimize search overlap and restore batch size if search failed. (vlf-goto-line): Profile operations, tune batch size and time whole search. * vlf-occur.el (vlf-occur-visit): Profile hexl operations. (vlf-occur-other-buffer): New function. (vlf-occur): Auto tune batch size. (vlf-build-occur): Auto tune batch size and time whole occur. * vlf-base.el (vlf-move-to-chunk-1, vlf-move-to-chunk-2) (vlf-insert-file-contents-1, vlf-delete-region): Profile primitive operations. (vlf-shift-undo-list): Ignore null undo list. --- packages/vlf/vlf-base.el | 38 ++--- packages/vlf/vlf-occur.el | 141 ++++++++++----- packages/vlf/vlf-search.el | 118 ++++++++----- packages/vlf/vlf-tune.el | 423 ++++++++++++++++++++++++++++++++++++++++++++ packages/vlf/vlf-write.el | 97 ++++++---- packages/vlf/vlf.el | 27 +++- 6 files changed, 682 insertions(+), 162 deletions(-) diff --git a/packages/vlf/vlf-base.el b/packages/vlf/vlf-base.el index 30e9b27..f568202 100644 --- a/packages/vlf/vlf-base.el +++ b/packages/vlf/vlf-base.el @@ -27,10 +27,7 @@ ;;; Code: -(defcustom vlf-batch-size 1000000 - "Defines how large each batch of file data initially is (in bytes)." - :group 'vlf :type 'integer) -(put 'vlf-batch-size 'permanent-local t) +(require 'vlf-tune) (defcustom vlf-before-chunk-update nil "Hook that runs before chunk update." @@ -50,10 +47,6 @@ (make-variable-buffer-local 'vlf-end-pos) (put 'vlf-end-pos 'permanent-local t) -(defvar vlf-file-size 0 "Total size of presented file.") -(make-variable-buffer-local 'vlf-file-size) -(put 'vlf-file-size 'permanent-local t) - (defconst vlf-sample-size 24 "Minimal number of bytes that can be properly decoded.") @@ -133,11 +126,10 @@ bytes added to the end." (setq restore-hexl t hexl-undo-list buffer-undo-list buffer-undo-list t) - (hexl-mode-exit)) + (vlf-tune-dehexlify)) (+ vlf-start-pos - (length (encode-coding-region - (point-min) (point-max) - buffer-file-coding-system t)))) + (vlf-tune-encode-length (point-min) + (point-max)))) vlf-end-pos)) (shifts (cond @@ -161,7 +153,7 @@ bytes added to the end." (when (and hexl (not restore-hexl)) (if (consp buffer-undo-list) (setq buffer-undo-list nil)) - (hexl-mode-exit)) + (vlf-tune-dehexlify)) (let ((shift-start 0) (shift-end 0)) (let ((pos (+ (position-bytes (point)) vlf-start-pos)) @@ -224,12 +216,12 @@ bytes added to the end." (set-buffer-modified-p modified) (set-visited-file-modtime) (when hexl - (hexl-mode) + (vlf-tune-hexlify) (setq restore-hexl nil)) (run-hooks 'vlf-after-chunk-update) (cons shift-start shift-end)))))) (when restore-hexl - (hexl-mode) + (vlf-tune-hexlify) (setq buffer-undo-list hexl-undo-list)) shifts)) @@ -252,7 +244,7 @@ bytes added to the end." vlf-end-pos t t) vlf-start-pos (- vlf-start-pos (car shifts)) vlf-end-pos (+ vlf-end-pos (cdr shifts))) - (if hexl (hexl-mode))) + (if hexl (vlf-tune-hexlify))) (goto-char (or (byte-to-position (+ pos (car shifts))) (point-max))))) (set-buffer-modified-p nil) @@ -293,7 +285,7 @@ bytes added to the end." (defun vlf-insert-file-contents-1 (start end) "Extract decoded file bytes START to END." - (insert-file-contents buffer-file-name nil start end)) + (vlf-tune-insert-file-contents start end)) (defun vlf-adjust-start (start end position adjust-end) "Adjust chunk beginning at absolute START to END till content can\ @@ -345,12 +337,10 @@ which deletion was performed." (eq encode-direction 'end) (< (- end border) (- border start)))) (dist (if encode-from-end - (- end (length (encode-coding-region - cut-point (point-max) - buffer-file-coding-system t))) - (+ start (length (encode-coding-region - position cut-point - buffer-file-coding-system t))))) + (- end (vlf-tune-encode-length cut-point + (point-max))) + (+ start (vlf-tune-encode-length position + cut-point)))) (len 0)) (if (< border dist) (while (< border dist) @@ -378,7 +368,7 @@ which deletion was performed." (defun vlf-shift-undo-list (n) "Shift undo list element regions by N." - (or (eq buffer-undo-list t) + (or (null buffer-undo-list) (eq buffer-undo-list t) (setq buffer-undo-list (nreverse (let ((min (point-min)) diff --git a/packages/vlf/vlf-occur.el b/packages/vlf/vlf-occur.el index 2dac4a4..a41f448 100644 --- a/packages/vlf/vlf-occur.el +++ b/packages/vlf/vlf-occur.el @@ -124,7 +124,7 @@ EVENT may hold details of the invocation." pos-relative))) (cond (current-prefix-arg (setq vlf-buffer (vlf file t)) - (or not-hexl (hexl-mode)) + (or not-hexl (vlf-tune-hexlify)) (switch-to-buffer occur-buffer)) ((not (buffer-live-p vlf-buffer)) (unless (catch 'found @@ -137,72 +137,109 @@ EVENT may hold details of the invocation." (setq vlf-buffer buf) (throw 'found t)))) (setq vlf-buffer (vlf file t)) - (or not-hexl (hexl-mode))) + (or not-hexl (vlf-tune-hexlify))) (switch-to-buffer occur-buffer) (setq vlf-occur-vlf-buffer vlf-buffer))) (pop-to-buffer vlf-buffer) (vlf-move-to-chunk chunk-start chunk-end) (goto-char match-pos))))) +(defun vlf-occur-other-buffer (regexp) + "Make whole file occur style index for REGEXP branching to new buffer. +Prematurely ending indexing will still show what's found so far." + (let ((vlf-buffer (current-buffer)) + (file buffer-file-name) + (batch-size vlf-batch-size) + (is-hexl (derived-mode-p 'hexl-mode)) + (insert-bps vlf-tune-insert-bps) + (encode-bps vlf-tune-encode-bps) + (hexl-bps vlf-tune-hexl-bps) + (dehexlify-bps vlf-tune-dehexlify-bps)) + (with-temp-buffer + (setq buffer-file-name file + buffer-file-truename file + buffer-undo-list t) + (set-buffer-modified-p nil) + (set (make-local-variable 'vlf-batch-size) batch-size) + (when vlf-tune-enabled + (setq vlf-tune-insert-bps insert-bps + vlf-tune-encode-bps encode-bps) + (if is-hexl + (progn (setq vlf-tune-hexl-bps hexl-bps + vlf-tune-dehexlify-bps dehexlify-bps) + (vlf-tune-batch '(:hexl :dehexlify :insert :encode))) + (vlf-tune-batch '(:insert :encode)))) + (vlf-mode 1) + (if is-hexl (vlf-tune-hexlify)) + (goto-char (point-min)) + (vlf-with-undo-disabled + (vlf-build-occur regexp vlf-buffer)) + (when vlf-tune-enabled + (setq insert-bps vlf-tune-insert-bps + encode-bps vlf-tune-encode-bps) + (if is-hexl + (setq insert-bps vlf-tune-insert-bps + encode-bps vlf-tune-encode-bps)))) + (when vlf-tune-enabled ;merge back tune measurements + (setq vlf-tune-insert-bps insert-bps + vlf-tune-encode-bps encode-bps) + (if is-hexl + (setq vlf-tune-insert-bps insert-bps + vlf-tune-encode-bps encode-bps))))) + (defun vlf-occur (regexp) "Make whole file occur style index for REGEXP. Prematurely ending indexing will still show what's found so far." (interactive (list (read-regexp "List lines matching regexp" (if regexp-history (car regexp-history))))) - (if (buffer-modified-p) ;use temporary buffer not to interfere with modifications - (let ((vlf-buffer (current-buffer)) - (file buffer-file-name) - (batch-size vlf-batch-size) - (is-hexl (derived-mode-p 'hexl-mode))) - (with-temp-buffer - (setq buffer-file-name file - buffer-file-truename file - buffer-undo-list t) - (set-buffer-modified-p nil) - (set (make-local-variable 'vlf-batch-size) batch-size) - (vlf-mode 1) - (if is-hexl - (hexl-mode)) - (run-hook-with-args 'vlf-before-batch-functions 'occur) - (goto-char (point-min)) - (vlf-with-undo-disabled - (vlf-build-occur regexp vlf-buffer)) - (run-hook-with-args 'vlf-after-batch-functions 'occur))) - (run-hook-with-args 'vlf-before-batch-functions 'occur) + (run-hook-with-args 'vlf-before-batch-functions 'occur) + (if (or (buffer-modified-p) + (< vlf-batch-size vlf-start-pos)) + (vlf-occur-other-buffer regexp) (let ((start-pos vlf-start-pos) (end-pos vlf-end-pos) - (pos (point))) + (pos (point)) + (batch-size vlf-batch-size) + (is-hexl (derived-mode-p 'hexl-mode))) + (vlf-tune-batch (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (vlf-with-undo-disabled - (vlf-beginning-of-file) + (vlf-move-to-batch 0) (goto-char (point-min)) (unwind-protect (vlf-build-occur regexp (current-buffer)) (vlf-move-to-chunk start-pos end-pos) - (goto-char pos)))) - (run-hook-with-args 'vlf-after-batch-functions 'occur))) + (if is-hexl (vlf-tune-hexlify)) + (goto-char pos) + (setq vlf-batch-size batch-size))))) + (run-hook-with-args 'vlf-after-batch-functions 'occur)) (defun vlf-build-occur (regexp vlf-buffer) "Build occur style index for REGEXP over VLF-BUFFER." - (let ((tramp-verbose (if (boundp 'tramp-verbose) - (min tramp-verbose 2))) - (case-fold-search t) - (line 1) - (last-match-line 0) - (last-line-pos (point-min)) - (total-matches 0) - (match-end-pos (+ vlf-start-pos (position-bytes (point)))) - (occur-buffer (generate-new-buffer - (concat "*VLF-occur " (file-name-nondirectory - buffer-file-name) - "*"))) - (line-regexp (concat "\\(?5:[\n\C-m]\\)\\|\\(?10:" - regexp "\\)")) - (batch-step (/ vlf-batch-size 8)) - (is-hexl (derived-mode-p 'hexl-mode)) - (end-of-file nil) - (reporter (make-progress-reporter - (concat "Building index for " regexp "...") - vlf-start-pos vlf-file-size))) + (let* ((tramp-verbose (if (boundp 'tramp-verbose) + (min tramp-verbose 2))) + (case-fold-search t) + (line 1) + (last-match-line 0) + (last-line-pos (point-min)) + (total-matches 0) + (match-end-pos (+ vlf-start-pos (position-bytes (point)))) + (occur-buffer (generate-new-buffer + (concat "*VLF-occur " (file-name-nondirectory + buffer-file-name) + "*"))) + (line-regexp (concat "\\(?5:[\n\C-m]\\)\\|\\(?10:" + regexp "\\)")) + (batch-step (min 1024 (/ vlf-batch-size 8))) + (is-hexl (derived-mode-p 'hexl-mode)) + (end-of-file nil) + (time (float-time)) + (tune-types (if is-hexl '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) + (reporter (make-progress-reporter + (concat "Building index for " regexp "...") + vlf-start-pos vlf-file-size))) (with-current-buffer occur-buffer (setq buffer-undo-list t)) (unwind-protect @@ -255,6 +292,7 @@ Prematurely ending indexing will still show what's found so far." total-matches)))))))) (setq end-of-file (= vlf-end-pos vlf-file-size)) (unless end-of-file + (vlf-tune-batch tune-types) (let ((batch-move (- vlf-end-pos batch-step))) (vlf-move-to-batch (if (or is-hexl (< match-end-pos @@ -274,7 +312,8 @@ Prematurely ending indexing will still show what's found so far." (set-buffer-modified-p nil) (if (zerop total-matches) (progn (kill-buffer occur-buffer) - (message "No matches for \"%s\"" regexp)) + (message "No matches for \"%s\" (%f secs)" + regexp (- (float-time) time))) (let ((file buffer-file-name) (dir default-directory)) (with-current-buffer occur-buffer @@ -292,10 +331,12 @@ in file: %s" total-matches line regexp file) vlf-occur-regexp regexp vlf-occur-hexl is-hexl vlf-occur-lines line))) - (display-buffer occur-buffer))))) - + (display-buffer occur-buffer) + (message "Occur finished for \"%s\" (%f secs)" + regexp (- (float-time) time)))))) -;; save, load vlf-occur data +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; save, load vlf-occur data (defun vlf-occur-save (file) "Serialize `vlf-occur' results to FILE which can later be reloaded." diff --git a/packages/vlf/vlf-search.el b/packages/vlf/vlf-search.el index 3b81d57..33dcc42 100644 --- a/packages/vlf/vlf-search.el +++ b/packages/vlf/vlf-search.el @@ -29,12 +29,22 @@ (require 'vlf) -(defun vlf-re-search (regexp count backward batch-step) +(defun vlf-re-search (regexp count backward batch-step + &optional reporter time) "Search for REGEXP COUNT number of times forward or BACKWARD. -BATCH-STEP is amount of overlap between successive chunks." +BATCH-STEP is amount of overlap between successive chunks. +Use existing REPORTER and start TIME if given. +Return t if search has been at least partially successful." (if (<= count 0) (error "Count must be positive")) (run-hook-with-args 'vlf-before-batch-functions 'search) + (or reporter (setq reporter (make-progress-reporter + (concat "Searching for " regexp "...") + (if backward + (- vlf-file-size vlf-end-pos) + vlf-start-pos) + vlf-file-size))) + (or time (setq time (float-time))) (let* ((tramp-verbose (if (boundp 'tramp-verbose) (min tramp-verbose 2))) (case-fold-search t) @@ -44,13 +54,9 @@ BATCH-STEP is amount of overlap between successive chunks." (match-end-pos match-start-pos) (to-find count) (is-hexl (derived-mode-p 'hexl-mode)) - (font-lock font-lock-mode) - (reporter (make-progress-reporter - (concat "Searching for " regexp "...") - (if backward - (- vlf-file-size vlf-end-pos) - vlf-start-pos) - vlf-file-size))) + (tune-types (if is-hexl '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) + (font-lock font-lock-mode)) (font-lock-mode 0) (vlf-with-undo-disabled (unwind-protect @@ -69,7 +75,8 @@ BATCH-STEP is amount of overlap between successive chunks." (match-end 0))))) ((zerop vlf-start-pos) (throw 'end-of-file nil)) - (t (let ((batch-move (- vlf-start-pos + (t (vlf-tune-batch tune-types) + (let ((batch-move (- vlf-start-pos (- vlf-batch-size batch-step)))) (vlf-move-to-batch @@ -82,9 +89,9 @@ BATCH-STEP is amount of overlap between successive chunks." match-start-pos)) (point-max) (or (byte-to-position - (- match-start-pos - vlf-start-pos)) - (point-max)))) + (- match-start-pos + vlf-start-pos)) + (point-max)))) (progress-reporter-update reporter (- vlf-file-size vlf-start-pos))))) @@ -101,7 +108,8 @@ BATCH-STEP is amount of overlap between successive chunks." (match-end 0))))) ((= vlf-end-pos vlf-file-size) (throw 'end-of-file nil)) - (t (let ((batch-move (- vlf-end-pos batch-step))) + (t (vlf-tune-batch tune-types) + (let ((batch-move (- vlf-end-pos batch-step))) (vlf-move-to-batch (if (or is-hexl (< match-end-pos batch-move)) @@ -111,42 +119,49 @@ BATCH-STEP is amount of overlap between successive chunks." (<= match-end-pos vlf-start-pos)) (point-min) (or (byte-to-position - (- match-end-pos - vlf-start-pos)) - (point-min)))) + (- match-end-pos + vlf-start-pos)) + (point-min)))) (progress-reporter-update reporter vlf-end-pos))))) (progress-reporter-done reporter)) (set-buffer-modified-p nil) + (if is-hexl (vlf-tune-hexlify)) (if font-lock (font-lock-mode 1)) - (if backward - (vlf-goto-match match-chunk-start match-chunk-end - match-end-pos match-start-pos - count to-find) - (vlf-goto-match match-chunk-start match-chunk-end - match-start-pos match-end-pos - count to-find)) - (run-hook-with-args 'vlf-after-batch-functions 'search))))) + (let ((result + (if backward + (vlf-goto-match match-chunk-start match-chunk-end + match-end-pos match-start-pos + count to-find time) + (vlf-goto-match match-chunk-start match-chunk-end + match-start-pos match-end-pos + count to-find time)))) + (run-hook-with-args 'vlf-after-batch-functions 'search) + result))))) (defun vlf-goto-match (match-chunk-start match-chunk-end - match-pos-start - match-pos-end - count to-find) + match-pos-start match-pos-end + count to-find time) "Move to MATCH-CHUNK-START MATCH-CHUNK-END surrounding\ MATCH-POS-START and MATCH-POS-END. According to COUNT and left TO-FIND, show if search has been -successful. Return nil if nothing found." +successful. Use start TIME to report how much it took. +Return nil if nothing found." (if (= count to-find) (progn (vlf-move-to-chunk match-chunk-start match-chunk-end) (goto-char (or (byte-to-position (- match-pos-start vlf-start-pos)) (point-max))) - (message "Not found") + (message "Not found (%f secs)" (- (float-time) time)) nil) (let ((success (zerop to-find))) (if success (vlf-update-buffer-name) (vlf-move-to-chunk match-chunk-start match-chunk-end)) + (setq vlf-batch-size (vlf-tune-optimal-load + (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode)))) (let* ((match-end (or (byte-to-position (- match-pos-end vlf-start-pos)) (point-max))) @@ -155,10 +170,11 @@ successful. Return nil if nothing found." vlf-start-pos)) match-end))) (overlay-put overlay 'face 'match) - (unless success + (if success + (message "Match found (%f secs)" (- (float-time) time)) (goto-char match-end) - (message "Moved to the %d match which is last" - (- count to-find))) + (message "Moved to the %d match which is last (%f secs)" + (- count to-find) (- (float-time) time))) (unwind-protect (sit-for 3) (delete-overlay overlay)) t)))) @@ -171,7 +187,9 @@ Search is performed chunk by chunk in `vlf-batch-size' memory." (if regexp-history (car regexp-history))) (or current-prefix-arg 1)))) - (vlf-re-search regexp count nil (/ vlf-batch-size 8))) + (let ((batch-size vlf-batch-size)) + (or (vlf-re-search regexp count nil (min 1024 (/ vlf-batch-size 8))) + (setq vlf-batch-size batch-size)))) (defun vlf-re-search-backward (regexp count) "Search backward for REGEXP prefix COUNT number of times. @@ -181,7 +199,9 @@ Search is performed chunk by chunk in `vlf-batch-size' memory." (if regexp-history (car regexp-history))) (or current-prefix-arg 1)))) - (vlf-re-search regexp count t (/ vlf-batch-size 8))) + (let ((batch-size vlf-batch-size)) + (or (vlf-re-search regexp count t (min 1024 (/ vlf-batch-size 8))) + (setq vlf-batch-size batch-size)))) (defun vlf-goto-line (n) "Go to line N. If N is negative, count from the end of file." @@ -193,11 +213,14 @@ Search is performed chunk by chunk in `vlf-batch-size' memory." (min tramp-verbose 2))) (start-pos vlf-start-pos) (end-pos vlf-end-pos) + (batch-size vlf-batch-size) (pos (point)) (is-hexl (derived-mode-p 'hexl-mode)) (font-lock font-lock-mode) + (time (float-time)) (success nil)) (font-lock-mode 0) + (vlf-tune-batch '(:raw)) (unwind-protect (if (< 0 n) (let ((start 0) @@ -213,20 +236,24 @@ Search is performed chunk by chunk in `vlf-batch-size' memory." (while (and (< (- end start) n) (< n (- vlf-file-size start))) (erase-buffer) - (insert-file-contents-literally buffer-file-name - nil start end) + (vlf-tune-insert-file-contents-literally start end) (goto-char (point-min)) (while (re-search-forward "[\n\C-m]" nil t) (setq n (1- n))) (vlf-verify-size) + (vlf-tune-batch '(:raw)) (setq start end end (min vlf-file-size (+ start vlf-batch-size))) (progress-reporter-update reporter start))) (when (< n (- vlf-file-size end)) - (vlf-move-to-chunk-2 start end) + (vlf-tune-batch (if is-hexl + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) + (vlf-move-to-chunk-2 start (+ start vlf-batch-size)) (goto-char (point-min)) - (setq success (vlf-re-search "[\n\C-m]" n nil 0))))) + (setq success (vlf-re-search "[\n\C-m]" n nil 0 + reporter time))))) (let ((start (max 0 (- vlf-file-size vlf-batch-size))) (end vlf-file-size) (reporter (make-progress-reporter @@ -239,25 +266,30 @@ Search is performed chunk by chunk in `vlf-batch-size' memory." (or is-hexl (while (and (< (- end start) n) (< n end)) (erase-buffer) - (insert-file-contents-literally buffer-file-name - nil start end) + (vlf-tune-insert-file-contents-literally start end) (goto-char (point-max)) (while (re-search-backward "[\n\C-m]" nil t) (setq n (1- n))) + (vlf-tune-batch '(:raw)) (setq end start start (max 0 (- end vlf-batch-size))) (progress-reporter-update reporter (- vlf-file-size end)))) (when (< n end) - (vlf-move-to-chunk-2 start end) + (vlf-tune-batch (if is-hexl + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) + (vlf-move-to-chunk-2 (- end vlf-batch-size) end) (goto-char (point-max)) - (setq success (vlf-re-search "[\n\C-m]" n t 0)))))) + (setq success (vlf-re-search "[\n\C-m]" n t 0 + reporter time)))))) (if font-lock (font-lock-mode 1)) (unless success (vlf-with-undo-disabled (vlf-move-to-chunk-2 start-pos end-pos)) (vlf-update-buffer-name) (goto-char pos) + (setq vlf-batch-size batch-size) (message "Unable to find line")) (run-hook-with-args 'vlf-after-batch-functions 'goto-line)))) diff --git a/packages/vlf/vlf-tune.el b/packages/vlf/vlf-tune.el new file mode 100644 index 0000000..adf8468 --- /dev/null +++ b/packages/vlf/vlf-tune.el @@ -0,0 +1,423 @@ +;;; vlf-tune.el --- VLF tuning operations -*- lexical-binding: t -*- + +;; Copyright (C) 2014 Free Software Foundation, Inc. + +;; Keywords: large files, batch size, performance +;; Author: Andrey Kotlarski <m00nati...@gmail.com> +;; URL: https://github.com/m00natic/vlfi + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: +;; This package provides wrappers for basic chunk operations that add +;; profiling and automatic tuning of `vlf-batch-size'. + +;;; Code: + +(defgroup vlf nil "View Large Files in Emacs." + :prefix "vlf-" :group 'files) + +(defcustom vlf-batch-size 1000000 + "Defines how large each batch of file data initially is (in bytes)." + :group 'vlf :type 'integer) +(put 'vlf-batch-size 'permanent-local t) + +(defcustom vlf-tune-enabled t + "Whether to allow automatic change of batch size. +If nil, completely disable. If `stats', maintain measure statistics, +but don't change batch size. If t, measure and change." + :group 'vlf :type '(choice (const :tag "Enabled" t) + (const :tag "Just statistics" stats) + (const :tag "Disabled" nil))) + +(defvar vlf-file-size 0 "Total size in bytes of presented file.") +(make-variable-buffer-local 'vlf-file-size) +(put 'vlf-file-size 'permanent-local t) + +(defun vlf-tune-ram-size () + "Try to determine RAM size in bytes." + (if (executable-find "free") + (let* ((free (shell-command-to-string "free")) + (match-from (string-match "[[:digit:]]+" free))) + (if match-from + (* 1000 (string-to-number (substring free match-from + (match-end 0)))))))) + +(defcustom vlf-tune-max (let ((ram-size (vlf-tune-ram-size))) + (if ram-size + (/ ram-size 20) + large-file-warning-threshold)) + "Maximum batch size in bytes when auto tuning." + :group 'vlf :type 'integer) + +(defcustom vlf-tune-step (/ vlf-tune-max 1000) + "Step used for tuning in bytes." + :group 'vlf :type 'integer) + +(defcustom vlf-tune-load-time 1.0 + "How many seconds should batch take to load for best user experience." + :group 'vlf :type 'float) + +(defvar vlf-tune-insert-bps nil + "Vector of bytes per second insert measurements.") +(make-variable-buffer-local 'vlf-tune-insert-bps) +(put 'vlf-tune-insert-bps 'permanent-local t) + +(defvar vlf-tune-insert-raw-bps nil + "Vector of bytes per second non-decode insert measurements.") +(make-variable-buffer-local 'vlf-tune-insert-raw-bps) +(put 'vlf-tune-insert-raw-bps 'permanent-local t) + +(defvar vlf-tune-encode-bps nil + "Vector of bytes per second encode measurements.") +(make-variable-buffer-local 'vlf-tune-encode-bps) +(put 'vlf-tune-encode-bps 'permanent-local t) + +(defvar vlf-tune-write-bps nil + "Vector of bytes per second write measurements.") +(make-variable-buffer-local 'vlf-tune-write-bps) +(put 'vlf-tune-write-bps 'permanent-local t) + +(defvar vlf-tune-hexl-bps nil + "Vector of bytes per second hexlify measurements.") +(make-variable-buffer-local 'vlf-tune-hexl-bps) +(put 'vlf-tune-hexl-bps 'permanent-local t) + +(defvar vlf-tune-dehexlify-bps nil + "Vector of bytes per second dehexlify measurements.") +(make-variable-buffer-local 'vlf-tune-dehexlify-bps) +(put 'vlf-tune-dehexlify-bps 'permanent-local t) + +(defun vlf-tune-closest-index (size) + "Get closest measurement index corresponding to SIZE." + (let ((step (float vlf-tune-step))) + (max 0 (1- (min (round size step) (round vlf-tune-max step)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; profiling + +(defun vlf-tune-initialize-measurement () + "Initialize measurement vector." + (make-local-variable 'vlf-tune-max) + (make-local-variable 'vlf-tune-step) + (make-vector (/ vlf-tune-max vlf-tune-step) nil)) + +(defmacro vlf-tune-add-measurement (vec size time) + "Add at an appropriate position in VEC new SIZE TIME measurement. +VEC is a vector of (mean time . count) elements ordered by size." + `(when (and vlf-tune-enabled (not (zerop ,size))) + (or ,vec (setq ,vec (vlf-tune-initialize-measurement))) + (let* ((idx (vlf-tune-closest-index ,size)) + (existing (aref ,vec idx))) + (aset ,vec idx (if (consp existing) + (let ((count (1+ (cdr existing)))) ;recalculate mean + (cons (/ (+ (* (1- count) (car existing)) + (/ ,size ,time)) + count) + count)) + (cons (/ ,size ,time) 1)))))) + +(defmacro vlf-time (&rest body) + "Get timing consed with result of BODY execution." + `(if vlf-tune-enabled + (let* ((time (float-time)) + (result (progn ,@body))) + (cons (- (float-time) time) result)) + (let ((result (progn ,@body))) + (cons nil result)))) + +(defun vlf-tune-insert-file-contents (start end) + "Extract decoded file bytes START to END and save time it takes." + (let ((result (vlf-time (insert-file-contents buffer-file-name + nil start end)))) + (vlf-tune-add-measurement vlf-tune-insert-bps + (- end start) (car result)) + (cdr result))) + +(defun vlf-tune-insert-file-contents-literally (start end) + "Insert raw file bytes START to END and save time it takes." + (let ((result (vlf-time (insert-file-contents-literally + buffer-file-name nil start end)))) + (vlf-tune-add-measurement vlf-tune-insert-raw-bps + (- end start) (car result)) + (cdr result))) + +(defun vlf-tune-encode-length (start end) + "Get length of encoded region START to END and save time it takes." + (let ((result (vlf-time (length (encode-coding-region + start end + buffer-file-coding-system t))))) + (vlf-tune-add-measurement vlf-tune-encode-bps + (cdr result) (car result)) + (cdr result))) + +(defun vlf-tune-write (start end append visit size) + "Save buffer and save time it takes. +START, END, APPEND, VISIT have same meaning as in `write-region'. +SIZE is number of bytes that are saved." + (let ((time (car (vlf-time (write-region start end buffer-file-name + append visit))))) + (vlf-tune-add-measurement vlf-tune-write-bps size time))) + +(defun vlf-tune-hexlify () + "Activate `hexl-mode' and save time it takes." + (or (derived-mode-p 'hexl-mode) + (let ((time (car (vlf-time (hexl-mode))))) + (vlf-tune-add-measurement vlf-tune-hexl-bps + hexl-max-address time)))) + +(defun vlf-tune-dehexlify () + "Exit `hexl-mode' and save time it takes." + (if (derived-mode-p 'hexl-mode) + (let ((time (car (vlf-time (hexl-mode-exit))))) + (vlf-tune-add-measurement vlf-tune-dehexlify-bps + hexl-max-address time)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; tuning + +(defun vlf-tune-approximate-nearby (vec index) + "VEC has value for INDEX, approximate to closest available." + (let ((val 0) + (left-idx (1- index)) + (right-idx (1+ index)) + (min-idx (max 0 (- index 5))) + (max-idx (min (+ index 6) + (1- (/ (min vlf-tune-max + (/ (1+ vlf-file-size) 2)) + vlf-tune-step))))) + (while (and (zerop val) (or (<= min-idx left-idx) + (< right-idx max-idx))) + (if (<= min-idx left-idx) + (let ((left (aref vec left-idx))) + (cond ((consp left) (setq val (car left))) + ((numberp left) (setq val left))))) + (if (< right-idx max-idx) + (let ((right (aref vec right-idx))) + (if (consp right) + (setq right (car right))) + (and (numberp right) (not (zerop right)) + (setq val (if (zerop val) + right + (/ (+ val right) 2)))))) + (setq left-idx (1- left-idx) + right-idx (1+ right-idx))) + val)) + +(defmacro vlf-tune-get-value (vec index &optional dont-approximate) + "Get value from VEC for INDEX. +If missing, approximate from nearby measurement, +unless DONT-APPROXIMATE is t." + `(if ,vec + (let ((val (aref ,vec ,index))) + (cond ((consp val) (car val)) + ((null val) + ,(if dont-approximate + `(aset ,vec ,index 0) + `(vlf-tune-approximate-nearby ,vec ,index))) + ((zerop val) ;index has been tried before, yet still no value + ,(if dont-approximate + `(aset ,vec ,index + (vlf-tune-approximate-nearby ,vec ,index)) + `(vlf-tune-approximate-nearby ,vec ,index))) + (t val))))) + +(defmacro vlf-tune-get-vector (key) + "Get vlf-tune vector corresponding to KEY." + `(cond ((eq ,key :insert) vlf-tune-insert-bps) + ((eq ,key :raw) vlf-tune-insert-raw-bps) + ((eq ,key :encode) vlf-tune-encode-bps) + ((eq ,key :write) vlf-tune-write-bps) + ((eq ,key :hexl) vlf-tune-hexl-bps) + ((eq ,key :dehexlify) vlf-tune-dehexlify-bps))) + +(defun vlf-tune-assess (type coef index &optional approximate) + "Get measurement value according to TYPE, COEF and INDEX. +If APPROXIMATE is t, do approximation for missing values." + (* coef (or (if approximate + (vlf-tune-get-value (vlf-tune-get-vector type) + index) + (vlf-tune-get-value (vlf-tune-get-vector type) + index t)) + 0))) + +(defun vlf-tune-score (types index &optional approximate time-max) + "Calculate cumulative speed over TYPES for INDEX. +If APPROXIMATE is t, do approximation for missing values. +If TIME-MAX is non nil, return cumulative time instead of speed. +If it is number, stop as soon as cumulative time gets equal or above." + (catch 'result + (let ((time 0) + (size (* (1+ index) vlf-tune-step)) + (cut-time (numberp time-max))) + (dolist (el types (if time-max time + (/ size time))) + (let ((bps (if (consp el) + (vlf-tune-assess (car el) (cadr el) index + approximate) + (vlf-tune-assess el 1 index approximate)))) + (if (zerop bps) + (throw 'result nil) + (setq time (+ time (/ size bps))) + (and cut-time (<= time-max time) + (throw 'result nil)))))))) + +(defun vlf-tune-conservative (types &optional index) + "Adjust `vlf-batch-size' to best nearby value over TYPES. +INDEX if given, specifies search independent of current batch size." + (if (eq vlf-tune-enabled t) + (let* ((half-max (/ (1+ vlf-file-size) 2)) + (idx (or index (vlf-tune-closest-index vlf-batch-size))) + (curr (if (< half-max (* idx vlf-tune-step)) t + (vlf-tune-score types idx)))) + (if curr + (let ((prev (if (zerop idx) t + (vlf-tune-score types (1- idx))))) + (if prev + (let ((next (if (or (eq curr t) + (< half-max (* (1+ idx) + vlf-tune-step))) + t + (vlf-tune-score types (1+ idx))))) + (cond ((null next) + (setq vlf-batch-size (* (+ 2 idx) + vlf-tune-step))) + ((eq curr t) + (or (eq prev t) + (setq vlf-batch-size + (* idx vlf-tune-step)))) + (t (let ((best-idx idx)) + (and (numberp prev) (< curr prev) + (setq curr prev + best-idx (1- idx))) + (and (numberp next) (< curr next) + (setq best-idx (1+ idx))) + (setq vlf-batch-size + (* (1+ best-idx) + vlf-tune-step)))))) + (setq vlf-batch-size (* idx vlf-tune-step)))) + (setq vlf-batch-size (* (1+ idx) vlf-tune-step)))))) + +(defun vlf-tune-binary (types min max) + "Adjust `vlf-batch-size' to optimal value using binary search, \ +optimizing over TYPES. +MIN and MAX specify interval of indexes to search." + (let ((sum (+ min max))) + (if (< (- max min) 3) + (vlf-tune-conservative types (/ sum 2)) + (let* ((left-idx (round (+ sum (* 2 min)) 4)) + (left (vlf-tune-score types left-idx))) + (if left + (let* ((right-idx (round (+ sum (* 2 max)) 4)) + (right (vlf-tune-score types right-idx))) + (cond ((null right) + (setq vlf-batch-size (* (1+ right-idx) + vlf-tune-step))) + ((< left right) + (vlf-tune-binary types (/ (1+ sum) 2) max)) + (t (vlf-tune-binary types min (/ sum 2))))) + (setq vlf-batch-size (* (1+ left-idx) vlf-tune-step))))))) + +(defun vlf-tune-linear (types max-idx) + "Adjust `vlf-batch-size' to optimal value using linear search, \ +optimizing over TYPES up to MAX-IDX." + (let ((best-idx 0) + (best-bps 0) + (idx 0) + (none-missing t)) + (while (and none-missing (< idx max-idx)) + (let ((bps (vlf-tune-score types idx))) + (cond ((null bps) + (setq vlf-batch-size (* (1+ idx) vlf-tune-step) + none-missing nil)) + ((< best-bps bps) (setq best-idx idx + best-bps bps)))) + (setq idx (1+ idx))) + (or (not none-missing) + (setq vlf-batch-size (* (1+ best-idx) vlf-tune-step))))) + +(defun vlf-tune-batch (types &optional linear) + "Adjust `vlf-batch-size' to optimal value optimizing on TYPES. +TYPES is alist of elements that may be of form (type coef) or +non list values in which case coeficient is assumed 1. +Types can be :insert, :raw, :encode, :write, :hexl or :dehexlify. +If LINEAR is non nil, use brute-force. In case requested measurement +is missing, stop search and set `vlf-batch-size' to this value. +Suitable for multiple batch operations." + (if (eq vlf-tune-enabled t) + (let ((max-idx (1- (/ (min vlf-tune-max + (/ (1+ vlf-file-size) 2)) + vlf-tune-step)))) + (cond (linear (vlf-tune-linear types max-idx)) + ((file-remote-p buffer-file-name) + (vlf-tune-conservative types)) + ((<= 1 max-idx) + (if (< max-idx 3) + (vlf-tune-conservative types (/ max-idx 2)) + (vlf-tune-binary types 0 max-idx))))))) + +(defun vlf-tune-optimal-load (types &optional min-idx max-idx) + "Get best batch size according to existing measurements over TYPES. +Best considered where primitive operations total is closest to +`vlf-tune-load-time'. If MIN-IDX and MAX-IDX are given, +confine search to this region." + (if vlf-tune-enabled + (progn + (setq max-idx (min (or max-idx vlf-tune-max) + (1- (/ (min vlf-tune-max + (/ (1+ vlf-file-size) 2)) + vlf-tune-step)))) + (let* ((idx (max 0 (or min-idx 0))) + (best-idx idx) + (best-time-diff vlf-tune-load-time) + (all-less t) + (all-more t)) + (while (and (not (zerop best-time-diff)) (< idx max-idx)) + (let ((time-diff (vlf-tune-score types idx t + (+ vlf-tune-load-time + best-time-diff)))) + (if time-diff + (progn + (setq time-diff (if (< vlf-tune-load-time time-diff) + (progn (setq all-less nil) + (- time-diff + vlf-tune-load-time)) + (setq all-more nil) + (- vlf-tune-load-time time-diff))) + (if (< time-diff best-time-diff) + (setq best-idx idx + best-time-diff time-diff))) + (setq all-less nil))) + (setq idx (1+ idx))) + (* vlf-tune-step (1+ (cond ((or (zerop best-time-diff) + (eq all-less all-more)) + best-idx) + (all-less max-idx) + (t min-idx)))))) + vlf-batch-size)) + +(defun vlf-tune-load (types &optional region) + "Adjust `vlf-batch-size' slightly to better load time. +Optimize on TYPES on the nearby REGION. Use 2 if REGION is nil." + (when (eq vlf-tune-enabled t) + (or region (setq region 2)) + (let ((idx (vlf-tune-closest-index vlf-batch-size))) + (setq vlf-batch-size (vlf-tune-optimal-load types (- idx region) + (+ idx 1 region)))))) + +(provide 'vlf-tune) + +;;; vlf-tune.el ends here diff --git a/packages/vlf/vlf-write.el b/packages/vlf/vlf-write.el index 5c94113..a29e1a9 100644 --- a/packages/vlf/vlf-write.el +++ b/packages/vlf/vlf-write.el @@ -43,43 +43,50 @@ If changing size of chunk, shift remaining file content." (when hexl (if (consp buffer-undo-list) (setq buffer-undo-list nil)) - (hexl-mode-exit)) + (vlf-tune-dehexlify)) (if (zerop vlf-file-size) ;new file - (progn (write-region nil nil buffer-file-name vlf-start-pos t) + (progn (vlf-tune-write nil nil vlf-start-pos t + (vlf-tune-encode-length (point-min) + (point-max))) (setq vlf-file-size (vlf-get-file-size buffer-file-truename) vlf-end-pos vlf-file-size) (vlf-update-buffer-name)) - (let* ((region-length (length (encode-coding-region - (point-min) (point-max) - buffer-file-coding-system t))) + (let* ((region-length (vlf-tune-encode-length (point-min) + (point-max))) (size-change (- vlf-end-pos vlf-start-pos region-length))) (if (zerop size-change) - (write-region nil nil buffer-file-name vlf-start-pos t) + (vlf-tune-write nil nil vlf-start-pos t + (- vlf-end-pos vlf-start-pos)) (let ((tramp-verbose (if (boundp 'tramp-verbose) (min tramp-verbose 2))) (pos (point)) (font-lock font-lock-mode)) (font-lock-mode 0) - (if (< 0 size-change) - (vlf-file-shift-back size-change) - (vlf-file-shift-forward (- size-change))) - (if font-lock (font-lock-mode 1)) - (vlf-move-to-chunk-2 vlf-start-pos - (if (< (- vlf-end-pos vlf-start-pos) - vlf-batch-size) - (+ vlf-start-pos vlf-batch-size) - vlf-end-pos)) - (vlf-update-buffer-name) - (goto-char pos))))) - (if hexl (hexl-mode))) + (let ((batch-size vlf-batch-size) + (time (float-time))) + (if (< 0 size-change) + (vlf-file-shift-back size-change region-length) + (vlf-file-shift-forward (- size-change) region-length)) + (if font-lock (font-lock-mode 1)) + (setq vlf-batch-size batch-size) + (vlf-move-to-chunk-2 vlf-start-pos + (if (< (- vlf-end-pos vlf-start-pos) + vlf-batch-size) + (+ vlf-start-pos vlf-batch-size) + vlf-end-pos)) + (vlf-update-buffer-name) + (goto-char pos) + (message "Save took %f seconds" (- (float-time) time))))))) + (if hexl (vlf-tune-hexlify))) (run-hook-with-args 'vlf-after-batch-functions 'write)) t) -(defun vlf-file-shift-back (size-change) - "Shift file contents SIZE-CHANGE bytes back." - (write-region nil nil buffer-file-name vlf-start-pos t) +(defun vlf-file-shift-back (size-change write-size) + "Shift file contents SIZE-CHANGE bytes back. +WRITE-SIZE is byte length of saved chunk." + (vlf-tune-write nil nil vlf-start-pos t write-size) (let ((read-start-pos vlf-end-pos) (coding-system-for-write 'no-conversion) (reporter (make-progress-reporter "Adjusting file content..." @@ -94,8 +101,8 @@ If changing size of chunk, shift remaining file content." (erase-buffer) (vlf-verify-size t) (insert-char 32 size-change)) - (write-region nil nil buffer-file-name (- vlf-file-size - size-change) t) + (vlf-tune-write nil nil (- vlf-file-size size-change) + t size-change) (progress-reporter-done reporter))) (defun vlf-shift-batch (read-pos write-pos) @@ -103,37 +110,48 @@ If changing size of chunk, shift remaining file content." back at WRITE-POS. Return nil if EOF is reached, t otherwise." (erase-buffer) (vlf-verify-size t) - (let ((read-end (+ read-pos vlf-batch-size))) - (insert-file-contents-literally buffer-file-name nil - read-pos - (min vlf-file-size read-end)) - (write-region nil nil buffer-file-name write-pos 0) + (vlf-tune-batch '(:raw :write)) + (let ((read-end (min (+ read-pos vlf-batch-size) vlf-file-size))) + (vlf-tune-insert-file-contents-literally read-pos read-end) + (vlf-tune-write nil nil write-pos 0 (- read-end read-pos)) (< read-end vlf-file-size))) -(defun vlf-file-shift-forward (size-change) +(defun vlf-file-shift-forward (size-change write-size) "Shift file contents SIZE-CHANGE bytes forward. +WRITE-SIZE is byte length of saved chunk. Done by saving content up front and then writing previous batch." - (let ((read-size (max (/ vlf-batch-size 2) size-change)) + (vlf-tune-batch '(:raw :write)) + (let ((read-size (max vlf-batch-size size-change)) (read-pos vlf-end-pos) (write-pos vlf-start-pos) (reporter (make-progress-reporter "Adjusting file content..." vlf-start-pos vlf-file-size))) (vlf-with-undo-disabled - (when (vlf-shift-batches read-size read-pos write-pos t) + (when (vlf-shift-batches read-size read-pos write-pos + write-size t) + (vlf-tune-batch '(:raw :write)) (setq write-pos (+ read-pos size-change) - read-pos (+ read-pos read-size)) + read-pos (+ read-pos read-size) + write-size read-size + read-size (max vlf-batch-size size-change)) (progress-reporter-update reporter write-pos) (let ((coding-system-for-write 'no-conversion)) - (while (vlf-shift-batches read-size read-pos write-pos nil) + (while (vlf-shift-batches read-size read-pos write-pos + write-size nil) + (vlf-tune-batch '(:raw :write)) (setq write-pos (+ read-pos size-change) - read-pos (+ read-pos read-size)) + read-pos (+ read-pos read-size) + write-size read-size + read-size (max vlf-batch-size size-change)) (progress-reporter-update reporter write-pos))))) (progress-reporter-done reporter))) -(defun vlf-shift-batches (read-size read-pos write-pos hide-read) +(defun vlf-shift-batches (read-size read-pos write-pos write-size + hide-read) "Append READ-SIZE bytes of file starting at READ-POS. Then write initial buffer content to file at WRITE-POS. +WRITE-SIZE is byte length of saved chunk. If HIDE-READ is non nil, temporarily hide literal read content. Return nil if EOF is reached, t otherwise." (vlf-verify-size t) @@ -142,14 +160,13 @@ Return nil if EOF is reached, t otherwise." (end-write-pos (point-max))) (when read-more (goto-char end-write-pos) - (insert-file-contents-literally buffer-file-name nil read-pos - (min vlf-file-size - (+ read-pos read-size)))) + (vlf-tune-insert-file-contents-literally + read-pos (min vlf-file-size (+ read-pos read-size)))) ;; write (if hide-read ; hide literal region if user has to choose encoding (narrow-to-region start-write-pos end-write-pos)) - (write-region start-write-pos end-write-pos - buffer-file-name write-pos 0) + (vlf-tune-write start-write-pos end-write-pos write-pos + (or (not read-more) 0) write-size) (delete-region start-write-pos end-write-pos) (if hide-read (widen)) read-more)) diff --git a/packages/vlf/vlf.el b/packages/vlf/vlf.el index 8eb2a4c..45a6d47 100644 --- a/packages/vlf/vlf.el +++ b/packages/vlf/vlf.el @@ -39,8 +39,7 @@ ;;; Code: -(defgroup vlf nil "View Large Files in Emacs." - :prefix "vlf-" :group 'files) +(require 'vlf-base) (defcustom vlf-before-batch-functions nil "Hook that runs before multiple batch operations. @@ -54,8 +53,6 @@ One argument is supplied that specifies current action. Possible values are: `write', `ediff', `occur', `search', `goto-line'." :group 'vlf :type 'hook) -(require 'vlf-base) - (autoload 'vlf-write "vlf-write" "Write current chunk to file." t) (autoload 'vlf-re-search-forward "vlf-search" "Search forward for REGEXP prefix COUNT number of times." t) @@ -172,6 +169,9 @@ When prefix argument is negative append next APPEND number of batches to the existing buffer." (interactive "p") (vlf-verify-size) + (vlf-tune-load (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (let* ((end (min (+ vlf-end-pos (* vlf-batch-size (abs append))) vlf-file-size)) (start (if (< append 0) @@ -188,6 +188,9 @@ When prefix argument is negative (interactive "p") (if (zerop vlf-start-pos) (error "Already at BOF")) + (vlf-tune-load (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (let* ((start (max 0 (- vlf-start-pos (* vlf-batch-size (abs prepend))))) (end (if (< prepend 0) vlf-end-pos @@ -253,19 +256,30 @@ with the prefix argument DECREASE it is halved." (defun vlf-set-batch-size (size) "Set batch to SIZE bytes and update chunk." - (interactive (list (read-number "Size in bytes: " vlf-batch-size))) + (interactive + (list (read-number "Size in bytes: " + (vlf-tune-optimal-load + (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode)))))) (setq vlf-batch-size size) (vlf-move-to-batch vlf-start-pos)) (defun vlf-beginning-of-file () "Jump to beginning of file content." (interactive) + (vlf-tune-load (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (vlf-move-to-batch 0)) (defun vlf-end-of-file () "Jump to end of file content." (interactive) (vlf-verify-size) + (vlf-tune-load (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (vlf-move-to-batch vlf-file-size)) (defun vlf-revert (&optional _auto noconfirm) @@ -281,6 +295,9 @@ Ask for confirmation if NOCONFIRM is nil." (defun vlf-jump-to-chunk (n) "Go to to chunk N." (interactive "nGoto to chunk: ") + (vlf-tune-load (if (derived-mode-p 'hexl-mode) + '(:hexl :dehexlify :insert :encode) + '(:insert :encode))) (vlf-move-to-batch (* (1- n) vlf-batch-size))) (defun vlf-no-modifications ()