Files
BMSSP/Makefile

496 lines
17 KiB
Makefile

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=<test_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=<test_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=<test_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) ---"