diff options
| author | Tassilo Horn | 2019-02-23 21:18:36 +0100 |
|---|---|---|
| committer | Tassilo Horn | 2019-02-23 21:31:15 +0100 |
| commit | e96923c188a2a38d09917c5b7f606187a1413a96 (patch) | |
| tree | 6ad7b9a1549bf520747db72e36eb62ceb6fcc720 /src | |
| parent | 5f640bfdf84753322763be23ebaa8ded92dc1c5d (diff) | |
| download | emacs-e96923c188a2a38d09917c5b7f606187a1413a96.tar.gz emacs-e96923c188a2a38d09917c5b7f606187a1413a96.zip | |
Improve replace-buffer-contents/replace-region-contents
* src/editfns.c (Freplace_buffer_contents): Add two optional arguments
for mitigating performance issues.
* lisp/emacs-lisp/subr-x.el (replace-region-contents): Move from
subr.el. Add the same two arguments as for replace-buffer-contents.
* lisp/json.el (json-pretty-print-max-secs): New variable holding the
default MAX-SECS value json-pretty-print passes to
replace-buffer-contents.
(json-pretty-print): Use it.
* doc/lispref/text.texi (Replacing): Add documentation for
replace-buffer-contents two new optional arguments. Document
replace-region-contents.
Diffstat (limited to 'src')
| -rw-r--r-- | src/editfns.c | 95 |
1 files changed, 75 insertions, 20 deletions
diff --git a/src/editfns.c b/src/editfns.c index 7a600bacf18..8f21f8a677e 100644 --- a/src/editfns.c +++ b/src/editfns.c | |||
| @@ -20,6 +20,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 20 | 20 | ||
| 21 | #include <config.h> | 21 | #include <config.h> |
| 22 | #include <sys/types.h> | 22 | #include <sys/types.h> |
| 23 | #include <sys/time.h> | ||
| 23 | #include <stdio.h> | 24 | #include <stdio.h> |
| 24 | 25 | ||
| 25 | #ifdef HAVE_PWD_H | 26 | #ifdef HAVE_PWD_H |
| @@ -1912,10 +1913,6 @@ determines whether case is significant or ignored. */) | |||
| 1912 | #undef EQUAL | 1913 | #undef EQUAL |
| 1913 | #define USE_HEURISTIC | 1914 | #define USE_HEURISTIC |
| 1914 | 1915 | ||
| 1915 | #ifdef USE_HEURISTIC | ||
| 1916 | #define DIFFSEQ_HEURISTIC | ||
| 1917 | #endif | ||
| 1918 | |||
| 1919 | /* Counter used to rarely_quit in replace-buffer-contents. */ | 1916 | /* Counter used to rarely_quit in replace-buffer-contents. */ |
| 1920 | static unsigned short rbc_quitcounter; | 1917 | static unsigned short rbc_quitcounter; |
| 1921 | 1918 | ||
| @@ -1937,30 +1934,54 @@ static unsigned short rbc_quitcounter; | |||
| 1937 | /* Bit vectors recording for each character whether it was deleted | 1934 | /* Bit vectors recording for each character whether it was deleted |
| 1938 | or inserted. */ \ | 1935 | or inserted. */ \ |
| 1939 | unsigned char *deletions; \ | 1936 | unsigned char *deletions; \ |
| 1940 | unsigned char *insertions; | 1937 | unsigned char *insertions; \ |
| 1938 | struct timeval start; \ | ||
| 1939 | double max_secs; \ | ||
| 1940 | unsigned int early_abort_tests; | ||
| 1941 | 1941 | ||
| 1942 | #define NOTE_DELETE(ctx, xoff) set_bit ((ctx)->deletions, (xoff)) | 1942 | #define NOTE_DELETE(ctx, xoff) set_bit ((ctx)->deletions, (xoff)) |
| 1943 | #define NOTE_INSERT(ctx, yoff) set_bit ((ctx)->insertions, (yoff)) | 1943 | #define NOTE_INSERT(ctx, yoff) set_bit ((ctx)->insertions, (yoff)) |
| 1944 | #define EARLY_ABORT(ctx) compareseq_early_abort (ctx) | ||
| 1944 | 1945 | ||
| 1945 | struct context; | 1946 | struct context; |
| 1946 | static void set_bit (unsigned char *, OFFSET); | 1947 | static void set_bit (unsigned char *, OFFSET); |
| 1947 | static bool bit_is_set (const unsigned char *, OFFSET); | 1948 | static bool bit_is_set (const unsigned char *, OFFSET); |
| 1948 | static bool buffer_chars_equal (struct context *, OFFSET, OFFSET); | 1949 | static bool buffer_chars_equal (struct context *, OFFSET, OFFSET); |
| 1950 | static bool compareseq_early_abort (struct context *); | ||
| 1949 | 1951 | ||
| 1950 | #include "minmax.h" | 1952 | #include "minmax.h" |
| 1951 | #include "diffseq.h" | 1953 | #include "diffseq.h" |
| 1952 | 1954 | ||
| 1953 | DEFUN ("replace-buffer-contents", Freplace_buffer_contents, | 1955 | DEFUN ("replace-buffer-contents", Freplace_buffer_contents, |
| 1954 | Sreplace_buffer_contents, 1, 1, "bSource buffer: ", | 1956 | Sreplace_buffer_contents, 1, 3, "bSource buffer: ", |
| 1955 | doc: /* Replace accessible portion of current buffer with that of SOURCE. | 1957 | doc: /* Replace accessible portion of current buffer with that of SOURCE. |
| 1956 | SOURCE can be a buffer or a string that names a buffer. | 1958 | SOURCE can be a buffer or a string that names a buffer. |
| 1957 | Interactively, prompt for SOURCE. | 1959 | Interactively, prompt for SOURCE. |
| 1960 | |||
| 1958 | As far as possible the replacement is non-destructive, i.e. existing | 1961 | As far as possible the replacement is non-destructive, i.e. existing |
| 1959 | buffer contents, markers, properties, and overlays in the current | 1962 | buffer contents, markers, properties, and overlays in the current |
| 1960 | buffer stay intact. | 1963 | buffer stay intact. |
| 1961 | Warning: this function can be slow if there's a large number of small | 1964 | |
| 1962 | differences between the two buffers. */) | 1965 | Because this function can be very slow if there is a large number of |
| 1963 | (Lisp_Object source) | 1966 | differences between the two buffers, there are two optional arguments |
| 1967 | mitigating this issue. | ||
| 1968 | |||
| 1969 | The MAX-SECS argument, if given, defines a hard limit on the time used | ||
| 1970 | for comparing the buffers. If it takes longer than MAX-SECS, the | ||
| 1971 | function falls back to a plain `delete-region' and | ||
| 1972 | `insert-buffer-substring'. (Note that the checks are not performed | ||
| 1973 | too evenly over time, so in some cases it may run a bit longer than | ||
| 1974 | allowed). | ||
| 1975 | |||
| 1976 | The optional argument MAX-COSTS defines the quality of the difference | ||
| 1977 | computation. If the actual costs exceed this limit, heuristics are | ||
| 1978 | used to provide a faster but suboptimal solution. The default value | ||
| 1979 | is 1000000. | ||
| 1980 | |||
| 1981 | This function returns t if a non-destructive replacement could be | ||
| 1982 | performed. Otherwise, i.e., if MAX-SECS was exceeded, it returns | ||
| 1983 | nil. */) | ||
| 1984 | (Lisp_Object source, Lisp_Object max_secs, Lisp_Object max_costs) | ||
| 1964 | { | 1985 | { |
| 1965 | struct buffer *a = current_buffer; | 1986 | struct buffer *a = current_buffer; |
| 1966 | Lisp_Object source_buffer = Fget_buffer (source); | 1987 | Lisp_Object source_buffer = Fget_buffer (source); |
| @@ -1985,15 +2006,18 @@ differences between the two buffers. */) | |||
| 1985 | empty. */ | 2006 | empty. */ |
| 1986 | 2007 | ||
| 1987 | if (a_empty && b_empty) | 2008 | if (a_empty && b_empty) |
| 1988 | return Qnil; | 2009 | return Qt; |
| 1989 | 2010 | ||
| 1990 | if (a_empty) | 2011 | if (a_empty) |
| 1991 | return Finsert_buffer_substring (source, Qnil, Qnil); | 2012 | { |
| 2013 | Finsert_buffer_substring (source, Qnil, Qnil); | ||
| 2014 | return Qt; | ||
| 2015 | } | ||
| 1992 | 2016 | ||
| 1993 | if (b_empty) | 2017 | if (b_empty) |
| 1994 | { | 2018 | { |
| 1995 | del_range_both (BEGV, BEGV_BYTE, ZV, ZV_BYTE, true); | 2019 | del_range_both (BEGV, BEGV_BYTE, ZV, ZV_BYTE, true); |
| 1996 | return Qnil; | 2020 | return Qt; |
| 1997 | } | 2021 | } |
| 1998 | 2022 | ||
| 1999 | ptrdiff_t count = SPECPDL_INDEX (); | 2023 | ptrdiff_t count = SPECPDL_INDEX (); |
| @@ -2007,6 +2031,12 @@ differences between the two buffers. */) | |||
| 2007 | ptrdiff_t *buffer; | 2031 | ptrdiff_t *buffer; |
| 2008 | USE_SAFE_ALLOCA; | 2032 | USE_SAFE_ALLOCA; |
| 2009 | SAFE_NALLOCA (buffer, 2, diags); | 2033 | SAFE_NALLOCA (buffer, 2, diags); |
| 2034 | |||
| 2035 | if (NILP (max_costs)) | ||
| 2036 | XSETFASTINT (max_costs, 1000000); | ||
| 2037 | else | ||
| 2038 | CHECK_FIXNUM (max_costs); | ||
| 2039 | |||
| 2010 | /* Micro-optimization: Casting to size_t generates much better | 2040 | /* Micro-optimization: Casting to size_t generates much better |
| 2011 | code. */ | 2041 | code. */ |
| 2012 | ptrdiff_t del_bytes = (size_t) size_a / CHAR_BIT + 1; | 2042 | ptrdiff_t del_bytes = (size_t) size_a / CHAR_BIT + 1; |
| @@ -2022,20 +2052,26 @@ differences between the two buffers. */) | |||
| 2022 | .insertions = SAFE_ALLOCA (ins_bytes), | 2052 | .insertions = SAFE_ALLOCA (ins_bytes), |
| 2023 | .fdiag = buffer + size_b + 1, | 2053 | .fdiag = buffer + size_b + 1, |
| 2024 | .bdiag = buffer + diags + size_b + 1, | 2054 | .bdiag = buffer + diags + size_b + 1, |
| 2025 | #ifdef DIFFSEQ_HEURISTIC | ||
| 2026 | .heuristic = true, | 2055 | .heuristic = true, |
| 2027 | #endif | 2056 | .too_expensive = XFIXNUM (max_costs), |
| 2028 | /* FIXME: Find a good number for .too_expensive. */ | 2057 | .max_secs = FLOATP (max_secs) ? XFLOAT_DATA (max_secs) : -1.0, |
| 2029 | .too_expensive = 64, | 2058 | .early_abort_tests = 0 |
| 2030 | }; | 2059 | }; |
| 2031 | memclear (ctx.deletions, del_bytes); | 2060 | memclear (ctx.deletions, del_bytes); |
| 2032 | memclear (ctx.insertions, ins_bytes); | 2061 | memclear (ctx.insertions, ins_bytes); |
| 2062 | |||
| 2063 | gettimeofday (&ctx.start, NULL); | ||
| 2033 | /* compareseq requires indices to be zero-based. We add BEGV back | 2064 | /* compareseq requires indices to be zero-based. We add BEGV back |
| 2034 | later. */ | 2065 | later. */ |
| 2035 | bool early_abort = compareseq (0, size_a, 0, size_b, false, &ctx); | 2066 | bool early_abort = compareseq (0, size_a, 0, size_b, false, &ctx); |
| 2036 | /* Since we didn’t define EARLY_ABORT, we should never abort | 2067 | |
| 2037 | early. */ | 2068 | if (early_abort) |
| 2038 | eassert (! early_abort); | 2069 | { |
| 2070 | del_range (min_a, ZV); | ||
| 2071 | Finsert_buffer_substring (source, Qnil,Qnil); | ||
| 2072 | SAFE_FREE_UNBIND_TO (count, Qnil); | ||
| 2073 | return Qnil; | ||
| 2074 | } | ||
| 2039 | 2075 | ||
| 2040 | rbc_quitcounter = 0; | 2076 | rbc_quitcounter = 0; |
| 2041 | 2077 | ||
| @@ -2097,6 +2133,7 @@ differences between the two buffers. */) | |||
| 2097 | --i; | 2133 | --i; |
| 2098 | --j; | 2134 | --j; |
| 2099 | } | 2135 | } |
| 2136 | |||
| 2100 | SAFE_FREE_UNBIND_TO (count, Qnil); | 2137 | SAFE_FREE_UNBIND_TO (count, Qnil); |
| 2101 | rbc_quitcounter = 0; | 2138 | rbc_quitcounter = 0; |
| 2102 | 2139 | ||
| @@ -2106,7 +2143,7 @@ differences between the two buffers. */) | |||
| 2106 | update_compositions (BEGV, ZV, CHECK_INSIDE); | 2143 | update_compositions (BEGV, ZV, CHECK_INSIDE); |
| 2107 | } | 2144 | } |
| 2108 | 2145 | ||
| 2109 | return Qnil; | 2146 | return Qt; |
| 2110 | } | 2147 | } |
| 2111 | 2148 | ||
| 2112 | static void | 2149 | static void |
| @@ -2173,6 +2210,18 @@ buffer_chars_equal (struct context *ctx, | |||
| 2173 | == BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_b, bpos_b); | 2210 | == BUF_FETCH_MULTIBYTE_CHAR (ctx->buffer_b, bpos_b); |
| 2174 | } | 2211 | } |
| 2175 | 2212 | ||
| 2213 | static bool | ||
| 2214 | compareseq_early_abort (struct context *ctx) | ||
| 2215 | { | ||
| 2216 | if (ctx->max_secs < 0.0) | ||
| 2217 | return false; | ||
| 2218 | |||
| 2219 | struct timeval now, diff; | ||
| 2220 | gettimeofday (&now, NULL); | ||
| 2221 | timersub (&now, &ctx->start, &diff); | ||
| 2222 | return diff.tv_sec + diff.tv_usec / 1000000.0 > ctx->max_secs; | ||
| 2223 | } | ||
| 2224 | |||
| 2176 | 2225 | ||
| 2177 | static void | 2226 | static void |
| 2178 | subst_char_in_region_unwind (Lisp_Object arg) | 2227 | subst_char_in_region_unwind (Lisp_Object arg) |
| @@ -4441,6 +4490,12 @@ it to be non-nil. */); | |||
| 4441 | binary_as_unsigned = true; | 4490 | binary_as_unsigned = true; |
| 4442 | #endif | 4491 | #endif |
| 4443 | 4492 | ||
| 4493 | DEFVAR_LISP ("replace-buffer-contents-max-secs", | ||
| 4494 | Vreplace_buffer_contents_max_secs, | ||
| 4495 | doc: /* If differencing the two buffers takes longer than this, | ||
| 4496 | `replace-buffer-contents' falls back to a plain delete and insert. */); | ||
| 4497 | Vreplace_buffer_contents_max_secs = Qnil; | ||
| 4498 | |||
| 4444 | defsubr (&Spropertize); | 4499 | defsubr (&Spropertize); |
| 4445 | defsubr (&Schar_equal); | 4500 | defsubr (&Schar_equal); |
| 4446 | defsubr (&Sgoto_char); | 4501 | defsubr (&Sgoto_char); |