~bigbes/tarantool

tarantool-protobuf

ref: 6e7835a29a3678aa86a49dfff76c55b18d091755 tarantool-protobuf/bench/starwing_bench.lua -rw-r--r-- 7.3 KiB
6e7835a2 — Eugene Blikh c_runtime: encode/decode singular sub-messages (ra6 3d) 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/usr/bin/env tarantool
-- Head-to-head bench: starwing/lua-protobuf vs this repo's pb runtime.
--
-- Mirrors bench/bench.lua's payload shapes, sizes, iteration counts, and
-- measurement loop so the numbers line up column-for-column. Runs ONLY
-- the starwing path; our path stays in bench/bench.lua and we put the
-- two outputs side-by-side.
--
-- We strip our runtime/ off package.path so `require('pb')` resolves to
-- starwing's pb.so (installed under .rocks/lib/tarantool/) — same module
-- name, different code.

-- Resolve `pb` to starwing's C module, not our Lua runtime that's
-- installed as a rock under .rocks/share/tarantool/pb/. We do this by
-- loading the .so directly through package.loadlib and stuffing the
-- result into package.loaded before require('pb') has a chance to hit
-- the Lua-path loader.
local clock = require('clock')
local fio   = require('fio')

local so_path = '.rocks/lib/tarantool/pb.so'
assert(fio.path.exists(so_path), 'starwing pb.so not at ' .. so_path)
local loader = assert(package.loadlib(so_path, 'luaopen_pb'))
package.loaded.pb = loader()
local pb = require('pb')      -- starwing
local io_   = io

-- ---- schema load -----------------------------------------------------------

local function slurp(path)
    local f = assert(fio.open(path, {'O_RDONLY'}))
    local s = f:read()
    f:close()
    return s
end

assert(pb.load(slurp('bench/starwing/hello.pb')),
    'failed to load hello.pb')
assert(pb.load(slurp('bench/starwing/proto2_basic.pb')),
    'failed to load proto2_basic.pb')

-- Match our runtime's default: int64 as cdata (starwing calls this 'int64').
pb.option('int64_as_number')   -- bench payloads stay in number range; avoids
                               -- per-call cdata allocation noise. Same regime
                               -- our bench/bench.lua exercises.

-- ---- payload builders (verbatim from bench/bench.lua) ----------------------

local function build_person_payload(target)
    if target <= 10 then
        return {name = 'bigbes', age = 42}
    end
    if target <= 100 then
        return {
            name  = string.rep('a', target - 10),
            age   = 42,
        }
    end
    local per_email   = 36
    local fixed_bytes = 80
    local n_emails    = math.max(1, math.floor((target - fixed_bytes) / per_email))
    local p = {
        name = 'bigbes',
        age  = 42,
        address = {street = '1 Main St', city = 'Springfield', zip = 12345},
        lucky_numbers = {7, 13, 21, 42, 99},
        emails = {},
    }
    for i = 1, n_emails do
        p.emails[i] = string.rep('e', 28) .. string.format('%04d', i)
    end
    return p
end

local function build_proto2_payload(target)
    if target <= 0 then
        -- starwing accepts extensions inline as fields of the .pb type
        -- (it loads the descriptor set, which includes them). The
        -- registered extension names are short — see registry below.
        return {id = 7, ext_count = 42}
    end
    local n_tags    = 16
    local per_tag   = 30
    local tags      = {}
    for i = 1, n_tags do
        tags[i] = string.rep('t', per_tag - 2) .. string.format('%02d', i)
    end
    local lucky = {}
    for i = 1, 8 do lucky[i] = 1000 + i end
    return {
        id            = 7,
        name          = 'bench',
        retries       = 9,
        lucky_numbers = lucky,
        tags          = tags,
        inner         = {key = string.rep('k', 16), weight = 3},
        Stats         = {latency_ns = 1234567, attempts = 4},
        ext_count     = 99,
        ext_label     = string.rep('x', 32),
    }
end

-- ---- measurement (mirrors bench/bench.lua) ---------------------------------

local PERSON_SIZES = {
    {label = '10B',   target = 10},
    {label = '100B',  target = 100},
    {label = '1KB',   target = 1024},
    {label = '10KB',  target = 10240},
    {label = '100KB', target = 102400},
}
local PROTO2_SIZES = {
    {label = 'min',  target = 0},
    {label = 'mid',  target = 1024},
}

local function iter_count(size_bytes)
    if size_bytes <  100   then return 200000 end
    if size_bytes <  2000  then return  50000 end
    if size_bytes <  20000 then return   5000 end
    return 500
end

local function summarize(samples)
    table.sort(samples)
    local n = #samples
    return {
        median = samples[math.floor((n + 1) / 2)],
        min    = samples[1],
        max    = samples[n],
    }
end

local function time_loop(fn, n)
    local t0 = clock.monotonic64()
    for _ = 1, n do fn() end
    local t1 = clock.monotonic64()
    return tonumber(t1 - t0) / 1e9
end

local function bench_throughput(fn, n, runs)
    for _ = 1, math.min(n, 1000) do fn() end
    local times = {}
    for r = 1, runs do
        collectgarbage('collect')
        times[r] = time_loop(fn, n)
    end
    local s = summarize(times)
    return {
        ns_per_op   = s.median / n * 1e9,
        msgs_per_s  = n / s.median,
    }
end

local function bench_alloc(fn, expected_bytes)
    local budget   = 64 * 1024 * 1024
    local per_iter = math.max(1, expected_bytes) * 2
    local n        = math.max(100, math.min(2000, math.floor(budget / per_iter)))
    for _ = 1, 100 do fn() end
    collectgarbage('collect')
    collectgarbage('stop')
    local before = collectgarbage('count')
    for _ = 1, n do fn() end
    local after = collectgarbage('count')
    collectgarbage('restart')
    collectgarbage('collect')
    return {
        bytes_per_op = (after - before) * 1024 / n,
    }
end

local function bench_one(type_name, payload)
    local encode = function() return pb.encode(type_name, payload) end
    local bytes  = pb.encode(type_name, payload)
    local decode = function() return pb.decode(type_name, bytes) end

    -- warmup decode
    local _ = pb.decode(type_name, bytes)

    local n    = iter_count(#bytes)
    local runs = 5

    local enc = bench_throughput(encode, n, runs)
    local ea  = bench_alloc(encode, #bytes)
    enc.mb_per_s        = #bytes * enc.msgs_per_s / 1e6
    enc.bytes_per_op    = ea.bytes_per_op

    local dec = bench_throughput(decode, n, runs)
    local da  = bench_alloc(decode, #bytes)
    dec.mb_per_s        = #bytes * dec.msgs_per_s / 1e6
    dec.bytes_per_op    = da.bytes_per_op

    return {size_bytes = #bytes, encode = enc, decode = dec}
end

-- ---- run -------------------------------------------------------------------

io_.stderr:write('starwing/lua-protobuf bench\n')
io_.stderr:write(string.format('  version: pb 0.5.3  (tarantool %s)\n', _TARANTOOL))

io_.write('\n=== hello.Person ===\n')
io_.write(string.format('%-7s  %12s  %12s  %12s  %12s\n',
    'size', 'enc MB/s', 'enc B/op', 'dec MB/s', 'dec B/op'))
for _, sz in ipairs(PERSON_SIZES) do
    local p = build_person_payload(sz.target)
    local r = bench_one('hello.Person', p)
    io_.write(string.format('%-7s  %12.2f  %12.1f  %12.2f  %12.1f\n',
        sz.label, r.encode.mb_per_s, r.encode.bytes_per_op,
        r.decode.mb_per_s, r.decode.bytes_per_op))
end

io_.write('\n=== proto2_basic.BenchPayload ===\n')
io_.write(string.format('%-7s  %12s  %12s  %12s  %12s\n',
    'size', 'enc MB/s', 'enc B/op', 'dec MB/s', 'dec B/op'))
for _, sz in ipairs(PROTO2_SIZES) do
    local p = build_proto2_payload(sz.target)
    local r = bench_one('proto2_basic.BenchPayload', p)
    io_.write(string.format('%-7s  %12.2f  %12.1f  %12.2f  %12.1f\n',
        sz.label, r.encode.mb_per_s, r.encode.bytes_per_op,
        r.decode.mb_per_s, r.decode.bytes_per_op))
end

os.exit(0)