Skip to content

Commit 79209b1

Browse files
committed
bench(effect): add the two-argument effect-step macrobenchmark (#265)
Bench.EffectStep drives a hot ST loop through a unary effect action, the case the late uncurry run splits. Bench.EffectStep2 is its two-argument sibling: with two real arguments the spine is already saturated when the early uncurry run measures it, so the split fires at the real arity and the thunk sits inside the worker, out of the late run reach. Every iteration pays the thunk allocation plus the call that forces it, which the committed counter oracles record as two NYI: bytecode FNEW trace aborts and an interpreted driver.
1 parent a5469ea commit 79209b1

5 files changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
chunk: Bench.EffectStep2.lua
2+
runtime: LuaJIT 2.1.1741730670
3+
main-chunk FNEW: 11
4+
function-body FNEW: 12
5+
total FNEW: 23
6+
prototypes: 24
7+
function-body FNEW sites:
8+
Bench.EffectStep2.lua:3
9+
Bench.EffectStep2.lua:5
10+
Bench.EffectStep2.lua:5
11+
Bench.EffectStep2.lua:8
12+
Bench.EffectStep2.lua:9
13+
Bench.EffectStep2.lua:11
14+
Bench.EffectStep2.lua:11
15+
Bench.EffectStep2.lua:29
16+
Bench.EffectStep2.lua:40
17+
Bench.EffectStep2.lua:43
18+
Bench.EffectStep2.lua:49
19+
Bench.EffectStep2.lua:56
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
chunk: Bench.EffectStep2.lua
2+
runtime: LuaJIT 2.1.1741730670
3+
main-chunk TNEW+TDUP: 3
4+
function-body TNEW+TDUP: 1
5+
total TNEW+TDUP: 4
6+
prototypes: 24
7+
function-body TNEW+TDUP sites:
8+
Bench.EffectStep2.lua:8 TDUP
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
spec: effect_step2
2+
runtime: LuaJIT 2.1.1741730670
3+
workload: n=100000 reps=4 result=1500000
4+
aborts (distinct site -- reason):
5+
Bench.EffectStep2.lua:11 -- NYI: bytecode FNEW
6+
Bench.EffectStep2.lua:29 -- NYI: bytecode FNEW
7+
Bench.EffectStep2.lua:40 -- NYI: bytecode FNEW
8+
Bench.EffectStep2.lua:56 -- NYI: bytecode FNEW
9+
Bench.EffectStep2.lua:8 -- NYI: bytecode FNEW
10+
Bench.EffectStep2.lua:9 -- NYI: bytecode FNEW
11+
bytecode end state (J*=compiled, I*=blacklisted):
12+
Bench.EffectStep2.lua:10 IFUNCF
13+
Bench.EffectStep2.lua:11 IFUNCF
14+
Bench.EffectStep2.lua:11 JFUNCF
15+
Bench.EffectStep2.lua:16 IFUNCF
16+
Bench.EffectStep2.lua:17 IFUNCF
17+
Bench.EffectStep2.lua:32 IFUNCF
18+
Bench.EffectStep2.lua:33 IFUNCF
19+
Bench.EffectStep2.lua:8 JFUNCF
20+
Bench.EffectStep2.lua:9 IFUNCF
21+
Bench.EffectStep2.lua:9 JFUNCF
22+
counts: aborts=6 compiled=3 blacklisted=7

bench/macro/effect_step2.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- A hot ST loop whose per-iteration step is a two-argument effect action,
2+
-- always fully applied and immediately run. Two real arguments saturate the
3+
-- spine before magicDo runs, so the uncurry split fires at the real arity and
4+
-- the thunk ends up inside the worker: every iteration allocates that closure
5+
-- and pays a second call to force it. With the thunk parameter absorbed into
6+
-- the worker the iteration is one n-ary call and no allocation.
7+
-- `effect_step.lua` is the unary sibling, which the late uncurry run covers.
8+
return {
9+
artifact = "Bench.EffectStep2",
10+
n = 100000,
11+
drive = function(mod, n)
12+
return mod.run(n)
13+
end,
14+
ideal = function(n)
15+
local acc = 0
16+
for _ = 1, n do acc = acc + 15 end
17+
return acc
18+
end,
19+
}

test/ps/src/Bench/EffectStep2.purs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
-- | A hot ST loop through a *two*-argument effect step that is always
2+
-- | fully applied and immediately run — the case the late uncurry run
3+
-- | (issue #200) cannot reach. With two real arguments the step's spine
4+
-- | is already saturated when the early uncurry run measures it, so the
5+
-- | split fires at the real arity and magicDo then rewrites the worker's
6+
-- | body into a thunk: every iteration allocates that closure and pays
7+
-- | the second call that forces it. Absorbing the thunk parameter into
8+
-- | the worker makes the iteration one n-ary call with no allocation.
9+
-- | `Bench.EffectStep` is the unary sibling, which the late run already
10+
-- | handles.
11+
module Bench.EffectStep2 where
12+
13+
import Prelude
14+
15+
import Control.Monad.ST (ST)
16+
import Control.Monad.ST as ST
17+
import Control.Monad.ST.Ref (STRef)
18+
import Control.Monad.ST.Ref as STRef
19+
20+
-- Two real arguments, so the early run splits it and the thunk ends up
21+
-- inside the worker. The body is deliberately over the call-site inline
22+
-- budget, so the call shape survives to be measured — built from
23+
-- read/write pairs, which unlike @modify@ allocate no per-iteration
24+
-- lambda that would drown the thunk-allocation delta this bench exposes.
25+
step :: forall r. Int -> STRef r Int -> ST r Unit
26+
step k ref = do
27+
a <- STRef.read ref
28+
_ <- STRef.write (a + k) ref
29+
b <- STRef.read ref
30+
_ <- STRef.write (b + k) ref
31+
c <- STRef.read ref
32+
_ <- STRef.write (c + k) ref
33+
d <- STRef.read ref
34+
_ <- STRef.write (d + k) ref
35+
e <- STRef.read ref
36+
_ <- STRef.write (e + k) ref
37+
pure unit
38+
39+
go :: forall r. Int -> STRef r Int -> ST r Int
40+
go i ref = do
41+
step 3 ref
42+
if i <= 1 then STRef.read ref else go (i - 1) ref
43+
44+
run :: Int -> Int
45+
run n = ST.run do
46+
ref <- STRef.new 0
47+
go n ref

0 commit comments

Comments
 (0)