pico-8 cartridge // http://www.pico-8.com version 16 __lua__ -- amico8 benchmark v1.1 -- stat(1) = cpu fraction 0..1 (1 = full frame at 60fps), LIVE within a frame -- timing uses stat(1) deltas around each test loop. -- (note: stat(100) is the load() breadcrumb on amico8/pico-8, NOT a timer - -- v1 used it as a cia timer, which broke once stat(100) became the breadcrumb.) -- press x/z to pause/resume, up/dn to scroll local tests = {} local results = {} local cur = 0 local phase = 0 -- 0=run, 1=done local scroll = 0 -- test data local ta = {} local x1,x2,x3 = 1.5, 2.3, -0.7 local ents = {} local ecls = {} -- how many iterations each test runs -- chosen so slow machines take ~20-80% of frame per test local n = { 500, -- 1 empty loop (baseline) 500, -- 2 flr 500, -- 3 abs 500, -- 4 min 500, -- 5 max 500, -- 6 mid 500, -- 7 sin 500, -- 8 cos 500, -- 9 sqrt 500, -- 10 x+y 500, -- 11 x*y 500, -- 12 x/y 50, -- 13 all(t) 32 items 50, -- 14 pairs(t) 32 items 100, -- 15 spr(0,x,y) 5, -- 16 rectfill(0,0,127,127) 20, -- 17 oop e:update() x20 ents 1, -- 18 map(0,0,0,0,16,16) 500, -- 19 lerp(a,b,t) call 500, -- 20 clerp(a,b,t) call (with clamp) } local tname = { "empty x500", "flr x500", "abs x500", "min x500", "max x500", "mid x500", "sin x500", "cos x500", "sqrt x500", "x+y x500", "x*y x500", "x/y x500", "all(t)x50 ", "pairs x50 ", "spr x100", "rfill x5 ", "oop x20 ", "map16x16 ", "lerp x500", "clerp x500", } -- small function that AOT can compile (only ADD/SUB/MUL/RETURN) local function lerp(a,b,t) return a+(b-a)*t end -- slightly bigger: clamp + lerp local function clerp(a,b,t) if t<0 then t=0 end if t>1 then t=1 end return a+(b-a)*t end local ntests = #tname function _init() -- fill test array for i=1,32 do add(ta, i*0.1+0.05) end -- oop class + instances ecls = { update=function(self) self.x = self.x + self.vx if self.x > 120 then self.x = 0 end self.t = self.t + 1 end } ecls.__index = ecls for i=1,20 do add(ents, setmetatable( {x=i*6,y=60,vx=0.5,t=0}, ecls)) end -- draw a minimal sprite in slot 0 (solid 2x2 block) -- uses memset to poke sprite data into ram directly -- sprite 0 = address 0x0000, 8x8 nibble-packed = 32 bytes for i=0,31 do poke(i, 0x55) end -- color 5, all pixels cur = 1 phase = 0 end -- run one test, return us/iter + cpu fraction function run_test(ti) local t0 = stat(1) -- cpu fraction used so far this frame (live, counts UP) local ni = n[ti] local r = 0 if ti==1 then for i=1,ni do r=r+1 end elseif ti==2 then for i=1,ni do r=flr(x1) end elseif ti==3 then for i=1,ni do r=abs(x1) end elseif ti==4 then for i=1,ni do r=min(x1,x2) end elseif ti==5 then for i=1,ni do r=max(x1,x2) end elseif ti==6 then for i=1,ni do r=mid(x1,x2,x3) end elseif ti==7 then for i=1,ni do r=sin(x1) end elseif ti==8 then for i=1,ni do r=cos(x1) end elseif ti==9 then for i=1,ni do r=sqrt(x1) end elseif ti==10 then for i=1,ni do r=x1+x2 end elseif ti==11 then for i=1,ni do r=x1*x2 end elseif ti==12 then for i=1,ni do r=x1/x2 end elseif ti==13 then for i=1,ni do for v in all(ta) do r=v end end elseif ti==14 then for i=1,ni do for k,v in pairs(ta) do r=v end end elseif ti==15 then for i=1,ni do spr(0, (i%16)*8, flr(i/16)*8) end elseif ti==16 then for i=1,ni do rectfill(0,0,127,127, i%15+1) end elseif ti==17 then for i=1,ni do for e in all(ents) do e:update() end end elseif ti==18 then for i=1,ni do map(0,0,0,0,16,16) end elseif ti==19 then for i=1,ni do r=lerp(x1,x2,0.5) end elseif ti==20 then for i=1,ni do r=clerp(x1,x2,0.5) end end -- cpu fraction consumed by the test loop (stat(1) counts UP, 1.0 = full 60fps frame) local t1 = stat(1) local dt = t1 - t0 if dt < 0 then dt = 0 end -- guard (shouldn't happen inside one _update) if dt > 1.9 then dt = 1.9 end -- clamp so dt*16667 stays under fix32 max (32767) -- us/iter = frac-of-frame * frame-budget(16667us at 60fps) / iterations local us = dt * 16667 / ni return us, dt -- us/iter, cpu fraction (of this test loop) end local paused = false function _update60() if btn(4) then paused = not paused end if btn(2) then scroll = max(0, scroll-1) end if btn(3) then scroll = min(ntests-8, scroll+1) end if paused or phase==1 then return end if cur <= ntests then local us, cpu = run_test(cur) results[cur] = {us=us, cpu=cpu, name=tname[cur]} cur = cur + 1 else phase = 1 end end function _draw() cls(1) -- title print("amico8 benchmark", 2, 2, 7) local st = phase==1 and "done" or ("test "..cur.."/"..ntests) print(st, 90, 2, 6) -- progress bar local pct = (cur-1) / ntests rectfill(0, 9, flr(127*pct), 10, 11) rectfill(flr(127*pct)+1, 9, 127, 10, 5) -- column headers print("test us/it cpu%", 0, 13, 6) line(0, 20, 127, 20, 5) -- results local row = 0 for i=1,ntests do if i >= scroll+1 and row < 10 then local y = 22 + row*10 local r = results[i] if r then -- color: green<50% frame, yellow<90%, red>90% local col = r.cpu < 0.5 and 11 or (r.cpu < 0.9 and 10 or 8) print(r.name, 0, y, 7) -- us/iter local us_s = tostr(r.us) print(us_s, 80, y, col) -- cpu% local cpu_pct = flr(r.cpu * 100) print(cpu_pct.."%", 108, y, col) else local col = i==cur and 7 or 5 print(tname[i], 0, y, col) print("...", 80, y, 5) end row = row + 1 end end -- footer line(0, 118, 127, 118, 5) if phase==1 then print("z=restart up/dn=scroll", 0, 121, 6) else print("running... up/dn=scroll", 0, 121, 5) end end