Skip to content

Commit 2ca96b0

Browse files
mattnN-R-K
authored andcommitted
patch 9.2.0140: file reading performance can be improved
Problem: Reading large files is slow because UTF-8 validation and newline scanning are performed byte-by-byte. Initial file loading also triggers listener and channel processing. Solution: Use memchr() for SIMD-optimized newline scanning, implement word-at-a-time ASCII skipping during UTF-8 validation using a bitmask, skip listener/netbeans/channel notifications when the ML_APPEND_NEW flag is set during readfile() (Yasuhiro Matsumoto). closes: #19612 Co-authored-by: NRK <nrk@disroot.org> Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent c970b47 commit 2ca96b0

File tree

3 files changed

+125
-68
lines changed

3 files changed

+125
-68
lines changed

src/fileio.c

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
// Is there any system that doesn't have access()?
2828
#define USE_MCH_ACCESS
2929

30+
// Bitmask with 0x80 set in each byte of a long_u word, used to detect
31+
// non-ASCII bytes (high bit set) in multiple bytes at once.
32+
#define NONASCII_MASK (((long_u)-1 / 0xFF) * 0x80)
33+
3034
#if defined(__hpux) && !defined(HAVE_DIRFD)
3135
# define dirfd(x) ((x)->__dd_fd)
3236
# define HAVE_DIRFD
@@ -2056,11 +2060,27 @@ readfile(
20562060
int incomplete_tail = FALSE;
20572061

20582062
// Reading UTF-8: Check if the bytes are valid UTF-8.
2059-
for (p = ptr; ; ++p)
2063+
for (p = ptr; ; )
20602064
{
2061-
int todo = (int)((ptr + size) - p);
2065+
int todo;
20622066
int l;
20632067

2068+
// Skip ASCII bytes quickly using word-at-a-time check.
2069+
{
2070+
char_u *ascii_end = ptr + size;
2071+
while (ascii_end - p >= (long)sizeof(long_u))
2072+
{
2073+
long_u word;
2074+
memcpy(&word, p, sizeof(long_u));
2075+
if (word & NONASCII_MASK)
2076+
break;
2077+
p += sizeof(long_u);
2078+
}
2079+
while (p < ascii_end && *p < 0x80)
2080+
++p;
2081+
}
2082+
2083+
todo = (int)((ptr + size) - p);
20642084
if (todo <= 0)
20652085
break;
20662086
if (*p >= 0x80)
@@ -2109,14 +2129,17 @@ readfile(
21092129
if (bad_char_behavior == BAD_DROP)
21102130
{
21112131
mch_memmove(p, p + 1, todo - 1);
2112-
--p;
21132132
--size;
21142133
}
2115-
else if (bad_char_behavior != BAD_KEEP)
2116-
*p = bad_char_behavior;
2134+
else
2135+
{
2136+
if (bad_char_behavior != BAD_KEEP)
2137+
*p = bad_char_behavior;
2138+
++p;
2139+
}
21172140
}
21182141
else
2119-
p += l - 1;
2142+
p += l;
21202143
}
21212144
}
21222145
if (p < ptr + size && !incomplete_tail)
@@ -2255,73 +2278,101 @@ readfile(
22552278
}
22562279
else
22572280
{
2258-
--ptr;
2259-
while (++ptr, --size >= 0)
2281+
// Use memchr() for SIMD-optimized newline scanning instead
2282+
// of scanning each byte individually.
2283+
char_u *end = ptr + size;
2284+
2285+
while (ptr < end)
22602286
{
2261-
if ((c = *ptr) != NUL && c != NL) // catch most common case
2262-
continue;
2263-
if (c == NUL)
2264-
*ptr = NL; // NULs are replaced by newlines!
2265-
else
2287+
char_u *nl = (char_u *)memchr(ptr, NL, end - ptr);
2288+
char_u *nul_scan;
2289+
2290+
if (nl == NULL)
22662291
{
2267-
if (skip_count == 0)
2292+
// No more newlines in buffer.
2293+
// Replace any NUL bytes with NL in remaining data.
2294+
while ((nul_scan = (char_u *)memchr(ptr, NUL,
2295+
end - ptr)) != NULL)
2296+
{
2297+
*nul_scan = NL;
2298+
ptr = nul_scan + 1;
2299+
}
2300+
ptr = end;
2301+
break;
2302+
}
2303+
2304+
// Replace NUL bytes with NL before the newline.
2305+
{
2306+
char_u *scan = ptr;
2307+
while ((nul_scan = (char_u *)memchr(scan, NUL,
2308+
nl - scan)) != NULL)
2309+
{
2310+
*nul_scan = NL;
2311+
scan = nul_scan + 1;
2312+
}
2313+
}
2314+
2315+
// Process the newline.
2316+
ptr = nl;
2317+
if (skip_count == 0)
2318+
{
2319+
*ptr = NUL; // end of line
2320+
len = (colnr_T)(ptr - line_start + 1);
2321+
if (fileformat == EOL_DOS)
22682322
{
2269-
*ptr = NUL; // end of line
2270-
len = (colnr_T)(ptr - line_start + 1);
2271-
if (fileformat == EOL_DOS)
2323+
if (ptr > line_start && ptr[-1] == CAR)
22722324
{
2273-
if (ptr > line_start && ptr[-1] == CAR)
2274-
{
2275-
// remove CR before NL
2276-
ptr[-1] = NUL;
2277-
--len;
2278-
}
2279-
/*
2280-
* Reading in Dos format, but no CR-LF found!
2281-
* When 'fileformats' includes "unix", delete all
2282-
* the lines read so far and start all over again.
2283-
* Otherwise give an error message later.
2284-
*/
2285-
else if (ff_error != EOL_DOS)
2286-
{
2287-
if ( try_unix
2288-
&& !read_stdin
2289-
&& (read_buffer
2290-
|| vim_lseek(fd, (off_T)0L, SEEK_SET)
2291-
== 0))
2292-
{
2293-
fileformat = EOL_UNIX;
2294-
if (set_options)
2295-
set_fileformat(EOL_UNIX, OPT_LOCAL);
2296-
file_rewind = TRUE;
2297-
keep_fileformat = TRUE;
2298-
goto retry;
2299-
}
2300-
ff_error = EOL_DOS;
2301-
}
2325+
// remove CR before NL
2326+
ptr[-1] = NUL;
2327+
--len;
23022328
}
2303-
if (ml_append(lnum, line_start, len, newfile) == FAIL)
2329+
/*
2330+
* Reading in Dos format, but no CR-LF found!
2331+
* When 'fileformats' includes "unix", delete all
2332+
* the lines read so far and start all over again.
2333+
* Otherwise give an error message later.
2334+
*/
2335+
else if (ff_error != EOL_DOS)
23042336
{
2305-
error = TRUE;
2306-
break;
2337+
if ( try_unix
2338+
&& !read_stdin
2339+
&& (read_buffer
2340+
|| vim_lseek(fd, (off_T)0L, SEEK_SET)
2341+
== 0))
2342+
{
2343+
fileformat = EOL_UNIX;
2344+
if (set_options)
2345+
set_fileformat(EOL_UNIX, OPT_LOCAL);
2346+
file_rewind = TRUE;
2347+
keep_fileformat = TRUE;
2348+
goto retry;
2349+
}
2350+
ff_error = EOL_DOS;
23072351
}
2352+
}
2353+
if (ml_append(lnum, line_start, len, newfile) == FAIL)
2354+
{
2355+
error = TRUE;
2356+
break;
2357+
}
23082358
#ifdef FEAT_PERSISTENT_UNDO
2309-
if (read_undo_file)
2310-
sha256_update(&sha_ctx, line_start, len);
2359+
if (read_undo_file)
2360+
sha256_update(&sha_ctx, line_start, len);
23112361
#endif
2312-
++lnum;
2313-
if (--read_count == 0)
2314-
{
2315-
error = TRUE; // break loop
2316-
line_start = ptr; // nothing left to write
2317-
break;
2318-
}
2362+
++lnum;
2363+
if (--read_count == 0)
2364+
{
2365+
error = TRUE; // break loop
2366+
line_start = ptr; // nothing left to write
2367+
break;
23192368
}
2320-
else
2321-
--skip_count;
2322-
line_start = ptr + 1;
23232369
}
2370+
else
2371+
--skip_count;
2372+
line_start = ptr + 1;
2373+
++ptr;
23242374
}
2375+
size = -1;
23252376
}
23262377
linerest = (long)(ptr - line_start);
23272378
ui_breakcheck();

src/memline.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,7 +3472,7 @@ ml_append_int(
34723472
#endif
34733473

34743474
#ifdef FEAT_NETBEANS_INTG
3475-
if (netbeans_active())
3475+
if (!(flags & ML_APPEND_NEW) && netbeans_active())
34763476
{
34773477
int line_len = (int)STRLEN(line);
34783478
if (line_len > 0)
@@ -3481,7 +3481,7 @@ ml_append_int(
34813481
}
34823482
#endif
34833483
#ifdef FEAT_JOB_CHANNEL
3484-
if (buf->b_write_to_channel)
3484+
if (!(flags & ML_APPEND_NEW) && buf->b_write_to_channel)
34853485
channel_write_new_lines(buf);
34863486
#endif
34873487
ret = OK;
@@ -3512,11 +3512,15 @@ ml_append_flush(
35123512
ml_flush_line(buf);
35133513

35143514
#ifdef FEAT_EVAL
3515-
// When inserting above recorded changes: flush the changes before changing
3516-
// the text. Then flush the cached line, it may become invalid.
3517-
may_invoke_listeners(buf, lnum + 1, lnum + 1, 1);
3518-
if (buf->b_ml.ml_line_lnum != 0)
3519-
ml_flush_line(buf);
3515+
if (!(flags & ML_APPEND_NEW))
3516+
{
3517+
// When inserting above recorded changes: flush the changes before
3518+
// changing the text. Then flush the cached line, it may become
3519+
// invalid. Skip during initial file read for performance.
3520+
may_invoke_listeners(buf, lnum + 1, lnum + 1, 1);
3521+
if (buf->b_ml.ml_line_lnum != 0)
3522+
ml_flush_line(buf);
3523+
}
35203524
#endif
35213525

35223526
return ml_append_int(buf, lnum, line, len, flags);

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
140,
737739
/**/
738740
139,
739741
/**/

0 commit comments

Comments
 (0)