Skip site navigation (1)Skip section navigation (2)
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>