RFR: JDK-8260030: Improve stringStream buffer handling

Kim Barrett kbarrett at openjdk.java.net
Thu Jan 21 15:43:32 UTC 2021


On Wed, 20 Jan 2021 10:27:11 GMT, Thomas Stuefe <stuefe at openjdk.org> wrote:

> stringStream objects are used as temporary string buffers a lot in hotspot. When investigating JDK-8259710, I found that a large majority of the stringStreams created never use the backing buffer fully; about 70% of all streams write less than 32 characters.
> 
> stringStream creates an backing buffer, C-heap allocated, with a default size of 256 characters. Some things could be improved:
> 
> - add a small in-object buffer for the first n characters to be written. This would avoid or at least delay allocation of the backing buffer from C-heap, at the expense of slightly increased object size and one memcpy when switching over to C-heap. Note that if the backing buffer is smaller than what now is the default size, the total footprint will go down despite the increased object size.
> 
> - Delaying allocation of the backing buffer would be good for the many cases where stringStream objects are created as temporary objects without being actually used, see eg. compile.hpp `class PrintInliningBuffer`
> 
> - Besides all that, the code could benefit from a little grooming (using modern style syntax etc).
> 
> ----
> 
> This patch:
> 
> - renames members of stringStream to conform to common syntax (eg leading underscores) and to be clearer
> - Uses initialization lists
> - Factors out buffer growth code from stringStream::write() to a new function stringStream::grow()
> - Introduces a new object-internal mini buffer, `stringStream::_small_buffer`, sized 32 bytes, which is initially used for all writes
> 
> This patch drastically reduces the number of malloc calls done from this class. The internal buffer size of 32byte seems a good cut-off. Running some unrelated test program (no tracing active), I see a reduction in the number of malloc calls from stringStream from ~211K malloc calls down to 53K (debug VM). In a release VM, it drops from ~85K down to about 1K. The reason is that `stringStream` is used in a ton of places as a temporary stack allocated object, to write very minimal text snippets to.
> 
> I also tweaked the associated gtest to test more thoroughly.

Changes requested by kbarrett (Reviewer).

src/hotspot/share/utilities/ostream.cpp line 341:

> 339:   assert(new_capacity > _capacity, "Sanity");
> 340:   assert(new_capacity > sizeof(_small_buffer), "Sanity");
> 341:   assert(!_is_fixed, "Don't call for caller provided buffers");

I think the !_is_fixed assert should be first.

src/hotspot/share/utilities/ostream.cpp line 357:

> 355: void stringStream::write(const char* s, size_t len) {
> 356:   size_t write_len = len;               // number of non-null bytes to write
> 357:   size_t end = _written + len + 1;    // position after write and final '\0'

There's a risk of arithmetic overflow here, if len is "bad".  Can this be
rewritten to be more careful?  Though maybe an assert that end <= _written
is sufficient, as line 369 already protects against buffer overrun.
Also, the comments aren't lined up.

src/hotspot/share/utilities/ostream.cpp line 407:

> 405: 
> 406: stringStream::~stringStream() {
> 407:   if (_is_fixed == false && _buffer != NULL && _buffer != _small_buffer) {

`_is_fixed == false` => `!_is_fixed`
Is `_buffer == NULL` actually possible now?

src/hotspot/share/utilities/ostream.hpp line 196:

> 194: class stringStream : public outputStream {
> 195:  protected:
> 196:   char*  _buffer;

Consider changing the protected members to private.  There are no
derived classes, and probably shouldn't be without more work.

src/hotspot/share/utilities/ostream.cpp line 389:

> 387: 
> 388: void stringStream::reset() {
> 389:   _written = 0; _precount = 0; _position = 0;

Is it a pre-existing bug that this doesn't also have `_newlines = 0;` ?

src/hotspot/share/utilities/ostream.hpp line 200:

> 198:   size_t _capacity;
> 199:   const bool _is_fixed;
> 200:   char   _small_buffer[32];

I'm a little surprised that 32 bytes is that effective. Any idea how much better
(in malloc avoidance) a few more words would be?  Since 32 bytes isn't really
all that much.

src/hotspot/share/utilities/ostream.cpp line 396:

> 394:   char* copy = c_heap ?
> 395:     NEW_C_HEAP_ARRAY(char, _written + 1, mtInternal) : NEW_RESOURCE_ARRAY(char, _written + 1);
> 396:   strncpy(copy, _buffer, _written);

Pre-existing: Should this really be using strncpy rather than memcpy?
strncpy will stop at an embedded NUL.

-------------

PR: https://git.openjdk.java.net/jdk/pull/2160


More information about the hotspot-dev mailing list