1 # performance/drop_in_bang: a `!` call of a definition that returns Unit: Base's Image.drop, Image.free 2 # or Image.sink, or a definition of the program declared `-> Unit`. A pure definition that returns Unit 3 # does nothing but free what it receives, and a GPU frees slower than its CPU. 4 # Heuristics: measured on a discrete GPU (CUDA), 30 frames that each dropped a 1M-leaf image with 5 # `Image.drop!` took 1.55 s, against 0.10 s with `Image.drop` (0.104 s against 0.094 s with `--gpu off`). 6 # "bend guide shaders" measured freeing Bend's slash boss scene in a bang at 12.9 ms a frame, against 8.0 7 # ms, on an Apple M-series GPU, whose memory is unified: the GPU loses on both kinds of memory. The JS 8 # lane has no GPU. A return type written another way (an alias of Unit) is not known. 9 import Base 10 import bend-lint-api@0.2.0.0/linter/lint.bend as Lint 11 import bend-lint-api@0.2.0.0/shared/names.bend as Names 12 import ./gpu_fit.bend as GpuFit 13 14 # Types 15 # ===== 16 17 # What judging a file's calls reads: the program's definitions, by the name the check gives them, and where the file 18 # writes `!(`. 19 type Program is Data: 20 Program{defs: Map<&2, Maybe<&2, Lint.Def>>, bangs: List<&2, U32>} 21 22 # Functions 23 # ========= 24 def message(callee: String) -> String: 25 callee 26 ++ "! only frees what it receives, as it returns Unit, and a GPU frees slower than its CPU. GPU: measured 15x slower than without the `!` on a discrete GPU (CUDA), dropping a 1M-leaf image; with unified memory (Apple M-series), freeing a scene in a bang made a frame 12.9 ms, against 8.0 ms (\"bend guide shaders\"). CPU (`--gpu off`): 10% slower. JS: no GPU, so no change. Confidence: high, as a GPU does not speed up freeing. Remove the `!`; to drop the last frame, let the next frame's fork tree drop it, a quadrant per tile (\"bend guide shaders\"). If a timing shows the `!` faster here, expect this finding." 27 28 # The finding about an application below `path`, if it is a `!` call that only frees. 29 def visit( 30 +program: Program, 31 +path: List<&2, Lint.Above>, 32 +node: Lint.Node 33 ) -> List<&2, Lint.Finding>: 34 match program node: 35 case Program{ 36 defs, 37 bangs 38 } Lint.Node{ 39 Lint.Application{}, 40 n, 41 span, 42 Con{Lint.Node{Lint.Reference{+t}, callee, Some{Lint.Span{p, beg, end}}, k, f}, args}, 43 facts 44 }: 45 Lint.finding( 46 Lint.Warning{}, 47 List.contains( 48 ~U32, 49 ~U32.is_eq, 50 bangs, 51 end 52 ) 53 && GpuFit.is_drop(Names.find(Lint.Def, defs, t), t), 54 span, 55 message(callee) 56 ) 57 case _ _: 58 [] 59 60 def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>): 61 Lint.done( 62 Lint.visit( 63 ~Program, 64 ~Lint.Finding, 65 ~visit, 66 Program{ 67 Lint.Program.defs(Lint.Here.sources(here), Map.new(&2, Maybe<&2, Lint.Def>)), 68 GpuFit.bangs(Lint.Here.text(here), 0, []) 69 }, 70 Lint.Here.nodes(here) 71 ) 72 ) 73 74 def rule() -> Lint.Rule: 75 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}