(* 빠른 검증 루프는 주장이 아니라 측정이다. 측정하는 것은 두 가지다: 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