Skip to content

Commit 2fcb4af

Browse files
committed
Optimize Font::CMap#read_codes to lower memory usage
The current code use string.each_byte which returns an Enumerator. It then uses calls to enum.next for getting the next byte. It turns out that this is quite wasteful in terms of memory usage as each call to #next seems to allocate (don't really know why) an Enumerator, an Array, a Proc and a Fiber. By using manual iteration all these allocations are avoided, leading to quite noticeable lower memory usage (sample script that decodes text of a PDF): Before: Initial memory: 17.26MB, Memory maps: 179 Processing 306 pages... <SNIP/> Final memory: 193.55MB (total growth: +176.29MB) Final memory maps: 14485 (total growth: +14306) After: Initial memory: 17.31MB, Memory maps: 177 Processing 306 pages... <SNIP/> Final memory: 73.47MB (total growth: +56.16MB) Final memory maps: 200 (total growth: +23)
1 parent ac073f2 commit 2fcb4af

2 files changed

Lines changed: 12 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
### Changed
99

10+
* Optimized decoding character codes with a CMap to drastically lower memory
11+
usage
1012
* CLI command `hexapdf inspect rev` to show whether the cross-reference table
1113
was reconstructed
1214

lib/hexapdf/font/cmap.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,24 @@ def add_codespace_range(first, *rest)
143143
# An error is raised if the string contains invalid bytes.
144144
def read_codes(string)
145145
codes = []
146-
bytes = string.each_byte
146+
bytes = string.bytes
147+
length = bytes.length
148+
i = 0
147149

148-
loop do
149-
byte = bytes.next
150+
while i < length
151+
byte = bytes[i]
152+
i += 1
150153
code = 0
151154

152155
found = @codespace_ranges.any? do |first_byte_range, rest_ranges|
153156
next unless first_byte_range.cover?(byte)
154157

155158
code = (code << 8) + byte
156159
valid = rest_ranges.all? do |range|
157-
begin
158-
byte = bytes.next
159-
rescue StopIteration
160+
if i < length
161+
byte = bytes[i]
162+
i += 1
163+
else
160164
raise HexaPDF::Error, "Missing bytes while reading codes via CMap"
161165
end
162166
code = (code << 8) + byte

0 commit comments

Comments
 (0)