9 ms·
> When performing string concatenation operations, it is more advantageous in terms of performance to use std::ostringstream rather than std::string. This appro
by Calavar 3y ago
> When performing string concatenation operations, it is more advantageous in terms of performance to use std::ostringstream rather than std::string. This approach is also used elsewhere, such as debug_utils and node_errors.
(From the Node.js GitHub issue.) Sounds like this guy is mixing up his Java knowledge with C++ knowledge.
C++ streams are frankly insane: Loads of implicit state, needing to set about a half dozen flags to do any nontrivial formatting, running the risk of accidentally "poisoning" all downstream operations if you forget to reset any of that state, the useless callbacks API [1], obfuscated function names (xsgetn, epptr, egptr), a ridiculously convoluted inheritance hierarchy that includes virtual/diamond inheritance [2], and use of virtual functions for simple buffer manipulation. These were all bad decisions even at the time.
[1] https://en.cppreference.com/w/cpp/io/ios_base/register_callback https://en.cppreference.com/w/cpp/io/ios_base/register_callb...
[2] https://i.stack.imgur.com/dXhXP.png https://i.stack.imgur.com/dXhXP.png
- AndrewStephens 3y agoiostreams are such a messy design, for all the reasons you mention and more. They are rightly avoided in all code that cares about performance.
- juunpp 3y ago> (From the Node.js GitHub issue.) Sounds like this guy is mixing up his Java knowledge with C++ knowledge. That is exactly it. C++ string streams have had atrocious performance since forever. Good abstraction, not very useful in practice. In Java, if I remember correctly, strings are immutable, so the StringBuilder or whatever ridiculous name it had was the faster way to build a string. > "I recently learned that some Node.js engineers prefer stream classes when building strings, for performance reasons." Pretty much tells you everything you need to know about node js, I guess.
- Rebelgecko 3y agoFor what it's worth, even in Java the compiler is often smart enough to replace naive string concatenation with equivalent StringBuilder usage (although I don't know if it is smart enough to do that in a for loop like this)
- throwaway2037 3y agoTo less informed readers, "smart enough" hear means: Imagine you have two Java Strings: a and b If you write the Java code: String c = a + b; The Java compiler will roughly replace this code with: String c = new StringBuilder(a).append(b).toString(); I'm not sure I would say this is "smart". Rather, it is just a hack to allow Strings to override the plus (+) operator. Normally, Java does not allow operator overidding, like C++.
- masklinn 3y agoTwo things: First, it has nothing to do with overriding the + operator, the operation is statically decidable so the compiler is perfectly aware that this is a string concatenation (not a numerical addition) and string is a special builtin, the compiler can generate anything it wants. Which is exactly what it does, they just originally decided to implement string concatenation as stringbuffer/stringbuilder ops. Meanwhile the addition of integers, longs, floats, and doubles are (or were anyway) different bytecode ops. This does not require general purpose operator overloading support. Builtins are not limited to the language’s user level semantics. And second OpenJDK has not done that since Java 9 (JEP 280), the compiler now emits generic “string concatenation” bytecode for the runtime / jit to deal with.
- masklinn 3y agoIt’s not, and they basically gave up on it. Historically the OpenJDK would compile direct concatenation to StringBuilder so e.g. a += b + c; Would become something like StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b); sb.append(c); a = sb.toString(); However it was never capable of doing such an optimisation across loops. In Java 9, they gave up on such static optimisations, instead the compiler now emits dedicated string concatenation bytecode (see JEP 280), which the runtime (and JIT) can then hook into. Does that handle loops? Unclear. Benchmarks / testimonies from back then (java9/java10 days) hint that no, direct concatenation remains much slower than StringBuilder. But I didn’t find anything super recent so maybe they improved the behaviour in the meantime.
- paulddraper 3y ago> "I recently learned that some Node.js engineers prefer stream classes when building strings, for performance reasons." Pretty much tells you everything you need to know about node js, I guess. Google Closure Library includes a StringBuffer class. [1] I recall it having explanatory notes, but I don't see them in the code now. JavaScript engines can optimize a string concatenating to in-place edit, if there is only one reference to the first string. The StringBuffer class keeps the reference count at one, guaranteeing this optimization is available, even if the StringBuffer itself is ever shared. [1] https://github.com/google/closure-library/blob/master/closure/goog/string/stringbuffer.js https://github.com/google/closure-library/blob/master/closur...
- lifthrasiir 3y agoA bigger problem is that iostream is still the only C++ way to read and write files. Yeah, you can still use `std::fopen` and so on, but the modern C++ strives to minimize type-ignorant C functions right? The introduction of `std::format` made the formatting aspect of iostream obsolete, but iostream still has no standard alternative for other aspects.
- Calavar 3y agostd::print is coming with C++23. In the meantime, there's std::format_to. You still have to dump the output into an std::ostream, but at least you don't have to use the disgusting ostream interface directly.
- nly 3y agostd::filebuf can be used directly without std::fstream if you just want to read and write bytes without doing formatted operations.
- lifthrasiir 3y agoThat's fair, but `std::filebuf` still carries an `imbue` baggage...
- thefourthchime 3y agoC++ dev for 20+ years. I refused to use them, they encapsulated everything with C++ I hated. A ton of implicit actions and gotchas. It's a gun in hand, foot in target.
- jnwatson 3y agostreams is the best evidence that C++ was an experiment. It was a sandbox to try a bunch of different language ideas. Overloading the shift operators for this purpose is prima facie insane, and anyone who has single-stepped through a C++ "hello world" program can figure out it isn't remotely efficient, but it was certainly creative.
- otabdeveloper4 3y agoThe shift operators are literally the nicest and most innocuous design decision of C++ streams. What's broken with them has nothing to do with bikeshedding about operator choice.
- lifthrasiir 3y agoBut still not without problems, because you can't naturally write `cout << x & 0x1f << endl;`...
- snovv_crash 3y agoJust put some brackets like you should for any operator chaining. Explicit over implicit etc.
- lifthrasiir 3y agoBut here `<<` doesn't really feel like an actual operator here, just an ordinary separator like `,`. (Of course, `,` is actually an operator in C++. But anyway.) It is just completely alien to other existing C++ conventions before and after that.
- calamari4065 3y agoHuh. I don't know much at all about C++ streams, but this sounds only marginally worse than C#'s streams
- colonwqbang 3y agoMaybe not. The problem is that C++ (before C++20) has no normal print and format function. You supposed to do everything with streams. To switch to two decimal places you would first output some magic value that sets an internal flag in the stream. Then you need to remember to restore it again. Of course you could just use good old C printf to get some work done. But if you did that the "real" C++ programmers would sneer at you.
- snovv_crash 3y agoprintf is fast, but has safety problems. Also frankly I don't like the fact that the formatting and the variable the data comes from are separated and you have to count out the argument orders to figure which one is which. Python's fstring gets this right.
- another2another 3y agoedit: nvm, looks like I just described fmtlib. I really should keep up..
- josefx 3y ago> Of course you could just use good old C printf The fun thing with printf is that variadic functions cannot handle C++ classes. In the past was doubly fun because compilers proactively compiled an error into the binary instead of raising an error at compile time. So any forgotten c_str() became a crash waiting to happen. C Printf never was, never could be and never will be a suitable way to output data from C++. Now excuse me while I go through the list of thousands of predefined format macros to find out which I need to use to output a uint_fast16_t without making the compiler vomit nonsense.
- deleted 3y ago
- kristianp 3y agoThe github issue: https://github.com/nodejs/node/pull/50253 https://github.com/nodejs/node/pull/50253 Note that the person mixing java knowledge and C++ isn't Daniel Lemire.
- uxp8u61q 3y agoWhat's the alternative to string streams for building strings pieces by pieces in C++? Plain old string concatenation? Asking for a friend... I should run benchmarks I guess.
- im3w1l 3y agoAre you doing something in a hot loop or not? The answer is likely that it doesn't really matter. If you really need speed, then estimate how large string you need in advance and preallocate it (either with new char[], or string::reserve ig).
- raverbashing 3y agoReally C++ the base language has issues, for sure But iostream is like taking someone that's crazy to use every single language feature and who think there's some ulterior motive to create these crazy inheritance levels etc Maybe we need C+=2 to make things less crazy
- kevin_thibedeau 3y agoCareful, the committee may decide to deprecate compound assignment again.