From f08fe63042b1c28fd5e289e73e780c2e9b504e89 Mon Sep 17 00:00:00 2001 From: Tobi Lutke Date: Wed, 11 Mar 2026 10:20:11 -0400 Subject: [PATCH] =?UTF-8?q?Replace=20split+join=20in=20truncatewords=20wit?= =?UTF-8?q?h=20manual=20word=20scan=20=E2=80=94=20avoids=20array=20+=20str?= =?UTF-8?q?ing=20allocations\n\nResult:=20{"status":"keep","combined=5F?= =?UTF-8?q?=C2=B5s":4280,"parse=5F=C2=B5s":3009,"render=5F=C2=B5s":1271,"a?= =?UTF-8?q?llocations":26395}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/liquid/standardfilters.rb | 58 ++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index ed614156..4cbdc8ed 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -275,18 +275,54 @@ module Liquid words = Utils.to_integer(words) words = 1 if words <= 0 - wordlist = begin - 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 + return input if words + 1 > MAX_I32 - wordlist.pop - truncate_string = Utils.to_s(truncate_string) - wordlist.join(" ").concat(truncate_string) + # Build result incrementally — avoids split() array + string allocations + len = input.bytesize + 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 # @liquid_public_docs