Skip to content

Commit f18082a

Browse files
committed
(perf) introduce perf test harness and refactor (#54)
Made a lot of backwards-compatible changes and simplifications to the code that I had wanted to do for a lot of time, while also keeping an eye in performance. The new changes improved overall performance on my limited, dev-machine-only, luajit-only tests. Before: ``` $ luajit perf.lua nil,string,empty,sequence,record,hybrid,recursive,meta,process,complex,big 0.054246,0.140177,0.149759,0.323062,0.441312,0.607064,0.296031,0.458631,0.105668,1.717528,2.047272 ``` After: ``` $ luajit perf.lua nil,string,empty,sequence,record,hybrid,recursive,meta,process,complex,big 0.036804,0.188984,0.099349,0.247425,0.342079,0.452339,0.203801,0.288931,0.169462,1.010763,1.616462 ```
1 parent 2d842fb commit f18082a

File tree

4 files changed

+340
-295
lines changed

4 files changed

+340
-295
lines changed

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.PHONY: all dev gen check test perf
2+
3+
LUA := $(shell luarocks config lua_interpreter)
4+
15
all: gen check test
26

37
dev:
@@ -14,5 +18,8 @@ check:
1418
test:
1519
busted
1620

21+
perf:
22+
$(shell luarocks config lua_interpreter) perf.lua
23+
1724

1825

inspect.lua

Lines changed: 107 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' en
4848
inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end })
4949

5050
local tostring = tostring
51+
local rep = string.rep
52+
local match = string.match
53+
local char = string.char
54+
local gsub = string.gsub
55+
local fmt = string.format
5156

5257
local function rawpairs(t)
5358
return next, t, nil
@@ -56,10 +61,10 @@ end
5661

5762

5863
local function smartQuote(str)
59-
if str:match('"') and not str:match("'") then
64+
if match(str, '"') and not match(str, "'") then
6065
return "'" .. str .. "'"
6166
end
62-
return '"' .. str:gsub('"', '\\"') .. '"'
67+
return '"' .. gsub(str, '"', '\\"') .. '"'
6368
end
6469

6570

@@ -69,17 +74,17 @@ local shortControlCharEscapes = {
6974
}
7075
local longControlCharEscapes = { ["\127"] = "\127" }
7176
for i = 0, 31 do
72-
local ch = string.char(i)
77+
local ch = char(i)
7378
if not shortControlCharEscapes[ch] then
7479
shortControlCharEscapes[ch] = "\\" .. i
75-
longControlCharEscapes[ch] = string.format("\\%03d", i)
80+
longControlCharEscapes[ch] = fmt("\\%03d", i)
7681
end
7782
end
7883

7984
local function escape(str)
80-
return (str:gsub("\\", "\\\\"):
81-
gsub("(%c)%f[0-9]", longControlCharEscapes):
82-
gsub("%c", shortControlCharEscapes))
85+
return (gsub(gsub(gsub(str, "\\", "\\\\"),
86+
"(%c)%f[0-9]", longControlCharEscapes),
87+
"%c", shortControlCharEscapes))
8388
end
8489

8590
local function isIdentifier(str)
@@ -107,59 +112,45 @@ local function sortKeys(a, b)
107112
return (a) < (b)
108113
end
109114

110-
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
111-
112-
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
113-
elseif dta then return true
114-
elseif dtb then return false
115-
end
115+
local dta = defaultTypeOrders[ta] or 100
116+
local dtb = defaultTypeOrders[tb] or 100
116117

117118

118-
return ta < tb
119+
return dta == dtb and ta < tb or dta < dtb
119120
end
120121

122+
local function getKeys(t)
121123

122-
123-
local function getSequenceLength(t)
124-
local len = 1
125-
local v = rawget(t, len)
126-
while v ~= nil do
127-
len = len + 1
128-
v = rawget(t, len)
124+
local seqLen = 1
125+
while rawget(t, seqLen) ~= nil do
126+
seqLen = seqLen + 1
129127
end
130-
return len - 1
131-
end
128+
seqLen = seqLen - 1
132129

133-
local function getNonSequentialKeys(t)
134-
local keys, keysLength = {}, 0
135-
local sequenceLength = getSequenceLength(t)
136-
for k, _ in rawpairs(t) do
137-
if not isSequenceKey(k, sequenceLength) then
138-
keysLength = keysLength + 1
139-
keys[keysLength] = k
130+
local keys, keysLen = {}, 0
131+
for k in rawpairs(t) do
132+
if not isSequenceKey(k, seqLen) then
133+
keysLen = keysLen + 1
134+
keys[keysLen] = k
140135
end
141136
end
142137
table.sort(keys, sortKeys)
143-
return keys, keysLength, sequenceLength
138+
return keys, keysLen, seqLen
144139
end
145140

146-
local function countTableAppearances(t, tableAppearances)
147-
tableAppearances = tableAppearances or {}
148-
149-
if type(t) == "table" then
150-
if not tableAppearances[t] then
151-
tableAppearances[t] = 1
152-
for k, v in rawpairs(t) do
153-
countTableAppearances(k, tableAppearances)
154-
countTableAppearances(v, tableAppearances)
155-
end
156-
countTableAppearances(getmetatable(t), tableAppearances)
141+
local function countCycles(x, cycles)
142+
if type(x) == "table" then
143+
if cycles[x] then
144+
cycles[x] = cycles[x] + 1
157145
else
158-
tableAppearances[t] = tableAppearances[t] + 1
146+
cycles[x] = 1
147+
for k, v in rawpairs(x) do
148+
countCycles(k, cycles)
149+
countCycles(v, cycles)
150+
end
151+
countCycles(getmetatable(x), cycles)
159152
end
160153
end
161-
162-
return tableAppearances
163154
end
164155

165156
local function makePath(path, a, b)
@@ -202,7 +193,10 @@ local function processRecursive(process,
202193
return processed
203194
end
204195

205-
196+
local function puts(buf, str)
197+
buf.n = buf.n + 1
198+
buf[buf.n] = str
199+
end
206200

207201

208202

@@ -219,118 +213,85 @@ local Inspector = {}
219213

220214
local Inspector_mt = { __index = Inspector }
221215

222-
function Inspector:puts(a, b, c, d, e)
223-
local buffer = self.buffer
224-
local len = #buffer
225-
buffer[len + 1] = a
226-
buffer[len + 2] = b
227-
buffer[len + 3] = c
228-
buffer[len + 4] = d
229-
buffer[len + 5] = e
230-
end
231-
232-
function Inspector:down(f)
233-
self.level = self.level + 1
234-
f()
235-
self.level = self.level - 1
236-
end
237-
238-
function Inspector:tabify()
239-
self:puts(self.newline,
240-
string.rep(self.indent, self.level))
241-
end
242-
243-
function Inspector:alreadyVisited(v)
244-
return self.ids[v] ~= nil
216+
local function tabify(inspector)
217+
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
245218
end
246219

247220
function Inspector:getId(v)
248221
local id = self.ids[v]
222+
local ids = self.ids
249223
if not id then
250224
local tv = type(v)
251-
id = (self.maxIds[tv] or 0) + 1
252-
self.maxIds[tv] = id
253-
self.ids[v] = id
225+
id = (ids[tv] or 0) + 1
226+
ids[v], ids[tv] = id, id
254227
end
255228
return tostring(id)
256229
end
257230

258-
259-
function Inspector:putValue(_)
260-
end
261-
262-
function Inspector:putKey(k)
263-
if isIdentifier(k) then
264-
self:puts(k)
265-
return
266-
end
267-
self:puts("[")
268-
self:putValue(k)
269-
self:puts("]")
270-
end
271-
272-
function Inspector:putTable(t)
273-
if t == inspect.KEY or t == inspect.METATABLE then
274-
self:puts(tostring(t))
275-
elseif self:alreadyVisited(t) then
276-
self:puts('<table ', self:getId(t), '>')
277-
elseif self.level >= self.depth then
278-
self:puts('{...}')
279-
else
280-
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
281-
282-
local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
283-
local mt = getmetatable(t)
284-
285-
self:puts('{')
286-
self:down(function()
287-
local count = 0
288-
for i = 1, sequenceLength do
289-
if count > 0 then self:puts(',') end
290-
self:puts(' ')
291-
self:putValue(t[i])
292-
count = count + 1
293-
end
294-
295-
for i = 1, nonSequentialKeysLength do
296-
local k = nonSequentialKeys[i]
297-
if count > 0 then self:puts(',') end
298-
self:tabify()
299-
self:putKey(k)
300-
self:puts(' = ')
301-
self:putValue(t[k])
302-
count = count + 1
231+
function Inspector:putValue(v)
232+
local buf = self.buf
233+
local tv = type(v)
234+
if tv == 'string' then
235+
puts(buf, smartQuote(escape(v)))
236+
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
237+
tv == 'cdata' or tv == 'ctype' then
238+
puts(buf, tostring(v))
239+
elseif tv == 'table' and not self.ids[v] then
240+
local t = v
241+
242+
if t == inspect.KEY or t == inspect.METATABLE then
243+
puts(buf, tostring(t))
244+
elseif self.level >= self.depth then
245+
puts(buf, '{...}')
246+
else
247+
if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end
248+
249+
local keys, keysLen, seqLen = getKeys(t)
250+
251+
puts(buf, '{')
252+
self.level = self.level + 1
253+
254+
for i = 1, seqLen + keysLen do
255+
if i > 1 then puts(buf, ',') end
256+
if i <= seqLen then
257+
puts(buf, ' ')
258+
self:putValue(t[i])
259+
else
260+
local k = keys[i - seqLen]
261+
tabify(self)
262+
if isIdentifier(k) then
263+
puts(buf, k)
264+
else
265+
puts(buf, "[")
266+
self:putValue(k)
267+
puts(buf, "]")
268+
end
269+
puts(buf, ' = ')
270+
self:putValue(t[k])
271+
end
303272
end
304273

274+
local mt = getmetatable(t)
305275
if type(mt) == 'table' then
306-
if count > 0 then self:puts(',') end
307-
self:tabify()
308-
self:puts('<metatable> = ')
276+
if seqLen + keysLen > 0 then puts(buf, ',') end
277+
tabify(self)
278+
puts(buf, '<metatable> = ')
309279
self:putValue(mt)
310280
end
311-
end)
312281

313-
if nonSequentialKeysLength > 0 or type(mt) == 'table' then
314-
self:tabify()
315-
elseif sequenceLength > 0 then
316-
self:puts(' ')
317-
end
282+
self.level = self.level - 1
318283

319-
self:puts('}')
320-
end
321-
end
284+
if keysLen > 0 or type(mt) == 'table' then
285+
tabify(self)
286+
elseif seqLen > 0 then
287+
puts(buf, ' ')
288+
end
289+
290+
puts(buf, '}')
291+
end
322292

323-
function Inspector:putValue(v)
324-
local tv = type(v)
325-
if tv == 'string' then
326-
self:puts(smartQuote(escape(v)))
327-
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
328-
tv == 'cdata' or tv == 'ctype' then
329-
self:puts(tostring(v))
330-
elseif tv == 'table' then
331-
self:putTable(v)
332293
else
333-
self:puts('<', tv, ' ', self:getId(v), '>')
294+
puts(buf, fmt('<%s %d>', tv, self:getId(v)))
334295
end
335296
end
336297

@@ -349,20 +310,22 @@ function inspect.inspect(root, options)
349310
root = processRecursive(process, root, {}, {})
350311
end
351312

313+
local cycles = {}
314+
countCycles(root, cycles)
315+
352316
local inspector = setmetatable({
317+
buf = { n = 0 },
318+
ids = {},
319+
cycles = cycles,
353320
depth = depth,
354321
level = 0,
355-
buffer = {},
356-
ids = {},
357-
maxIds = {},
358322
newline = newline,
359323
indent = indent,
360-
tableAppearances = countTableAppearances(root),
361324
}, Inspector_mt)
362325

363326
inspector:putValue(root)
364327

365-
return table.concat(inspector.buffer)
328+
return table.concat(inspector.buf)
366329
end
367330

368331
setmetatable(inspect, {

0 commit comments

Comments
 (0)