SHELL = /bin/bash CXX = g++ # CXX = clang++ SRC_DIR = src GEN_DIR = generator VAL_DIR = validator INC_DIR = include CHK_DIR = checker TEST_DIR = src/test BUILD_DIR = build BUILD_TARGET_DIR = $(BUILD_DIR)/target BUILD_GEN_DIR = $(BUILD_DIR)/gen BUILD_VAL_DIR = $(BUILD_DIR)/val BUILD_CHK_DIR = $(BUILD_DIR)/chk BUILD_TEST_DIR = $(BUILD_DIR)/test LOG_DIR = $(BUILD_DIR)/logs BUILD_IN_DIR = $(BUILD_DIR)/input BUILD_OUT_A_DIR = $(BUILD_DIR)/$(SOL_A)_out BUILD_OUT_B_DIR = $(BUILD_DIR)/$(SOL_B)_out DIRS = $(BUILD_DIR) $(BUILD_TARGET_DIR) $(BUILD_GEN_DIR) $(BUILD_VAL_DIR) $(BUILD_CHK_DIR) $(LOG_DIR) $(BUILD_IN_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR) $(BUILD_TEST_DIR) CXXFLAGS = -Wall -O3 -std=c++2a GENFLAGS = -N $(N) -M $(M) -pm $(pm) SRC_MAIN_FILES = $(wildcard $(SRC_DIR)/*/main.cpp) SOLS_EXE = $(patsubst $(SRC_DIR)/%/main.cpp, $(BUILD_TARGET_DIR)/%, $(SRC_MAIN_FILES)) SOL_A ?= bsb SOL_B ?= dijk SOL_A_EXE = $(BUILD_TARGET_DIR)/$(SOL_A) SOL_B_EXE = $(BUILD_TARGET_DIR)/$(SOL_B) GEN_SRCS = $(wildcard $(GEN_DIR)/*.cpp) GENS_EXE = $(patsubst $(GEN_DIR)/%.cpp, $(BUILD_GEN_DIR)/%, $(GEN_SRCS)) gen ?= gen1 GEN_TO_RUN = $(BUILD_GEN_DIR)/$(gen) VAL_EXE = $(BUILD_VAL_DIR)/validator CHK_EXE = $(BUILD_CHK_DIR)/checker PADDED_ID = $(shell printf "%05d" $(id)) IN_FILE = $(BUILD_IN_DIR)/input$(PADDED_ID) OUT_A_FILE = $(BUILD_OUT_A_DIR)/$(SOL_A)$(PADDED_ID).out OUT_B_FILE = $(BUILD_OUT_B_DIR)/$(SOL_B)$(PADDED_ID).out TEST_COUNT ?= 100 N ?= 1000 M ?= 5000 pm ?= 0.1 .PHONY: all all: $(SOLS_EXE) $(GENS_EXE) $(VAL_EXE) $(CHK_EXE) $(DIRS) @echo "All executables compiled in $(BUILD_DIR)/" $(BUILD_DIR) $(BUILD_TARGET_DIR) $(BUILD_GEN_DIR) $(BUILD_VAL_DIR) $(BUILD_CHK_DIR) $(LOG_DIR) $(BUILD_IN_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR) $(BUILD_TEST_DIR): @echo "Creating directory: $@" @mkdir -p $@ $(BUILD_TARGET_DIR)/%: $(SRC_DIR)/%/main.cpp | $(BUILD_TARGET_DIR) @echo "Compiling Solution $< -> $@" $(CXX) $(CXXFLAGS) -o $@ $< $(BUILD_GEN_DIR)/%: $(GEN_DIR)/%.cpp | $(BUILD_GEN_DIR) @echo "Compiling Generator $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $< $(BUILD_VAL_DIR)/%: $(VAL_DIR)/%.cpp | $(BUILD_VAL_DIR) @echo "Compiling Validator $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $< $(BUILD_CHK_DIR)/%: $(CHK_DIR)/%.cpp | $(BUILD_CHK_DIR) @echo "Compiling Checker $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $< .PRECIOUS: $(BUILD_IN_DIR)/input% $(BUILD_IN_DIR)/input%: $(GEN_TO_RUN) | $(BUILD_IN_DIR) @echo "--- Generating Input: $@ (Using [$(gen)]) ---" @SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \ echo "Using Seed: $$SEED" >&2; \ ./$(GEN_TO_RUN) $(GENFLAGS) "$$SEED" > $@ $(BUILD_TEST_DIR)/%: $(TEST_DIR)/%.cpp | $(BUILD_TEST_DIR) @echo "Compiling Unit Test $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -I$(SRC_DIR)/bsb -o $@ $< -lgtest -lgtest_main -pthread .PHONY: force force: $(BUILD_OUT_A_DIR)/$(SOL_A)%.out: $(BUILD_IN_DIR)/input% $(SOL_A_EXE) force | $(BUILD_OUT_A_DIR) @echo "--- Running $(SOL_A) (ID: $*) ---" @time ./$(SOL_A_EXE) < $< > $@ $(BUILD_OUT_B_DIR)/$(SOL_B)%.out: $(BUILD_IN_DIR)/input% $(SOL_B_EXE) force | $(BUILD_OUT_B_DIR) @echo "--- Running $(SOL_B) (ID: $*) ---" @time ./$(SOL_B_EXE) < $< > $@ .PHONY: gen gen: all ifeq ($(id),) @echo "Error: 'make gen' requires an 'id' variable." @echo "Usage: make gen id= [gen=gen_name]" @exit 1 else @$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M) @echo "--- Generated $(IN_FILE) ---" endif # # 레거시 gen1 타겟 # .PHONY: gen1 # gen1: $(BUILD_GEN_DIR)/gen1 # @echo "--- gen1 : Generating 1 test case (DEPRECATED: use 'make gen id=1') ---" # @SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \ # echo "./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) $$SEED | tee $(BUILD_IN_DIR)/input_legacy"; \ # ./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) $$SEED | tee $(BUILD_IN_DIR)/input_legacy # # 레거시 gen1-silence 타겟 # .PHONY: gen1-silence # gen1-silence: $(BUILD_GEN_DIR)/gen1 # @echo "--- gen1 : Generating 1 test case (DEPRECATED: use 'make gen id=1') ---" # @SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \ # ./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) "$$SEED" > $(BUILD_IN_DIR)/input_legacy .PHONY: val val: all $(VAL_EXE) ifeq ($(id),) @echo "Error: 'make val' requires an 'id' variable." @echo "Usage: make val id=" @exit 1 else @$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M) @echo "--- Validating $(IN_FILE) (ID=$(id)) ---" @./$(VAL_EXE) < $(IN_FILE) @echo "Validator OK." endif .PHONY: check check: all $(CHK_EXE) ifeq ($(id),) @echo "Error: 'make check' requires an 'id' variable." @echo "Usage: make check id=" @exit 1 else @echo "--- Checking ID=$(id) ---" @$(MAKE) --no-print-directory $(OUT_A_FILE) id=$(id) gen=$(gen) SOL_A=$(SOL_A) GENARGS=$(GENARGS) @$(MAKE) --no-print-directory $(OUT_B_FILE) id=$(id) gen=$(gen) SOL_B=$(SOL_B) GENARGS=$(GENARGS) @echo "--- Comparing Outputs (ID=$(id)) ---" @./$(CHK_EXE) $(IN_FILE) $(OUT_A_FILE) $(OUT_B_FILE) || ( \ echo "WA (Wrong Answer): Checker found an issue!" && \ echo "Input: $(IN_FILE)" && \ exit 1 \ ) @echo "OK: ID=$(id) outputs match!" endif UNIT_TEST_SRCS = $(wildcard $(TEST_DIR)/*.cpp) UNIT_TEST_NAMES = $(patsubst $(TEST_DIR)/%.cpp, %, $(UNIT_TEST_SRCS)) .PHONY: test test: all $(LOG_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR) ifdef target @# Case 1: Unit Test 실행 (예: make test target=L33) @echo "--- Running Unit Test Target: [$(target)] ---" @$(MAKE) --no-print-directory $(BUILD_TEST_DIR)/$(target) @echo "-------------------------------------------" @./$(BUILD_TEST_DIR)/$(target) else ifeq ($(id),) @echo "--- Running $(TEST_COUNT) tests (Comparing $(SOL_A) vs $(SOL_B)) ---" @echo "--- Using Generator [$(gen)] (N=$(N), M=$(M)) ---" @LOG_FILE=$(LOG_DIR)/test_run_$$(date +%Y%m%d_%H%M%S).log; \ echo "--- Logging to $${LOG_FILE} ---"; \ script -q -c ' \ set -o pipefail; \ for i in {1..$(TEST_COUNT)}; do \ IN_FILE_LOOP=$(BUILD_IN_DIR)/input$$(printf "%05d" $$i); \ OUT_A_FILE_LOOP=$(BUILD_OUT_A_DIR)/$(SOL_A)$$(printf "%05d" $$i).out; \ OUT_B_FILE_LOOP=$(BUILD_OUT_B_DIR)/$(SOL_B)$$(printf "%05d" $$i).out; \ \ if [ ! -f $$IN_FILE_LOOP ]; then \ SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \ printf "Test %4d (Seed: %s): " "$$i" "$$SEED"; \ ./$(GEN_TO_RUN) -N $(N) -M $(M) "$$SEED" > $$IN_FILE_LOOP; \ printf "(GEN) "; \ else \ printf "Test %4d (Seed: EXISTING): " "$$i"; \ fi; \ \ printf "$(SOL_A): "; \ ( TIMEFORMAT=%R; time ./$(SOL_A_EXE) < $$IN_FILE_LOOP > $$OUT_A_FILE_LOOP ) 2>&1 | tr "\n" " " ; \ printf "$(SOL_B): "; \ ( TIMEFORMAT=%R; time ./$(SOL_B_EXE) < $$IN_FILE_LOOP > $$OUT_B_FILE_LOOP ) 2>&1 | tr "\n" " " ; \ printf "\t CHK: "; \ ( ./$(CHK_EXE) $$IN_FILE_LOOP $$OUT_A_FILE_LOOP $$OUT_B_FILE_LOOP ) 2>&1 || { printf "\nWA! (Input: $$IN_FILE_LOOP)\n"; echo "Input saved to $$IN_FILE_LOOP"; exit 1; }; \ done; \ echo "--- All $(TEST_COUNT) tests passed! ---"; \ ' /dev/null | tee $${LOG_FILE}; \ if [ $${PIPESTATUS[0]} -ne 0 ]; then \ printf "\n--- TESTS FAILED! See %s ---\n" "$${LOG_FILE}"; \ exit 1; \ fi else @echo "--- Running single test for ID=$(id) ---" @$(MAKE) --no-print-directory check id=$(id) gen=$(gen) SOL_A=$(SOL_A) SOL_B=$(SOL_B) N=$(N) M=$(M) endif .PHONY: $(UNIT_TEST_NAMES) $(UNIT_TEST_NAMES): @$(MAKE) --no-print-directory test target=$@ .PHONY: bench bench: all ifeq ($(id),) @echo "Error: 'make bench' requires an 'id' variable." @exit 1 else @$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M) @echo "--- Benchmarking $(SOL_A_EXE) with $(IN_FILE) (ID=$(id)) ---" @time ./$(SOL_A_EXE) < $(IN_FILE) > $(OUT_A_FILE) endif .PHONY: format format: @echo "--- Formatting all C/C++ files ---" @find $(SRC_DIR) $(GEN_DIR) $(VAL_DIR) $(CHK_DIR) $(INC_DIR) -type f \( -name "*.cpp" -o -name "*.c" -o -name "*.hpp" -o -name "*.h" \) -print0 | xargs -0 clang-format -i @echo "Formatting complete." .PHONY: clean-outputs clean-outputs: @echo "Cleaning up $(BUILD_OUT_A_DIR)/ and $(BUILD_OUT_B_DIR)/..." @rm -rf $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR) .PHONY: clean-inputs clean-inputs: @echo "Cleaning up $(BUILD_IN_DIR)/..." @rm -rf $(BUILD_IN_DIR) .PHONY: clean-tests clean-tests: clean-inputs clean-outputs .PHONY: clean clean: @echo "Cleaning up $(BUILD_DIR)/..." @rm -rf $(BUILD_DIR) # ----------------------------------------------------------------------------- # Benchmark Target (Advanced) # - N, M 증가 (Step) # - 동일 조건 반복 (Repeat) # - 유효성 검사 (Validator) 포함 # - 상세 정보(Seed, Command) CSV 기록 # ----------------------------------------------------------------------------- BENCH_TIMESTAMP = $(shell date +%Y%m%d_%H%M%S) BENCH_CSV = $(LOG_DIR)/benchmark_results_$(BENCH_TIMESTAMP).csv BENCH_META = $(LOG_DIR)/benchmark_results_$(BENCH_TIMESTAMP).meta START_N ?= 10000 END_N ?= 50000 STEP_N ?= 1000 # M 계산식 (bash arithmetic expansion 문법) M_CALC ?= n * 4 # 동일 조건 반복 횟수 REPEAT ?= 5 .PHONY: benchmark benchmark: all $(CHK_EXE) $(LOG_DIR) @echo "--- Starting Benchmark ---" @echo "Data: $(BENCH_CSV)" @echo "Meta: $(BENCH_META)" @# 1. 메타 파일(.meta)에 실행 정보 기록 @echo "Execution Info: Date=$(BENCH_TIMESTAMP) | Host=$(shell hostname)" > $(BENCH_META) @echo "Reproduce Command: make benchmark START_N=$(START_N) END_N=$(END_N) STEP_N=$(STEP_N) REPEAT=$(REPEAT) M_CALC='$(M_CALC)' SOL_A=$(SOL_A) SOL_B=$(SOL_B) gen=$(gen)" >> $(BENCH_META) @# 2. CSV 파일(.csv)에는 순수 헤더만 기록 (엑셀 호환성 확보) @echo "N,M,Iter,Seed,$(SOL_A)_Time,$(SOL_B)_Time,Gen_Cmd,Sol_A_Cmd,Sol_B_Cmd" > $(BENCH_CSV) @bash -c ' \ export LC_ALL=C; \ TIMEFORMAT=%R; \ \ for (( n=$(START_N); n<=$(END_N); n+=$(STEP_N) )); do \ m=$$(( $(M_CALC) )); \ \ for (( r=1; r<=$(REPEAT); r++ )); do \ SEED=$$(cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \ \ TMP_IN="$(BUILD_IN_DIR)/bench_in_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ TMP_OUT_A="$(BUILD_DIR)/bench_out_a_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ TMP_OUT_B="$(BUILD_DIR)/bench_out_b_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ \ CMD_GEN="./$(GEN_TO_RUN) -N $$n -M $$m $$SEED"; \ CMD_SOL_A="./$(SOL_A_EXE) < input"; \ CMD_SOL_B="./$(SOL_B_EXE) < input"; \ \ printf "Run: N=%-5d M=%-5d Iter=%-2d ... " $$n $$m $$r; \ \ $$CMD_GEN > $$TMP_IN; \ \ t_a=$$( { time ./$(SOL_A_EXE) < $$TMP_IN > $$TMP_OUT_A; } 2>&1 ); \ t_b=$$( { time ./$(SOL_B_EXE) < $$TMP_IN > $$TMP_OUT_B; } 2>&1 ); \ \ if ! ./$(CHK_EXE) $$TMP_IN $$TMP_OUT_A $$TMP_OUT_B > /dev/null 2>&1; then \ printf "\n[FAIL] Checker found mismatch!\n"; \ echo "Seed: $$SEED"; \ echo "Check Metafile: $(BENCH_META)"; \ exit 1; \ fi; \ \ echo "$$n,$$m,$$r,$$SEED,$$t_a,$$t_b,\"$$CMD_GEN\",\"$$CMD_SOL_A\",\"$$CMD_SOL_B\"" >> $(BENCH_CSV); \ \ echo "OK. [A: $${t_a}s] [B: $${t_b}s]"; \ \ rm -f $$TMP_IN $$TMP_OUT_A $$TMP_OUT_B; \ done; \ done \ ' @echo "--- Benchmark Complete! ---" @echo "CSV Data: $(BENCH_CSV)" @echo "Metadata: $(BENCH_META)" # .PHONY: benchmark # benchmark: all $(CHK_EXE) $(LOG_DIR) # @echo "--- Starting Benchmark with Checker ---" # @echo "Range: N=[$(START_N) .. $(END_N)], Step=$(STEP_N)" # @echo "Formula: M = $(M_CALC)" # @echo "Checker: $(CHK_EXE)" # @echo "Output: $(BENCH_CSV)" # @echo "N,M,Iter,Seed,$(SOL_A)_Time,$(SOL_B)_Time,Gen_Cmd,Sol_A_Cmd,Sol_B_Cmd" > $(BENCH_CSV) # @bash -c ' \ # export LC_ALL=C; \ # TIMEFORMAT=%R; \ # \ # for (( n=$(START_N); n<=$(END_N); n+=$(STEP_N) )); do \ # m=$$(( $(M_CALC) )); \ # \ # for (( r=1; r<=$(REPEAT); r++ )); do \ # SEED=$$(cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \ # \ # # 병렬 실행을 위한 임시 파일명 설정 (Input, OutA, OutB) \ # TMP_IN="$(BUILD_IN_DIR)/bench_in_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ # TMP_OUT_A="$(BUILD_DIR)/bench_out_a_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ # TMP_OUT_B="$(BUILD_DIR)/bench_out_b_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ # \ # CMD_GEN="./$(GEN_TO_RUN) -N $$n -M $$m $$SEED"; \ # CMD_SOL_A="./$(SOL_A_EXE) < input"; \ # CMD_SOL_B="./$(SOL_B_EXE) < input"; \ # \ # printf "Run: N=%-5d M=%-5d Iter=%-2d ... " $$n $$m $$r; \ # \ # # 1. Generator 실행 \ # $$CMD_GEN > $$TMP_IN; \ # \ # # 2. Sol A 실행 (시간 측정 + 출력 저장) \ # t_a=$$( { time ./$(SOL_A_EXE) < $$TMP_IN > $$TMP_OUT_A; } 2>&1 ); \ # \ # # 3. Sol B 실행 (시간 측정 + 출력 저장) \ # t_b=$$( { time ./$(SOL_B_EXE) < $$TMP_IN > $$TMP_OUT_B; } 2>&1 ); \ # \ # # 4. Checker 실행 (비교) \ # if ! ./$(CHK_EXE) $$TMP_IN $$TMP_OUT_A $$TMP_OUT_B > /dev/null 2>&1; then \ # printf "\n[FAIL] Checker found mismatch!\n"; \ # echo "Seed: $$SEED"; \ # echo "Input saved to: $$TMP_IN"; \ # echo "Out A saved to: $$TMP_OUT_A"; \ # echo "Out B saved to: $$TMP_OUT_B"; \ # exit 1; \ # fi; \ # \ # # 5. 통과 시 CSV 기록 \ # echo "$$n,$$m,$$r,$$SEED,$$t_a,$$t_b,\"$$CMD_GEN\",\"$$CMD_SOL_A\",\"$$CMD_SOL_B\"" >> $(BENCH_CSV); \ # \ # echo "OK. [A: $${t_a}s] [B: $${t_b}s]"; \ # \ # # 6. 임시 파일 삭제 \ # rm -f $$TMP_IN $$TMP_OUT_A $$TMP_OUT_B; \ # done; \ # done \ # ' # @echo "--- Benchmark Complete! Data saved to $(BENCH_CSV) ---" # .PHONY: benchmark # benchmark: all $(LOG_DIR) # @echo "--- Starting Advanced Benchmark ---" # @echo "Range: N=[$(START_N) .. $(END_N)], Step=$(STEP_N)" # @echo "Formula: M = $(M_CALC)" # @echo "Repeat: $(REPEAT) times per setting" # @echo "Output: $(BENCH_CSV)" # @echo "N,M,Iter,Seed,$(SOL_A)_Time,$(SOL_B)_Time,Gen_Cmd,Sol_A_Cmd,Sol_B_Cmd" > $(BENCH_CSV) # @bash -c ' \ # export LC_ALL=C; \ # TIMEFORMAT=%R; \ # \ # for (( n=$(START_N); n<=$(END_N); n+=$(STEP_N) )); do \ # m=$$(( $(M_CALC) )); \ # \ # for (( r=1; r<=$(REPEAT); r++ )); do \ # SEED=$$(cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \ # \ # # [변경] 파일명에 타임스탬프와 PID($$)를 포함하여 충돌 방지 \ # TMP_IN="$(BUILD_IN_DIR)/bench_input_$(BENCH_TIMESTAMP)_$$$$.tmp"; \ # \ # CMD_GEN="./$(GEN_TO_RUN) -N $$n -M $$m $$SEED"; \ # CMD_SOL_A="./$(SOL_A_EXE) < input"; \ # CMD_SOL_B="./$(SOL_B_EXE) < input"; \ # \ # printf "Run: N=%-5d M=%-5d Iter=%-2d ... " $$n $$m $$r; \ # \ # $$CMD_GEN > $$TMP_IN; \ # \ # if ! ./$(VAL_EXE) < $$TMP_IN > /dev/null 2>&1; then \ # echo " [FAIL] Validator rejected input! Seed: $$SEED"; \ # rm -f $$TMP_IN; \ # exit 1; \ # fi; \ # \ # t_a=$$( { time ./$(SOL_A_EXE) < $$TMP_IN > /dev/null; } 2>&1 ); \ # t_b=$$( { time ./$(SOL_B_EXE) < $$TMP_IN > /dev/null; } 2>&1 ); \ # \ # echo "$$n,$$m,$$r,$$SEED,$$t_a,$$t_b,\"$$CMD_GEN\",\"$$CMD_SOL_A\",\"$$CMD_SOL_B\"" >> $(BENCH_CSV); \ # \ # echo "OK. [A: $${t_a}s] [B: $${t_b}s]"; \ # \ # rm -f $$TMP_IN; \ # done; \ # done \ # ' # @echo "--- Benchmark Complete! Data saved to $(BENCH_CSV) ---" # .PHONY: benchmark # benchmark: all $(LOG_DIR) # @echo "--- Starting Advanced Benchmark ---" # @echo "Range: N=[$(START_N) .. $(END_N)], Step=$(STEP_N)" # @echo "Formula: M = $(M_CALC)" # @echo "Repeat: $(REPEAT) times per setting" # @echo "Validator: $(VAL_EXE)" # @echo "Output: $(BENCH_CSV)" # @# CSV Header 작성 # @echo "N,M,Iter,Seed,$(SOL_A)_Time,$(SOL_B)_Time,Gen_Cmd,Sol_A_Cmd,Sol_B_Cmd" > $(BENCH_CSV) # @bash -c ' \ # export LC_ALL=C; \ # TIMEFORMAT=%R; \ # \ # # 1. N 루프 \ # for (( n=$(START_N); n<=$(END_N); n+=$(STEP_N) )); do \ # m=$$(( $(M_CALC) )); \ # \ # # 2. 반복 루프 (Repeat) \ # for (( r=1; r<=$(REPEAT); r++ )); do \ # SEED=$$(cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \ # TMP_IN="$(BUILD_IN_DIR)/bench_input.tmp"; \ # \ # # 명령어 문자열 구성 (CSV 기록용) \ # CMD_GEN="./$(GEN_TO_RUN) -N $$n -M $$m $$SEED"; \ # CMD_SOL_A="./$(SOL_A_EXE) < input"; \ # CMD_SOL_B="./$(SOL_B_EXE) < input"; \ # \ # printf "Run: N=%-5d M=%-5d Iter=%-2d ... " $$n $$m $$r; \ # \ # # 3. Generator 실행 \ # $$CMD_GEN > $$TMP_IN; \ # \ # # 4. Validator 실행 (실패 시 즉시 종료) \ # if ! ./$(VAL_EXE) < $$TMP_IN > /dev/null 2>&1; then \ # echo " [FAIL] Validator rejected input! Seed: $$SEED"; \ # exit 1; \ # fi; \ # \ # # 5. 솔루션 실행 및 시간 측정 \ # t_a=$$( { time ./$(SOL_A_EXE) < $$TMP_IN > /dev/null; } 2>&1 ); \ # t_b=$$( { time ./$(SOL_B_EXE) < $$TMP_IN > /dev/null; } 2>&1 ); \ # \ # # 6. CSV 저장 (명령어에 콤마가 포함될 수 있으므로 따옴표로 감싸는 것이 안전하나, 여기선 단순화) \ # echo "$$n,$$m,$$r,$$SEED,$$t_a,$$t_b,\"$$CMD_GEN\",\"$$CMD_SOL_A\",\"$$CMD_SOL_B\"" >> $(BENCH_CSV); \ # \ # echo "OK. [A: $${t_a}s] [B: $${t_b}s]"; \ # \ # rm -f $$TMP_IN; \ # done; \ # done \ # ' # @echo "--- Benchmark Complete! Data saved to $(BENCH_CSV) ---"