diff --git a/bench/bench.ml b/bench/bench.ml new file mode 100644 index 0000000..598cf0c --- /dev/null +++ b/bench/bench.ml @@ -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 diff --git a/bench/dune b/bench/dune new file mode 100644 index 0000000..43d5758 --- /dev/null +++ b/bench/dune @@ -0,0 +1,3 @@ +(executable + (name bench) + (libraries coollang unix)) diff --git a/docs/thesis.md b/docs/thesis.md index c7a100f..91f024c 100644 --- a/docs/thesis.md +++ b/docs/thesis.md @@ -332,3 +332,14 @@ L2 빠른 테스트 / L3 fuzzing / L4 formal proof → 요청 시, 분리 실행 오류를 더 빨리 잡는가? / 컴파일 복잡도·시간은 예측 가능한가? / invalidation 범위를 넓히는가? / unrelated code 의미를 바꾸는가? / 기존 개념의 중복 표현인가? → 강한 이유 없으면 거절 + +■ 측정 (2026-08, v0 fast path) +100,391줄 / 200 모듈 (사슬 의존). bench/bench.ml로 재현. + cold 전체 검사 245ms + 본문만 수정 0.8ms, 재검사 1개 모듈 + 시그니처 수정 2.1ms, 재검사 2개 모듈 (+ downstream 소진성 위반 검출) +증분 루프의 비용은 시간이 아니라 재검사 범위가 결정한다. 본문 수정이 +downstream을 한 칸도 건드리지 않는 것이 이 아키텍처의 주장이고, 위 수치의 +"재검사 1개"가 그 주장이다. 시그니처 수정이 사슬 끝까지 가지 않고 2개에서 +멈추는 것도 같은 규칙의 결과다 — m101의 interface는 변하지 않으므로 m102는 +다시 볼 이유가 없다.