Replace split+join in truncatewords with manual word scan — avoids array + string allocations\n\nResult: {"status":"keep","combined_µs":4280,"parse_µs":3009,"render_µs":1271,"allocations":26395}

This commit is contained in:
Tobi Lutke
2026-03-11 10:20:11 -04:00
parent 6723d4fa15
commit b48615f4a7
+47 -11
View File
@@ -266,18 +266,54 @@ module Liquid
words = Utils.to_integer(words) words = Utils.to_integer(words)
words = 1 if words <= 0 words = 1 if words <= 0
wordlist = begin return input if words + 1 > MAX_I32
input.split(" ", words + 1)
rescue RangeError
# integer too big for String#split, but we can semantically assume no truncation is needed
return input if words + 1 > MAX_I32
raise # unexpected error
end
return input if wordlist.length <= words
wordlist.pop # Build result incrementally — avoids split() array + string allocations
truncate_string = Utils.to_s(truncate_string) len = input.bytesize
wordlist.join(" ").concat(truncate_string) pos = 0
word_count = 0
result = nil
# Skip leading whitespace
while pos < len
b = input.getbyte(pos)
break unless b == 32 || b == 9 || b == 10 || b == 13 || b == 12
pos += 1
end
while pos < len
word_start = pos
word_count += 1
# Skip non-whitespace chars (word body)
while pos < len
b = input.getbyte(pos)
break if b == 32 || b == 9 || b == 10 || b == 13 || b == 12
pos += 1
end
if word_count > words
# Truncate — result already has the first N words
truncate_string = Utils.to_s(truncate_string)
return result.concat(truncate_string)
end
# Append word to result (only allocate result when we know truncation is possible)
if result
result << " " << input.byteslice(word_start, pos - word_start)
else
result = +input.byteslice(word_start, pos - word_start)
end
# Skip whitespace between words
while pos < len
b = input.getbyte(pos)
break unless b == 32 || b == 9 || b == 10 || b == 13 || b == 12
pos += 1
end
end
input
end end
# @liquid_public_docs # @liquid_public_docs