Benefits of the new String.Buffer implementation: - Close to drop-in replacement for IOBuffer. - Performance hovers around that of IOBuffer's. - Support all native Pike character widths. - Backward compatible with the old String.Buffer. - Creating a String.Buffer object from a shared string does *not* incur a single copy (and is thus very lightweight). - A String.Buffer initialised from a shared string allows one to access the shared string in parts without copying the data at all. - Supports System.Memory objects just like IOBuffer. - No need to account for locks on subbuffers; String.Buffer resolves those automatically and on demand. - Explicit copying of the buffer data is not supported anymore, the implicit copy-on-demand mechanism is more efficient.
Stephen R. van den Berg wrote:
Things like subbuffers is unfortunately actually impossible when using a stringbuilder.
Well, it's not, really. Unless I made some rather striking mistakes.
Behold, the source code of String.Buffer NG has been checked in.
How do you add the trailing 0, and how do you avoid the fact that the
The trailing \0 is only required for shared strings, I streamlined the string_builder code dealing with that in a separate patch.
string_builder_x functions can at any time reallocate the 's' member to point to another memory location, making the sub-object totally invalid, and how do you avoid the fact that the str member is actually _part of_ the struct pike-string structure, so you at least temporarily have to overwrite the data in the main buffer?
This is solved using refcounts and copying the stringbuffer as soon as we want to modify one that has more than one ref.
With regard to benchmarks and speed. I'd say String.Buffer now rivals and possibly sometimes exceeds the performance of IOBuffer. Case in point:
int perf(object buffer) { buffer->add("11"); for(int i=0;i<100000; i++ ) { int l; if( buffer->cut ) { l = (buffer[0]<<8) | buffer[1]; buffer->cut(0,2,1); } else l = buffer->read_int(2); buffer->add(random_string(l)); } return sizeof(buffer); }
perf( String.Buffer() );
Result 37: 3270303901 Compilation: 936ns, Execution: 5.72s
perf( Stdio.IOBuffer() );
Result 35: 3274269166 Compilation: 941ns, Execution: 5.60s
But, String.Buffer supports close to all methods of IOBuffer, so a fairer test would be:
int perf(object buffer) { buffer->add("11"); for(int i=0;i<100000; i++ ) { int l; l = buffer->read_int(2); buffer->add(random_string(l)); } return sizeof(buffer); }
perf( String.Buffer() );
Result 44: 3271319355 Compilation: 949ns, Execution: 5.69s
perf( Stdio.IOBuffer() );
Result 41: 3278225479 Compilation: 942ns, Execution: 5.59s
Looks close enough. One should compare program-code-size too, I didn't bother to do that yet because it is part of builtin.cmod. I'd wager that my String.Buffer implementation has a smaller code footprint than the current IOBuffer.
Comments, criticism, patches are welcome, of course.