~bigbes/tarantool

tarantool-protobuf

ref: ee07048d3ab9678553493c643fb74328e7d5e908 tarantool-protobuf/bench/alloc_probe.lua -rw-r--r-- 6.0 KiB
ee07048d — Eugene Blikh beads: close 74c 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env tarantool
-- Surgical alloc probe. Strips the encode path apart, measures KB-delta
-- per primitive operation, so we can attribute the ~136 B/op encode floor
-- to a specific source (table, varint string, result string, ...).
--
-- All cases run with GC stopped so allocations accumulate. We divide by
-- N to get bytes/op. To eliminate string-interning noise we use *unique*
-- input bytes per iteration where appropriate (suffix counter).

package.path = './runtime/?.lua;./runtime/?/init.lua;'
    .. './examples/expected/?.lua;./examples/expected/?/init.lua;'
    .. package.path

local wire = require('pb.wire')
local hello = require('full.hello.hello_pb')

local N = 50000

local function alloc_per_op(label, fn, mk_arg)
    -- Warmup: let the JIT compile and any one-shot allocations settle.
    for i = 1, 1000 do fn(mk_arg and mk_arg(i) or nil) end
    collectgarbage('collect')
    collectgarbage('stop')
    local before = collectgarbage('count')
    for i = 1, N do fn(mk_arg and mk_arg(i) or nil) end
    local after = collectgarbage('count')
    collectgarbage('restart')
    collectgarbage('collect')
    local per_op = (after - before) * 1024 / N
    print(string.format('  %-50s %8.1f B/op', label, per_op))
end

print('=== Baseline: noop ===')
alloc_per_op('empty function', function() end)
alloc_per_op('return nil', function() return nil end)

print('\n=== Table allocation ===')
alloc_per_op('local t = {}', function() local t = {} end)
alloc_per_op('local t, n = {}, 0', function() local t, n = {}, 0 end)
alloc_per_op('local t = {}; t[1]=1; t[2]=2; t[3]=3', function()
    local t = {}; t[1] = 1; t[2] = 2; t[3] = 3
end)
alloc_per_op('local t = {}; for i=1,5 do t[i]=i end', function()
    local t = {}; for i = 1, 5 do t[i] = i end
end)
alloc_per_op('local t = {}; for i=1,10 do t[i]=i end', function()
    local t = {}; for i = 1, 10 do t[i] = i end
end)

print('\n=== wire.encode_varint (interned: same input) ===')
alloc_per_op('encode_varint(42)         [1-byte fast]', function()
    local s = wire.encode_varint(42)
end)
alloc_per_op('encode_varint(200)        [2-byte slow]', function()
    local s = wire.encode_varint(200)
end)
alloc_per_op('encode_varint(1e6)        [3-byte slow]', function()
    local s = wire.encode_varint(1000000)
end)

print('\n=== wire.encode_varint (unique per iter) ===')
alloc_per_op('encode_varint(i % 128)    [1-byte, unique-ish]', function(i)
    local s = wire.encode_varint(i % 128)
end, function(i) return i end)
alloc_per_op('encode_varint(128 + i)    [2-byte, unique]', function(i)
    local s = wire.encode_varint(128 + i)
end, function(i) return i end)

print('\n=== string.char (intern check) ===')
alloc_per_op('string.char(42)           [1-byte, same]', function()
    local s = string.char(42)
end)
alloc_per_op('string.char(i % 256)      [1-byte, varying]', function(i)
    local s = string.char(i % 256)
end, function(i) return i end)

print('\n=== table.concat ===')
alloc_per_op('concat of 3 short literal strings', function()
    local s = table.concat({"\x0a", "\x06", "bigbes"})
end)
alloc_per_op('concat of 5 short literal strings', function()
    local s = table.concat({"\x0a", "\x06", "bigbes", "\x10", "\x2a"})
end)

print('\n=== Full encode (Person fixtures) ===')
local p10 = {name = 'bigbes', age = 42}
alloc_per_op('Person_encode 10B (same input each iter)', function()
    local s = hello.Person_encode(p10)
end)

-- Unique per iter via the lucky_numbers field (varying).
alloc_per_op('Person_encode 10B (varying age field)', function(i)
    p10.age = 42 + (i % 100)
    local s = hello.Person_encode(p10)
end, function(i) return i end)

local p100 = {name = string.rep('a', 90), age = 42}
alloc_per_op('Person_encode 100B (same input)', function()
    local s = hello.Person_encode(p100)
end)

-- Decode floor probe.
local p10_bytes = hello.Person_encode(p10)
local p100_bytes = hello.Person_encode(p100)
print('\n=== Full decode (Person fixtures) ===')
alloc_per_op('Person_decode 10B (same input)', function()
    local t = hello.Person_decode(p10_bytes)
end)
alloc_per_op('Person_decode 100B (same input)', function()
    local t = hello.Person_decode(p100_bytes)
end)

print('\n=== Drill-down: encode pieces (10B Person) ===')
-- Imitate Person_encode body manually so we can attribute each step.
alloc_per_op('table {} + n', function()
    local out, n = {}, 0
end)
alloc_per_op('+ append tag literal', function()
    local out, n = {}, 0
    n = n + 1; out[n] = "\x0a"
end)
alloc_per_op('+ append name varlen + name string', function()
    local out, n = {}, 0
    local v = 'bigbes'
    n = n + 1; out[n] = "\x0a"
    n = n + 1; out[n] = wire.encode_varint(#v)
    n = n + 1; out[n] = v
end)
alloc_per_op('+ append age tag + varint', function()
    local out, n = {}, 0
    local v = 'bigbes'
    n = n + 1; out[n] = "\x0a"
    n = n + 1; out[n] = wire.encode_varint(#v)
    n = n + 1; out[n] = v
    n = n + 1; out[n] = "\x10"
    n = n + 1; out[n] = wire.encode_varint(42)
end)
alloc_per_op('+ final table.concat (full path)', function()
    local out, n = {}, 0
    local v = 'bigbes'
    n = n + 1; out[n] = "\x0a"
    n = n + 1; out[n] = wire.encode_varint(#v)
    n = n + 1; out[n] = v
    n = n + 1; out[n] = "\x10"
    n = n + 1; out[n] = wire.encode_varint(42)
    local r = table.concat(out)
end)

print('\n=== Large payload encode (unique output to defeat interning) ===')
-- Build a 1KB+ Person; vary an integer field so the encoded output is
-- unique per iter and the result string can't be interned.
local p1k = {
    name = 'bigbes', age = 42,
    address = {street = '1 Main St', city = 'Springfield', zip = 12345},
    lucky_numbers = {7, 13, 21, 42, 99},
    emails = {},
}
for i = 1, 26 do p1k.emails[i] = string.rep('e', 28) .. string.format('%04d', i) end
print(string.format('  1KB Person encoded size = %d bytes', #hello.Person_encode(p1k)))

alloc_per_op('Person_encode 1KB (same input — interned)', function()
    local s = hello.Person_encode(p1k)
end)
alloc_per_op('Person_encode 1KB (varying age — NOT interned)', function(i)
    p1k.age = 42 + i
    local s = hello.Person_encode(p1k)
end, function(i) return i end)