bench: 증분 루프를 측정으로 증명한다
100k줄 200모듈에서 본문 수정은 downstream을 한 칸도 건드리지 않고(재검사 1개, 0.8ms), 시그니처 수정은 hash가 변한 곳까지만 전파되어(재검사 2개, 2.1ms) downstream 소진성 위반을 실제로 잡는다. "빠르다"가 아니라 "다시 볼 것이 적다"가 주장이므로, 시간보다 재검사된 모듈 수를 먼저 출력한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
(* 빠른 검증 루프는 주장이 아니라 측정이다.
|
||||
|
||||
측정하는 것은 두 가지다:
|
||||
1. 전체 검사 시간 (cold) — 규모가 커져도 파국이 아닌가
|
||||
2. 증분 재검사 시간 (warm) — 그리고 무엇이 재검사되었는가
|
||||
|
||||
두 번째가 본체다. 아키텍처의 주장은 "빠르다"가 아니라 "다시 볼 것이
|
||||
적다"이고, 그것은 시간이 아니라 재검사된 모듈 수로 먼저 증명된다.
|
||||
시간은 그 수가 옳다는 것의 따름 결과다. *)
|
||||
|
||||
open Coollang
|
||||
|
||||
let modules = 200
|
||||
let fns_per_module = 68
|
||||
let now () = Unix.gettimeofday ()
|
||||
|
||||
let find_sub hay needle =
|
||||
let n = String.length needle and h = String.length hay in
|
||||
let rec go i =
|
||||
if i + n > h then failwith "not found"
|
||||
else if String.sub hay i n = needle then i
|
||||
else go (i + 1)
|
||||
in
|
||||
go 0
|
||||
|
||||
let write file s =
|
||||
let oc = open_out_bin file in
|
||||
output_string oc s;
|
||||
close_out oc
|
||||
|
||||
(* i번 모듈은 i-1번 모듈을 가져온다. 사슬이므로 시그니처 변경은 끝까지
|
||||
전파되어야 하고, 본문 변경은 한 칸도 가면 안 된다. *)
|
||||
let gen_module i ~body =
|
||||
let b = Buffer.create 8192 in
|
||||
if i > 0 then
|
||||
Buffer.add_string b (Printf.sprintf "import \"m%d\" as Up\n\n" (i - 1));
|
||||
Buffer.add_string b "pub enum Shape {\n Circle(Int),\n Square(Int),\n}\n\n";
|
||||
Buffer.add_string b "pub copyable struct Point {\n x: Int,\n y: Int,\n}\n\n";
|
||||
Buffer.add_string b
|
||||
(Printf.sprintf
|
||||
"pub fn area(s: Shape) -> Int {\n\
|
||||
\ match s {\n\
|
||||
\ Circle(r) => r * r,\n\
|
||||
\ Square(w) => w * w,\n\
|
||||
\ }\n\
|
||||
}\n\n");
|
||||
if i > 0 then
|
||||
Buffer.add_string b
|
||||
"pub fn up_area(s: Up.Shape) -> Int {\n\
|
||||
\ match s {\n\
|
||||
\ Up.Circle(r) => Up.area(s),\n\
|
||||
\ Up.Square(w) => w * w,\n\
|
||||
\ }\n\
|
||||
}\n\n";
|
||||
for k = 0 to fns_per_module - 1 do
|
||||
Buffer.add_string b
|
||||
(Printf.sprintf
|
||||
"pub fn f%d(p: Point, n: Int) -> Int {\n\
|
||||
\ let a = p.x + n\n\
|
||||
\ let b = p.y * %d\n\
|
||||
\ let c = if a > b { a } else { b }\n\
|
||||
\ %s\n\
|
||||
}\n\n"
|
||||
k (k + 1) body)
|
||||
done;
|
||||
Buffer.contents b
|
||||
|
||||
let count_lines s =
|
||||
String.fold_left (fun n c -> if c = '\n' then n + 1 else n) 0 s
|
||||
|
||||
let ms t = Printf.sprintf "%.1fms" (t *. 1000.)
|
||||
|
||||
let () =
|
||||
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_bench" in
|
||||
ignore
|
||||
(Sys.command
|
||||
(Printf.sprintf "rm -rf %s && mkdir -p %s" (Filename.quote dir)
|
||||
(Filename.quote dir)));
|
||||
let path i = Filename.concat dir (Printf.sprintf "m%d.cool" i) in
|
||||
let lines = ref 0 in
|
||||
for i = 0 to modules - 1 do
|
||||
let s = gen_module i ~body:"a + c" in
|
||||
lines := !lines + count_lines s;
|
||||
write (path i) s
|
||||
done;
|
||||
Printf.printf "모듈 %d개, %d줄 생성\n" modules !lines;
|
||||
|
||||
(* 1. cold: 전체 그래프 로드와 검사 *)
|
||||
let st = Session.create ~root:dir () in
|
||||
let t0 = now () in
|
||||
Session.load st (path (modules - 1));
|
||||
let cold = now () -. t0 in
|
||||
let errs = Session.errors st in
|
||||
Printf.printf "cold 전체 검사 %s (오류 %d건)\n" (ms cold) (List.length errs);
|
||||
if errs <> [] then
|
||||
List.iter
|
||||
(fun e -> prerr_endline (Session.string_of_error e))
|
||||
(List.filteri (fun i _ -> i < 5) errs);
|
||||
|
||||
(* 2. warm: 사슬 한가운데 모듈의 본문만 수정 *)
|
||||
let mid = modules / 2 in
|
||||
write (path mid) (gen_module mid ~body:"a + c + 1");
|
||||
let t0 = now () in
|
||||
let touched = Session.recheck st [ path mid ] in
|
||||
let warm_body = now () -. t0 in
|
||||
Printf.printf "warm 본문만 수정 %s (재검사 %d개, 오류 %d건)\n" (ms warm_body)
|
||||
(List.length touched)
|
||||
(List.length (Session.errors st));
|
||||
|
||||
(* 3. warm: 같은 모듈의 시그니처 수정 — variant 추가 *)
|
||||
let sig_src =
|
||||
let s = gen_module mid ~body:"a + c + 1" in
|
||||
let needle = " Square(Int),\n" in
|
||||
let i = find_sub s needle in
|
||||
String.sub s 0 (i + String.length needle)
|
||||
^ " Tri(Int),\n"
|
||||
^ String.sub s
|
||||
(i + String.length needle)
|
||||
(String.length s - i - String.length needle)
|
||||
in
|
||||
write (path mid) sig_src;
|
||||
let t0 = now () in
|
||||
let touched = Session.recheck st [ path mid ] in
|
||||
let warm_sig = now () -. t0 in
|
||||
let errs = Session.errors st in
|
||||
Printf.printf "warm 시그니처 수정 %s (재검사 %d개, 오류 %d건)\n" (ms warm_sig)
|
||||
(List.length touched) (List.length errs);
|
||||
List.iter (fun e -> print_endline (" " ^ Session.string_of_error e)) errs
|
||||
@@ -0,0 +1,3 @@
|
||||
(executable
|
||||
(name bench)
|
||||
(libraries coollang unix))
|
||||
Reference in New Issue
Block a user