Right, << and StringBuffer is still less efficient, at least in terms of the asymptotic number of bytes copied.
I guess what I'm saying is that if you "properly" write a template engine, there should be no advantage to ropes at all.
Rather than a tree of non-copied fragments in the language runtime (ropes), you can simply create an array of non-copied fragments in the language itself.
Following the template engine I wrote in Python and JS:
- Fragments come from two places: holes and consts. holes come from variables, and consts are figured out at template compile time (e.g. which happens when the web server starts, and shouldn't be benchmarked).
- Fragments are never copied. They are simply appended to a big array of fragments. The template engine is a recursive tree walk interpreter that always appends to an array.
This is even true for a feature like #include. Some template engines may do the "right" thing for local fragments, but the copying may sneak back in for #include. But if you care, it's easy to handle #include in a consistent way.
So if you use that "obvious" architecture, then I claim there should be no advantage to ropes. But I guess that's not what happens in practice!
(Well, seeing some sibling comments about copying being faster, at least the claim is that this architecture should be as fast or as slow as ropes! :) But yeah I think ERB is probably doing something different and even worse, which is not surprising.)