SHELL := $(shell which bash) .ONESHELL: COL := $(patsubst %.gp,%,$(wildcard *.gp)) DAT := $(COL:=.dat) DIS := $(shell grep '^ID=.*$$' /etc/os-release | cut -d'=' -f2) MAX := 32 OPS := $(shell echo $$(( 2**15 ))) SEQ := cas old stack std SRC := $(wildcard *.cc) SVG := $(COL:=.svg) T := $(shell echo $$(( $(shell nproc) / 2 ))) VER := $(shell grep '^VERSION_ID=.*$$' /etc/os-release | cut -d'=' -f2 | tr -d '"') EXE := $(basename $(SRC)) LIB := ./rpmalloc/$(DIS)/$(VER) LOG := $(foreach e,$(EXE),$(foreach o,$(OPS),$(foreach t,$(shell for ((i=1; i<=$(MAX); i*=2)); do echo $$i; done),$(e)-o$(o)-t$(t).log))) STA := $(LOG:.log=.sta) TXT := $(foreach e,$(EXE),$(foreach o,$(OPS),$(e)-o$(o)-t$(T).txt)) W1 := $(shell m=0; for i in $(EXE); do l=$${#i}; (( l > m )) && m=$$l; done; echo $$((m + 2))) W2 := 10 W3 := $(shell echo $$(( $(W1) + ( $(W2) + 1 ) * $(words $(COL)) ))) CXXFLAGS := -O3 -std=c++23 -Wall # -g LDFLAGS := -L$(LIB) LDLIBS := -latomic -ldl -lrpmalloc # $(filter-out stm%,$(EXE)): CXXFLAGS += -fanalyzer -fsanitize={null,thread,undefined} # address,leak $(filter stm%,$(EXE)): CXXFLAGS += -fgnu-tm export LC_ALL=C.UTF-8 export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:$(LIB) all: @if make -qs $(TXT); then make -s sort else make -s stat fi check: @printf '%$(W1)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s\n' | tr ' ' '-' printf '%$(W1)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s\n' program drd futex helgrind memcheck rpmalloc printf '%$(W1)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s\n' | tr ' ' '-' for i in $(EXE); do printf '%$(W1)s' $$i make --no-print-directory -s $${i} &> /dev/null make --no-print-directory $${i}.{drd,futex,helgrind,memcheck,rpmalloc} echo done printf '%$(W1)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s\n' | tr ' ' '-' echo "drd, helgrind & thread sanitizer produces many false positives." echo "Some of them can be suppressed as seen in spinlock.h" printf '%$(W1)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s%$(W2)s\n' | tr ' ' '-' clean: -rm -fr $(DAT) $(EXE) $(SVG) *.log *.sta *.txt .*.d core* *~ -find -maxdepth 2 -mindepth 2 -name makefile -execdir make $@ \; -killall -KILL $(EXE) eog loupe &> /dev/null || true dat: $(DAT) exe: $(EXE) help: @echo "export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:$(LIB)" key: @printf '%$(W3)s\n' | tr ' _' '- ' echo " program: program name + sequential(.) or parallel(:)" echo " ops: push()'s + pop()'s" echo " elapsed: elapsed time (ms)" echo " task: task-clock time (ms)" echo " ratio1: ops / elapsed (ops/ms)" echo " ratio2: ops / task (ops/ms)" lockfree: @IFS='' while read -r line; do for i in asp mutex semaphore shared_ptr spinlock stm stm2; do if echo "$$line" | grep -q $$i; then line='' break fi done if [[ -n "$$line" ]]; then echo "$$line" fi done < <(make --no-print-directory parallel) parallel: @make --no-print-directory sort | grep -v '[[:alnum:]]\. ' sequential: @make --no-print-directory sort | grep -v '[[:alnum:]]: ' sort: $(filter %-t$(T).txt,$(TXT)) | key @readarray -t F < <(head -n1 $< | tr ' ' '\n' | sed '/^$$/d') declare -a S=('' 'n' 'n' 'n' 'n' 'n') declare -a R=('' '' '' '' 'r' 'r') for ((i=0; i<$${#F[@]}; ++i)); do printf '%$(W3)s\n' | tr ' ' '-' if [[ "$$i" == "0" ]]; then w=$(W1) else w=$(W2) fi in=$$(printf "%$${w}s" "$${F[$$i]}") out=$$(printf "%$${w}s" "[$${F[$$i]:0:$$((w - 2))}]") head -n1 -q $< | sed "s/$${in}/$${out}/" printf '%$(W3)s\n' | tr ' ' '-' for t in $^; do tail -n1 $$t done | sort -bf -k$$((i+1)),$$((i+1))$${S[$$i]}$${R[$$i]} -k$${#F[@]},$${#F[@]}$${S[$${#F[@]}]}$${R[$${#F[@]}]} done printf '%$(W3)s\n' | tr ' ' '-' stat: | key @for i in $(filter %-t$(T).txt,$(TXT)); do make -s $$i &> /dev/null if echo $$i | grep $(firstword $(EXE)) &> /dev/null; then printf '%$(W3)s\n' | tr ' ' '-' head -n 1 $$i printf '%$(W3)s\n' | tr ' ' '-' fi tail -n 1 $$i done printf '%$(W3)s\n' | tr ' ' '-' svg: $(SVG) @for i in $^; do xdg-open $$i &> /dev/null & done %: %.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -MF .$*.d ./$< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) -o $@ %.dat: @printf '%80s\n _$@\n%80s\n' | tr ' _' '# ' echo "program $(EXE)" | tee $@ for (( i = 1; i <= $(MAX); i *= 2 )); do echo -n "$$i " for e in $(EXE); do make -s $${e}-o$(OPS)-t$${i}.txt &> /dev/null for (( j = 0; j <= $(words $(COL)); ++j )); do c=$$(cat $${e}-o$(OPS)-t$${i}.txt | tr -s ' ' | cut -d' ' -f$$((j+2))) if echo "$$c" | grep "$*" &> /dev/null; then echo -en "$$c " | tail -n 1 fi done done echo done | tee -a $@ %.drd: % @valgrind --tool=drd --error-exitcode=1 --exit-on-first-error=yes -- ./$< -o 1000 -t $(T) &> /dev/null if [ $$? -eq 0 ]; then echo -en "\e[1;32m$$(printf '%$(W2)s' PASSED)\e[0m" else echo -en "\e[1;31m$$(printf '%$(W2)s' FAILED)\e[0m" fi %.futex: % @[[ " $(SEQ) " =~ " $< " ]] && threads=1 || threads=$(T) read -r time seconds usecs calls errors syscall < <(strace -c -e futex -f ./$< -o $(OPS) -t $$threads |& grep futex) &> /dev/null if (( calls == 0 )); then echo -en "\e[1;32m$$(printf '%$(W2)s' $$calls)\e[0m" else echo -en "\e[1;31m$$(printf '%$(W2)s' $$calls)\e[0m" fi %.helgrind: % @valgrind --tool=helgrind --error-exitcode=1 --exit-on-first-error=yes -- ./$< -o 1000 -t $(T) &> /dev/null if [ $$? -eq 0 ]; then echo -en "\e[1;32m$$(printf '%$(W2)s' PASSED)\e[0m" else echo -en "\e[1;31m$$(printf '%$(W2)s' FAILED)\e[0m" fi %.memcheck: % @[[ " $(SEQ) " =~ " $< " ]] && threads=1 || threads=2 [[ " stm stm2 " =~ " $< " ]] && undef=no || undef=yes LD_PRELOAD=$$(gcc -print-file-name=libc.so.6) valgrind --tool=memcheck --run-libc-freeres=no --run-cxx-freeres=no --error-exitcode=1 --exit-on-first-error=yes --undef-value-errors=$$undef -- ./$< -o 1000 -t $$threads &> /dev/null if [ $$? -eq 0 ]; then echo -en "\e[1;32m$$(printf '%$(W2)s' PASSED)\e[0m" else echo -en "\e[1;31m$$(printf '%$(W2)s' FAILED)\e[0m" fi %.rpmalloc: % @if ldd ./$< | grep -q librpmalloc.so; then echo -en "\e[1;32m$$(printf '%$(W2)s' FOUND)\e[0m" else echo -en "\e[1;31m$$(printf '%$(W2)s' MISSING)\e[0m" fi %.txt: %.log %.sta @log="[$$(cat $*.log | tr '\n' ',')]" ops=$$(python -c "import statistics; print(int(statistics.mean($$log)))") elapsed=$$(grep -m1 duration_time $*.sta | cut -d';' -f1) elapsed=$$(python -c "print('%g' % ($$elapsed / 1000000))") # ns -> ms ratio1=$$(python -c "print('%g' % ($$ops / $$elapsed))") task=$$(grep -m1 task-clock $*.sta | cut -d';' -f1) ratio2=$$(python -c "print('%g' % ($$ops / $$task))") exe=$$(echo $* | sed -n 's/\([^-]*\)-.*/\1/p') if [[ " $(SEQ) " =~ " $$exe " ]]; then exe+='.' else exe+=':' fi printf '%$(W1).$(W1)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s\n' program ops elapsed task ratio1 ratio2 > $@ printf '%$(W1).$(W1)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s %$(W2).$(W2)s\n' $$exe $$(printf '%.0f' $$ops) "$$(printf '%.1f' $$elapsed)" "$$(printf '%.1f' $$task)" "$$(printf '%.0f' $$ratio1)" "$$(printf '%.0f' $$ratio2)" >> $@ %.svg: %.gp | %.dat @gnuplot -e "basename='$*'" $< > $@ -include .*.d .PHONY: all check clean dat exe help key lockfree parallel sequential sort stat svg .PRECIOUS: $(DAT) $(LOG) $(STA) $(TXT) $(SVG) .SECONDEXPANSION: %.log %.sta: $$(firstword $$(subst -, ,$$*)) @o=$$(echo $* | sed -n 's/.*-o\([[:digit:]]*\).*/\1/p') t=$$(echo $* | sed -n 's/.*-t\([[:digit:]]*\).*/\1/p') if [[ -z "$$o" || -z "$$t" ]]; then msg="Error: invalid parameters for ./$< -o$$o -t$$t" notify-send "$$msg" echo "$$msg" >&2 exit 1 fi if [[ $< -nt $*.sta ]]; then [[ " $(SEQ) " =~ " $< " ]] && (( o = o * t )) && t=1 perf stat -e 'duration_time,task-clock' -r 10 -x ';' -- ./$< -o $$o -t $$t 1> $*.log 2> $*.sta fi