Skip to content

Commit bfbd05a

Browse files
byrootpetergoldstein
authored andcommitted
Add string_fastpath option to save on serializing simple strings
Going through `Marshal.load/dump` (or other serializers) when the value is already a string is quite costly for not a whole lot of value. The only real benefit is that it automatically preserve the string encoding, but we can assume the overwhelming majority of strings are either UTF-8 or BINARY, so we can encode that in the bitflags. For strings that have a different encoding, or objects that inherit from `String` we can continue to use the serializer. Note that only the `#store` path checks for the option. `#retrieve` always handle this new format. This is so that you can seemlessly upgrade without having to flush your caches. You can first upgrade dalli and then later turn on the option. It would be nice to make this option a default at some point though.
1 parent a512c69 commit bfbd05a

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

lib/dalli/protocol/value_serializer.rb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ValueSerializer
1818
# https://www.hjp.at/zettel/m/memcached_flags.rxml
1919
# Looks like most clients use bit 0 to indicate native language serialization
2020
FLAG_SERIALIZED = 0x1
21+
FLAG_UTF8 = 0x2
2122

2223
attr_accessor :serialization_options
2324

@@ -27,10 +28,24 @@ def initialize(protocol_options)
2728
end
2829

2930
def store(value, req_options, bitflags)
30-
do_serialize = !(req_options && req_options[:raw])
31-
store_value = do_serialize ? serialize_value(value) : value.to_s
32-
bitflags |= FLAG_SERIALIZED if do_serialize
33-
[store_value, bitflags]
31+
if req_options
32+
return [value.to_s, bitflags] if req_options[:raw]
33+
34+
# If the value is a simple string, going through serialization is costly
35+
# for no benefit other than preserving encoding.
36+
# Assuming most strings are either UTF-8 or BINARY we can just store
37+
# that information in the bitflags.
38+
if req_options[:string_fastpath] && value.instance_of?(String)
39+
case value.encoding
40+
when Encoding::BINARY
41+
return [value, bitflags]
42+
when Encoding::UTF_8
43+
return [value, bitflags | FLAG_UTF8]
44+
end
45+
end
46+
end
47+
48+
[serialize_value(value), bitflags | FLAG_SERIALIZED]
3449
end
3550

3651
def retrieve(value, bitflags)
@@ -41,6 +56,8 @@ def retrieve(value, bitflags)
4156
rescue StandardError
4257
raise UnmarshalError, 'Unable to unmarshal value'
4358
end
59+
elsif (bitflags & FLAG_UTF8) != 0
60+
value.force_encoding(Encoding::UTF_8)
4461
else
4562
value
4663
end

test/integration/test_serializer.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,40 @@
1414
end
1515
end
1616

17+
it 'skips the serializer for simple strings when string_fastpath is enabled' do
18+
memcached(p, 29_198) do |_dc, port|
19+
memcache = Dalli::Client.new("127.0.0.1:#{port}", string_fastpath: true)
20+
string = 'héllø'
21+
memcache.set 'utf-8', string
22+
23+
assert_equal string, memcache.get('utf-8')
24+
assert_equal Encoding::UTF_8, memcache.get('utf-8').encoding
25+
26+
binary = "\0\xff".b
27+
memcache.set 'binary', binary
28+
29+
assert_equal binary, memcache.get('binary')
30+
assert_equal Encoding::BINARY, memcache.get('binary').encoding
31+
32+
latin1 = string.encode(Encoding::ISO_8859_1)
33+
memcache.set 'latin1', latin1
34+
35+
assert_equal latin1, memcache.get('latin1')
36+
assert_equal Encoding::ISO_8859_1, memcache.get('latin1').encoding
37+
38+
# Ensure strings that went through the fastpath are properly retreived
39+
# by clients without string_fastpath enabled.
40+
memcache = Dalli::Client.new("127.0.0.1:#{port}", string_fastpath: false)
41+
42+
assert_equal string, memcache.get('utf-8')
43+
assert_equal Encoding::UTF_8, memcache.get('utf-8').encoding
44+
assert_equal binary, memcache.get('binary')
45+
assert_equal Encoding::BINARY, memcache.get('binary').encoding
46+
assert_equal latin1, memcache.get('latin1')
47+
assert_equal Encoding::ISO_8859_1, memcache.get('latin1').encoding
48+
end
49+
end
50+
1751
it 'supports a custom serializer' do
1852
memcached(p, 29_198) do |_dc, port|
1953
memcache = Dalli::Client.new("127.0.0.1:#{port}", serializer: JSON)

0 commit comments

Comments
 (0)