Date: Sat, 01 Aug 2026 03:35:29 +0000 From: Kyle Evans <kevans@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: a3a884c0d43a - main - stdio: *memstream: grow the buffer by 1.5x on write Message-ID: <6a6d6981.367df.741dd191@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=a3a884c0d43ab02187022be9ae9084e6c725ba68 commit a3a884c0d43ab02187022be9ae9084e6c725ba68 Author: Kyle Evans <kevans@FreeBSD.org> AuthorDate: 2026-08-01 03:34:37 +0000 Commit: Kyle Evans <kevans@FreeBSD.org> CommitDate: 2026-08-01 03:34:37 +0000 stdio: *memstream: grow the buffer by 1.5x on write This improves performance by reducing the number of allocations as we write into the memstream, both in the fully buffered case with larger memstreams and also more trivially in the line- and un-buffered case as they flush back to the underlying buffer more often. The inspiration for this was taken from Apple's implementation in https://github.com/apple-oss-distributions/libc, but expanded to include wmemstream for consistency. I've added a test for the bug that I hit in libder that caused me to notice this in the first place, and fixed that bug in this version. Reviewed by: des, jhb (both slightly previous version) Sponsored by: Klara, Inc. Differential Revision: https://reviews.freebsd.org/D57355 --- lib/libc/stdio/open_memstream.c | 21 +++++++++++----- lib/libc/stdio/open_wmemstream.c | 22 +++++++++++----- lib/libc/tests/stdio/open_memstream2_test.c | 39 +++++++++++++++++++++++++++++ lib/libc/tests/stdio/open_wmemstream_test.c | 38 ++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/lib/libc/stdio/open_memstream.c b/lib/libc/stdio/open_memstream.c index d0a22bcfe59f..fbff50001cda 100644 --- a/lib/libc/stdio/open_memstream.c +++ b/lib/libc/stdio/open_memstream.c @@ -28,9 +28,11 @@ */ #include "namespace.h" +#include <sys/param.h> #include <assert.h> #include <errno.h> #include <limits.h> +#include <stdckdint.h> #ifdef DEBUG #include <stdint.h> #endif @@ -55,13 +57,20 @@ static int memstream_grow(struct memstream *ms, fpos_t newoff) { char *buf; - ssize_t newsize; + ssize_t newlen; if (newoff < 0 || newoff >= SSIZE_MAX) - newsize = SSIZE_MAX - 1; + newlen = SSIZE_MAX - 1; else - newsize = newoff; - if (newsize > ms->size) { + newlen = newoff; + if (newlen > ms->size) { + size_t newsize; + + if (ckd_add(&newsize, ms->size, ms->size / 2)) + newsize = SSIZE_MAX - 1; + + newsize = MAX(newsize, newlen); + buf = realloc(*ms->bufp, newsize + 1); if (buf == NULL) return (0); @@ -75,8 +84,8 @@ memstream_grow(struct memstream *ms, fpos_t newoff) ms->size = newsize; } - if (newsize > ms->len) - ms->len = newsize; + if (newlen > ms->len) + ms->len = newlen; return (1); } diff --git a/lib/libc/stdio/open_wmemstream.c b/lib/libc/stdio/open_wmemstream.c index d1844e573b1b..0cadf892023e 100644 --- a/lib/libc/stdio/open_wmemstream.c +++ b/lib/libc/stdio/open_wmemstream.c @@ -28,9 +28,11 @@ */ #include "namespace.h" +#include <sys/param.h> #include <assert.h> #include <errno.h> #include <limits.h> +#include <stdckdint.h> #ifdef DEBUG #include <stdint.h> #endif @@ -56,13 +58,20 @@ static int wmemstream_grow(struct wmemstream *ms, fpos_t newoff) { wchar_t *buf; - ssize_t newsize; + ssize_t newlen; if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t)) - newsize = SSIZE_MAX / sizeof(wchar_t) - 1; + newlen = SSIZE_MAX / sizeof(wchar_t) - 1; else - newsize = newoff; - if (newsize > ms->size) { + newlen = newoff; + if (newlen > ms->size) { + size_t newsize; + + if (ckd_add(&newsize, ms->size, ms->size / 2)) + newsize = SSIZE_MAX - 1; + + newsize = MAX(newsize, newlen); + buf = reallocarray(*ms->bufp, newsize + 1, sizeof(wchar_t)); if (buf == NULL) return (0); @@ -74,8 +83,9 @@ wmemstream_grow(struct wmemstream *ms, fpos_t newoff) *ms->bufp = buf; ms->size = newsize; } - if (newsize > ms->len) - ms->len = newsize; + + if (newlen > ms->len) + ms->len = newlen; return (1); } diff --git a/lib/libc/tests/stdio/open_memstream2_test.c b/lib/libc/tests/stdio/open_memstream2_test.c index c9c6528832d8..13df97f12dcd 100644 --- a/lib/libc/tests/stdio/open_memstream2_test.c +++ b/lib/libc/tests/stdio/open_memstream2_test.c @@ -184,7 +184,45 @@ ATF_TC_BODY(seek_tests, tc) SEEK_OK(-1, SEEK_END, 2); SEEK_OK(OFF_MAX - 1, SEEK_SET, OFF_MAX - 1); SEEK_FAIL(2, SEEK_CUR, EOVERFLOW); + + fclose(fp); +} + +ATF_TC(resize_test); +ATF_TC_HEAD(resize_test, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Tests that memstream growth doesn't move SEEK_END too far"); +} +ATF_TC_BODY(resize_test, tc) +{ + FILE *fp; + size_t wsz; + char addition[] = "Test String"; + + fp = open_memstream(&buf, &len); + ATF_REQUIRE_MSG(fp != NULL, "open_memstream failed: %d", errno); + + /* Doesn't matter what we write, we'll just want to trigger alloc 2x. */ + wsz = fprintf(fp, "%s", addition); + ATF_REQUIRE_EQ(wsz, sizeof(addition) - 1); + ATF_REQUIRE(fflush(fp) == 0); + + /* Trigger growth. */ + ATF_REQUIRE_EQ('a', fputc('a', fp)); + ATF_REQUIRE_EQ(wsz + 1, ftello(fp)); + + /* + * Seeking to the end shouldn't put us past the last write, because + * we've only done a small series of writes. + */ + ATF_REQUIRE(fseek(fp, 0, SEEK_END) == 0); + ATF_REQUIRE_EQ(wsz + 1, ftello(fp)); + + ATF_REQUIRE(!ferror(fp)); fclose(fp); + + ATF_REQUIRE_EQ(wsz + 1, len); } ATF_TP_ADD_TCS(tp) @@ -193,6 +231,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, open_group_test); ATF_TP_ADD_TC(tp, simple_tests); ATF_TP_ADD_TC(tp, seek_tests); + ATF_TP_ADD_TC(tp, resize_test); return (atf_no_error()); } diff --git a/lib/libc/tests/stdio/open_wmemstream_test.c b/lib/libc/tests/stdio/open_wmemstream_test.c index 7ab882a4740a..3fce061e832f 100644 --- a/lib/libc/tests/stdio/open_wmemstream_test.c +++ b/lib/libc/tests/stdio/open_wmemstream_test.c @@ -187,12 +187,50 @@ ATF_TC_BODY(seek_tests, tc) fclose(fp); } +ATF_TC(resize_test); +ATF_TC_HEAD(resize_test, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Tests that wmemstream growth doesn't move SEEK_END too far"); +} +ATF_TC_BODY(resize_test, tc) +{ + FILE *fp; + size_t wsz; + wchar_t addition[] = L"Test String"; + + fp = open_wmemstream(&buf, &len); + ATF_REQUIRE_MSG(fp != NULL, "open_memstream failed: %d", errno); + + /* Doesn't matter what we write, we'll just want to trigger alloc 2x. */ + wsz = fwprintf(fp, L"%ls", addition); + ATF_REQUIRE_EQ(wsz, wcslen(addition)); + ATF_REQUIRE(fflush(fp) == 0); + + /* Trigger growth. */ + ATF_REQUIRE_EQ(L'a', fputwc(L'a', fp)); + ATF_REQUIRE_EQ(wsz + 1, ftello(fp)); + + /* + * Seeking to the end shouldn't put us past the last write, because + * we've only done a small series of writes. + */ + ATF_REQUIRE(fseek(fp, 0, SEEK_END) == 0); + ATF_REQUIRE_EQ(wsz + 1, ftello(fp)); + + ATF_REQUIRE(!ferror(fp)); + fclose(fp); + + ATF_REQUIRE_EQ(wsz + 1, len); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, open_group_test); ATF_TP_ADD_TC(tp, simple_tests); ATF_TP_ADD_TC(tp, seek_tests); + ATF_TP_ADD_TC(tp, resize_test); return (atf_no_error()); }home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a6d6981.367df.741dd191>
