CC: Tweaked

CC: Tweaked

42M Downloads

Use a string buffer within textutils.serialize

SquidDev opened this issue ยท 3 comments

commented

textutils.serialize (or rather serializeImpl) currently builds up its result via immediate string concatenation. This means its complexity is quadratic, which isn't fantastic.

While textutils.serialize's performance is not a serious concern, it would still be nice to improve it. The best way to do that would be replacing our concatenation with a classic string buffer and a final concatenation:

local function serializeImpl( t, tTracking, sIndent, buffer )
    if sType == "string" then
        buffer[#buffer + 1] = string.format( "%q", t )
    elseif -- etc...
end

function serialize( t )
    local tTracking, buffer = {}, {}
    serializeImpl( t, tTracking, "", buffer )
    return table.concat(buffer)
end

One other thing to note is that getting the length of a table is O(lg(n)), so it may be worth keeping that around too. However it's probably worth profiling doing so and seeing if it is worthwhile or not.

commented

Just had a stab at this, and was unable to find a solution which consistently ended up faster than the naive implementation. Which leaves me slightly worried, as it does question everything I know about Lua performance. Welp.

commented

If people want to have a look at this themselves, happy to share my benchmarking scripts and various attempts.


Edit: Just to clarify the above, there are solutions which work better in specific (arguably pathological) cases, but not in the general case.

Here's a snapshot of profiling data. I'm number of serialize calls in a 3 second window, so higher is better:

Strategy Small Deep Wide Long
str-concat 42426 586 18 36
cache-insert 27533 3285 13 127
str-concat-buffer 32774 503 16 221
inline-insert-nocache 32516 4510 11 178
inline-insert 31428 4607 13 169
upvalue 28732 3265 11 140
table-insert 35770 3804 11 156

For reference, str-concat is the current implementation. You can see it totally falls apart on some edge cases (very heavily nested tables or long arrays), but for the most part it's still significantly better.

commented

I had a little go with this and got a pretty good result.
benchmark-results
As shown, the new approach is better in every benchmark.

The benchmark script and impl are available here (pastebin get Kr9BdBWJ). See line 174 for changing the benchmarks run, and the tables the benchmarks are run on.

Edit: One potential issue with the function is that it relies on some maybe-implementation-specific behaviour: next has been seen to first return array keys, then hash keys. The serialize implementation uses this to skip the array part by getting the next key with next(value, length).