refactor(api): reduce temporary allocations when replacing lines

The way ml_replace_buf is implemented makes it unfriendly for
being used in a loop: every call allocates a scratch buffer for putting
the line into the "dirty" state. This then immediately needs to be freed
as the next ml_replace_buf and/or ml_append_buf call will flush that buffer.

It's better to later pay the price of allocating the scratch buffer only if
the line is being immediately edited (likely when using the API to only
change one line) with an extra memcpy, than allocating that buffer
multiple times every time the API is called.

Of course, a separate xmalloc/xfree cycle for each time the dirty line
changes is unwanted to begin with. But fixing that is a later refactor.
This commit is contained in:
bfredl
2024-02-19 12:00:26 +01:00
parent 8952a89db5
commit f2c73e9ee2
8 changed files with 73 additions and 80 deletions

View File

@@ -1694,7 +1694,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) {
continue; // no file
}
ml_flush_line(buf); // flush buffered line
ml_flush_line(buf, false); // flush buffered line
// flush locked block
ml_find_line(buf, 0, ML_FLUSH);
if (bufIsChanged(buf) && check_file && mf_need_trans(buf->b_ml.ml_mfp)
@@ -1745,7 +1745,7 @@ void ml_preserve(buf_T *buf, bool message, bool do_fsync)
// before.
got_int = false;
ml_flush_line(buf); // flush buffered line
ml_flush_line(buf, false); // flush buffered line
ml_find_line(buf, 0, ML_FLUSH); // flush locked block
int status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0));
@@ -1862,7 +1862,7 @@ static char *ml_get_buf_impl(buf_T *buf, linenr_T lnum, bool will_change)
siemsg(_(e_ml_get_invalid_lnum_nr), (int64_t)lnum);
recursive--;
}
ml_flush_line(buf);
ml_flush_line(buf, false);
errorret:
STRCPY(questions, "???");
buf->b_ml.ml_line_lnum = lnum;
@@ -1881,7 +1881,7 @@ errorret:
// Don't use the last used line when 'swapfile' is reset, need to load all
// blocks.
if (buf->b_ml.ml_line_lnum != lnum) {
ml_flush_line(buf);
ml_flush_line(buf, false);
// Find the data block containing the line.
// This also fills the stack with the blocks from the root to the data
@@ -1964,7 +1964,7 @@ int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile)
}
if (curbuf->b_ml.ml_line_lnum != 0) {
ml_flush_line(curbuf);
ml_flush_line(curbuf, false);
}
return ml_append_int(curbuf, lnum, line, len, newfile, false);
}
@@ -1984,7 +1984,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfi
}
if (buf->b_ml.ml_line_lnum != 0) {
ml_flush_line(buf);
ml_flush_line(buf, false);
}
return ml_append_int(buf, lnum, line, len, newfile, false);
}
@@ -2423,7 +2423,7 @@ void ml_add_deleted_len_buf(buf_T *buf, char *ptr, ssize_t len)
int ml_replace(linenr_T lnum, char *line, bool copy)
{
return ml_replace_buf(curbuf, lnum, line, copy);
return ml_replace_buf(curbuf, lnum, line, copy, false);
}
/// Replace line "lnum", with buffering, in current buffer.
@@ -2438,7 +2438,7 @@ int ml_replace(linenr_T lnum, char *line, bool copy)
/// changed_lines(), unless update_screen(UPD_NOT_VALID) is used.
///
/// @return FAIL for failure, OK otherwise
int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy, bool noalloc)
{
if (line == NULL) { // just checking...
return FAIL;
@@ -2450,12 +2450,13 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
}
if (copy) {
assert(!noalloc);
line = xstrdup(line);
}
if (buf->b_ml.ml_line_lnum != lnum) {
// another line is buffered, flush it
ml_flush_line(buf);
ml_flush_line(buf, false);
}
if (kv_size(buf->update_callbacks)) {
@@ -2469,6 +2470,13 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
buf->b_ml.ml_line_ptr = line;
buf->b_ml.ml_line_lnum = lnum;
buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
if (noalloc) {
// TODO(bfredl): this is a bit of a hack. but replacing lines in a loop is really common,
// and allocating a separate scratch buffer for each line which is immediately freed adds
// a lot of noise. A more general refactor could be to use a _fixed_ scratch buffer for
// all lines up to $REASONABLE_SIZE .
ml_flush_line(buf, true);
}
return OK;
}
@@ -2483,7 +2491,7 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
/// @return FAIL for failure, OK otherwise
int ml_delete(linenr_T lnum, bool message)
{
ml_flush_line(curbuf);
ml_flush_line(curbuf, false);
return ml_delete_int(curbuf, lnum, message);
}
@@ -2496,7 +2504,7 @@ int ml_delete(linenr_T lnum, bool message)
/// @return FAIL for failure, OK otherwise
int ml_delete_buf(buf_T *buf, linenr_T lnum, bool message)
{
ml_flush_line(buf);
ml_flush_line(buf, false);
return ml_delete_int(buf, lnum, message);
}
@@ -2516,7 +2524,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
set_keep_msg(_(no_lines_msg), 0);
}
int i = ml_replace_buf(buf, 1, "", true);
int i = ml_replace_buf(buf, 1, "", true, false);
buf->b_ml.ml_flags |= ML_EMPTY;
return i;
@@ -2724,7 +2732,7 @@ size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits)
}
/// flush ml_line if necessary
static void ml_flush_line(buf_T *buf)
static void ml_flush_line(buf_T *buf, bool noalloc)
{
static bool entered = false;
@@ -2798,10 +2806,13 @@ static void ml_flush_line(buf_T *buf)
ml_delete_int(buf, lnum, false);
}
}
xfree(new_line);
if (!noalloc) {
xfree(new_line);
}
entered = false;
} else if (buf->b_ml.ml_flags & ML_ALLOCATED) {
assert(!noalloc); // caller must set ML_LINE_DIRTY with noalloc, handled above
xfree(buf->b_ml.ml_line_ptr);
}
@@ -3903,7 +3914,7 @@ int ml_find_line_or_offset(buf_T *buf, linenr_T lnum, int *offp, bool no_ff)
// was never cached to start with anyway).
bool can_cache = (lnum != 0 && !ffdos && buf->b_ml.ml_line_lnum == lnum);
if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
ml_flush_line(curbuf);
ml_flush_line(curbuf, false);
} else if (can_cache && buf->b_ml.ml_line_offset > 0) {
return (int)buf->b_ml.ml_line_offset;
}
@@ -4030,7 +4041,7 @@ void goto_byte(int cnt)
{
int boff = cnt;
ml_flush_line(curbuf); // cached line may be dirty
ml_flush_line(curbuf, false); // cached line may be dirty
setpcmark();
if (boff) {
boff--;