perf: measure set benchmarks with user and system time
This commit is contained in:
@@ -3,15 +3,19 @@ import argparse
|
|||||||
import ctypes
|
import ctypes
|
||||||
import gc
|
import gc
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import statistics
|
import statistics
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import sys
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
HERE = Path(__file__).resolve().parent
|
||||||
BUILD = HERE / "build"
|
BUILD = HERE / "build"
|
||||||
LIBC = ctypes.CDLL(None)
|
LIBC = ctypes.CDLL(None)
|
||||||
LIBC.free.argtypes = [ctypes.c_void_p]
|
LIBC.free.argtypes = [ctypes.c_void_p]
|
||||||
|
TIME_COMMAND = os.environ.get("TIME_COMMAND") or shutil.which("time") or "/usr/bin/time"
|
||||||
|
TIME_FORMAT = "%U\t%S"
|
||||||
|
|
||||||
|
|
||||||
class SetAPI:
|
class SetAPI:
|
||||||
@@ -49,108 +53,132 @@ class SetAPI:
|
|||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
def median_fini(api, symbols, bpp, calls, rounds):
|
def make_symbols(args):
|
||||||
samples = []
|
symbols = tuple(
|
||||||
for _ in range(rounds):
|
f"symbol_{i:08d}_version_ALT_{i % 97}".encode() for i in range(args.symbols)
|
||||||
sets = [api.new_with_symbols(symbols) for _ in range(calls)]
|
)
|
||||||
results = []
|
required = (
|
||||||
start = time.perf_counter_ns()
|
symbols[::2]
|
||||||
for value in sets:
|
if args.required is None
|
||||||
results.append(api.lib.set_fini(value, bpp))
|
else tuple(symbols[i * args.symbols // args.required] for i in range(args.required))
|
||||||
samples.append((time.perf_counter_ns() - start) / calls)
|
)
|
||||||
if not all(results):
|
return symbols, required
|
||||||
raise RuntimeError("set_fini returned NULL")
|
|
||||||
encoded = [ctypes.string_at(result) for result in results]
|
|
||||||
if len(set(encoded)) != 1:
|
|
||||||
raise RuntimeError("set_fini is not deterministic")
|
|
||||||
for value, result in zip(sets, results):
|
|
||||||
api.release(value, result)
|
|
||||||
return statistics.median(samples)
|
|
||||||
|
|
||||||
|
|
||||||
def median_build(api, symbols, bpp, calls, rounds):
|
def api_for(name):
|
||||||
samples = []
|
if name == "set9":
|
||||||
for _ in range(rounds):
|
return SetAPI(BUILD / "libset9.so")
|
||||||
sets = []
|
if name == "direct":
|
||||||
results = []
|
return SetAPI(BUILD / "libdirect-hash.so")
|
||||||
start = time.perf_counter_ns()
|
raise ValueError(f"unknown implementation: {name}")
|
||||||
for _ in range(calls):
|
|
||||||
|
|
||||||
|
def child_main(args):
|
||||||
|
symbols, required = make_symbols(args)
|
||||||
|
api = api_for(args.time_child_impl)
|
||||||
|
checksum = 0
|
||||||
|
|
||||||
|
if args.time_child_operation == "fini":
|
||||||
|
# Measured by /usr/bin/time around this child process. The repeated work
|
||||||
|
# is set construction plus set_fini; keeping construction in the same
|
||||||
|
# child avoids Python-side subsection timers while still reporting CPU
|
||||||
|
# user/system time from the external time utility.
|
||||||
|
for _ in range(args.fini_calls):
|
||||||
value = api.new_with_symbols(symbols)
|
value = api.new_with_symbols(symbols)
|
||||||
result = api.lib.set_fini(value, bpp)
|
result = api.lib.set_fini(value, args.bpp)
|
||||||
sets.append(value)
|
if not result:
|
||||||
results.append(result)
|
|
||||||
samples.append((time.perf_counter_ns() - start) / calls)
|
|
||||||
if not all(results):
|
|
||||||
raise RuntimeError("set_fini returned NULL")
|
raise RuntimeError("set_fini returned NULL")
|
||||||
for value, result in zip(sets, results):
|
checksum ^= len(ctypes.string_at(result))
|
||||||
api.release(value, result)
|
api.release(value, result)
|
||||||
return statistics.median(samples)
|
elif args.time_child_operation == "add":
|
||||||
|
for _ in range(args.fini_calls):
|
||||||
|
value = api.new_with_symbols(symbols)
|
||||||
def median_add(api, symbols, calls, rounds):
|
checksum ^= int(value)
|
||||||
samples = []
|
|
||||||
for _ in range(rounds):
|
|
||||||
values = []
|
|
||||||
start = time.perf_counter_ns()
|
|
||||||
for _ in range(calls):
|
|
||||||
values.append(api.new_with_symbols(symbols))
|
|
||||||
samples.append((time.perf_counter_ns() - start) / calls)
|
|
||||||
for value in values:
|
|
||||||
api.lib.set_free(value)
|
api.lib.set_free(value)
|
||||||
return statistics.median(samples)
|
elif args.time_child_operation == "build":
|
||||||
|
for _ in range(args.fini_calls):
|
||||||
|
value = api.new_with_symbols(symbols)
|
||||||
def median_cmp(api, provider, requirement, calls, rounds):
|
result = api.lib.set_fini(value, args.bpp)
|
||||||
|
if not result:
|
||||||
|
raise RuntimeError("set_fini returned NULL")
|
||||||
|
checksum ^= len(ctypes.string_at(result))
|
||||||
|
api.release(value, result)
|
||||||
|
elif args.time_child_operation == "cmp-cold":
|
||||||
|
provider = api.encode(symbols, args.bpp)
|
||||||
|
requirement = api.encode(required, args.bpp)
|
||||||
|
for _ in range(args.cold_calls):
|
||||||
|
pid = os.fork()
|
||||||
|
if pid == 0:
|
||||||
|
result = api.lib.rpmsetcmp(provider, requirement)
|
||||||
|
os._exit(0 if result == 1 else 1)
|
||||||
|
_, status = os.waitpid(pid, 0)
|
||||||
|
if status != 0:
|
||||||
|
raise RuntimeError(f"cold rpmsetcmp child failed: status={status}")
|
||||||
|
checksum += 1
|
||||||
|
elif args.time_child_operation == "cmp-warm":
|
||||||
|
provider = api.encode(symbols, args.bpp)
|
||||||
|
requirement = api.encode(required, args.bpp)
|
||||||
expected = api.lib.rpmsetcmp(provider, requirement)
|
expected = api.lib.rpmsetcmp(provider, requirement)
|
||||||
if expected != 1 or api.lib.rpmsetcmp(provider, provider) != 0:
|
if expected != 1 or api.lib.rpmsetcmp(provider, provider) != 0:
|
||||||
raise RuntimeError(f"unexpected rpmsetcmp result: {expected}")
|
raise RuntimeError(f"unexpected rpmsetcmp result: {expected}")
|
||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
api.lib.rpmsetcmp(provider, requirement)
|
api.lib.rpmsetcmp(provider, requirement)
|
||||||
|
for _ in range(args.cmp_calls):
|
||||||
samples = []
|
|
||||||
for _ in range(rounds):
|
|
||||||
checksum = 0
|
|
||||||
start = time.perf_counter_ns()
|
|
||||||
for _ in range(calls):
|
|
||||||
checksum += api.lib.rpmsetcmp(provider, requirement)
|
checksum += api.lib.rpmsetcmp(provider, requirement)
|
||||||
samples.append((time.perf_counter_ns() - start) / calls)
|
if checksum != args.cmp_calls:
|
||||||
if checksum != calls:
|
|
||||||
raise RuntimeError("rpmsetcmp result changed during benchmark")
|
raise RuntimeError("rpmsetcmp result changed during benchmark")
|
||||||
return statistics.median(samples)
|
else:
|
||||||
|
raise RuntimeError(f"unknown timed operation: {args.time_child_operation}")
|
||||||
|
|
||||||
|
# Keep a small observable side effect so timed loops are not optimized away
|
||||||
|
# inside the C library or by future wrappers.
|
||||||
|
print(checksum, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def cold_cmp_once(api, provider, requirement):
|
def measure_with_time(args, implementation, operation, calls):
|
||||||
read_fd, write_fd = os.pipe()
|
|
||||||
pid = os.fork()
|
|
||||||
if pid == 0:
|
|
||||||
os.close(read_fd)
|
|
||||||
start = time.perf_counter_ns()
|
|
||||||
result = api.lib.rpmsetcmp(provider, requirement)
|
|
||||||
elapsed = time.perf_counter_ns() - start
|
|
||||||
os.write(write_fd, f"{elapsed} {result}".encode())
|
|
||||||
os.close(write_fd)
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
os.close(write_fd)
|
|
||||||
payload = b""
|
|
||||||
while chunk := os.read(read_fd, 128):
|
|
||||||
payload += chunk
|
|
||||||
os.close(read_fd)
|
|
||||||
_, status = os.waitpid(pid, 0)
|
|
||||||
if status != 0:
|
|
||||||
raise RuntimeError(f"cold rpmsetcmp child failed: status={status}")
|
|
||||||
elapsed, result = map(int, payload.split())
|
|
||||||
if result != 1:
|
|
||||||
raise RuntimeError(f"unexpected cold rpmsetcmp result: {result}")
|
|
||||||
return elapsed
|
|
||||||
|
|
||||||
|
|
||||||
def median_cmp_cold(api, provider, requirement, calls, rounds):
|
|
||||||
samples = []
|
samples = []
|
||||||
for _ in range(rounds):
|
for _ in range(args.rounds):
|
||||||
total = sum(cold_cmp_once(api, provider, requirement) for _ in range(calls))
|
with tempfile.NamedTemporaryFile(prefix="arsv-time-", delete=False) as handle:
|
||||||
samples.append(total / calls)
|
time_path = Path(handle.name)
|
||||||
return statistics.median(samples)
|
command = [
|
||||||
|
TIME_COMMAND,
|
||||||
|
"-f",
|
||||||
|
TIME_FORMAT,
|
||||||
|
"-o",
|
||||||
|
str(time_path),
|
||||||
|
"--",
|
||||||
|
sys.executable,
|
||||||
|
str(Path(__file__).resolve()),
|
||||||
|
"--skip-build",
|
||||||
|
"--time-child",
|
||||||
|
"--time-child-impl",
|
||||||
|
implementation,
|
||||||
|
"--time-child-operation",
|
||||||
|
operation,
|
||||||
|
"--symbols",
|
||||||
|
str(args.symbols),
|
||||||
|
"--bpp",
|
||||||
|
str(args.bpp),
|
||||||
|
"--rounds",
|
||||||
|
"1",
|
||||||
|
"--fini-calls",
|
||||||
|
str(args.fini_calls),
|
||||||
|
"--cmp-calls",
|
||||||
|
str(args.cmp_calls),
|
||||||
|
"--cold-calls",
|
||||||
|
str(args.cold_calls),
|
||||||
|
]
|
||||||
|
if args.required is not None:
|
||||||
|
command.extend(["--required", str(args.required)])
|
||||||
|
try:
|
||||||
|
subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
user_text, system_text = time_path.read_text().split()
|
||||||
|
finally:
|
||||||
|
time_path.unlink(missing_ok=True)
|
||||||
|
samples.append((float(user_text) / calls, float(system_text) / calls))
|
||||||
|
users = [sample[0] for sample in samples]
|
||||||
|
systems = [sample[1] for sample in samples]
|
||||||
|
return statistics.median(users), statistics.median(systems)
|
||||||
|
|
||||||
|
|
||||||
def verify_complete_decoding(api, provider, requirement):
|
def verify_complete_decoding(api, provider, requirement):
|
||||||
@@ -167,11 +195,17 @@ def verify_complete_decoding(api, provider, requirement):
|
|||||||
raise RuntimeError("second operand was not decoded and validated completely")
|
raise RuntimeError("second operand was not decoded and validated completely")
|
||||||
|
|
||||||
|
|
||||||
def format_time(ns):
|
def format_cpu_time(seconds):
|
||||||
return f"{ns / 1000:.2f} us"
|
return f"{seconds * 1_000_000:.2f} us"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def format_ratio(new, old):
|
||||||
|
if old == 0:
|
||||||
|
return "n/a"
|
||||||
|
return f"{new / old:.2f}x"
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
parser = argparse.ArgumentParser(description="Compare set9 and direct-hash set APIs")
|
parser = argparse.ArgumentParser(description="Compare set9 and direct-hash set APIs")
|
||||||
parser.add_argument("--symbols", type=int, default=1000)
|
parser.add_argument("--symbols", type=int, default=1000)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -185,6 +219,18 @@ def main():
|
|||||||
parser.add_argument("--cmp-calls", type=int, default=2000)
|
parser.add_argument("--cmp-calls", type=int, default=2000)
|
||||||
parser.add_argument("--cold-calls", type=int, default=20)
|
parser.add_argument("--cold-calls", type=int, default=20)
|
||||||
parser.add_argument("--skip-build", action="store_true")
|
parser.add_argument("--skip-build", action="store_true")
|
||||||
|
parser.add_argument("--time-child", action="store_true", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("--time-child-impl", choices=("set9", "direct"), help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument(
|
||||||
|
"--time-child-operation",
|
||||||
|
choices=("fini", "add", "build", "cmp-cold", "cmp-warm"),
|
||||||
|
help=argparse.SUPPRESS,
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = build_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.symbols < 2 or not 10 <= args.bpp <= 32:
|
if args.symbols < 2 or not 10 <= args.bpp <= 32:
|
||||||
parser.error("symbols must be >= 2 and bpp must be in 10..32")
|
parser.error("symbols must be >= 2 and bpp must be in 10..32")
|
||||||
@@ -192,26 +238,27 @@ def main():
|
|||||||
parser.error("required must be in 1..symbols-1")
|
parser.error("required must be in 1..symbols-1")
|
||||||
if min(args.rounds, args.fini_calls, args.cmp_calls, args.cold_calls) < 1:
|
if min(args.rounds, args.fini_calls, args.cmp_calls, args.cold_calls) < 1:
|
||||||
parser.error("rounds and call counts must be positive")
|
parser.error("rounds and call counts must be positive")
|
||||||
|
if args.time_child and not (args.time_child_impl and args.time_child_operation):
|
||||||
|
parser.error("--time-child requires --time-child-impl and --time-child-operation")
|
||||||
|
|
||||||
if not args.skip_build:
|
if not args.skip_build:
|
||||||
subprocess.run([str(HERE / "build.sh")], check=True)
|
subprocess.run([str(HERE / "build.sh")], check=True)
|
||||||
|
|
||||||
symbols = tuple(
|
if args.time_child:
|
||||||
f"symbol_{i:08d}_version_ALT_{i % 97}".encode() for i in range(args.symbols)
|
child_main(args)
|
||||||
)
|
return
|
||||||
required = (
|
|
||||||
symbols[::2]
|
if not Path(TIME_COMMAND).is_file():
|
||||||
if args.required is None
|
raise RuntimeError(f"time executable not found: {TIME_COMMAND}")
|
||||||
else tuple(symbols[i * args.symbols // args.required] for i in range(args.required))
|
|
||||||
)
|
symbols, required = make_symbols(args)
|
||||||
apis = {
|
apis = {
|
||||||
"set9": SetAPI(BUILD / "libset9.so"),
|
"set9": api_for("set9"),
|
||||||
"direct": SetAPI(BUILD / "libdirect-hash.so"),
|
"direct": api_for("direct"),
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.disable()
|
gc.disable()
|
||||||
try:
|
try:
|
||||||
timings = {name: [[] for _ in range(5)] for name in apis}
|
|
||||||
lengths = {}
|
lengths = {}
|
||||||
encoded = {}
|
encoded = {}
|
||||||
for name, api in apis.items():
|
for name, api in apis.items():
|
||||||
@@ -221,31 +268,20 @@ def main():
|
|||||||
wire_format = "D1/base64" if provider.startswith(b"D1") else "golomb/base62"
|
wire_format = "D1/base64" if provider.startswith(b"D1") else "golomb/base62"
|
||||||
lengths[name] = (len(provider), wire_format)
|
lengths[name] = (len(provider), wire_format)
|
||||||
|
|
||||||
operations = (
|
|
||||||
lambda name, api: median_fini(api, symbols, args.bpp, args.fini_calls, 1),
|
|
||||||
lambda name, api: median_add(api, symbols, args.fini_calls, 1),
|
|
||||||
lambda name, api: median_build(api, symbols, args.bpp, args.fini_calls, 1),
|
|
||||||
lambda name, api: median_cmp_cold(
|
|
||||||
api, encoded[name][0], encoded[name][1], args.cold_calls, 1
|
|
||||||
),
|
|
||||||
lambda name, api: median_cmp(
|
|
||||||
api, encoded[name][0], encoded[name][1], args.cmp_calls, 1
|
|
||||||
),
|
|
||||||
)
|
|
||||||
names = tuple(apis)
|
|
||||||
for operation_index, operation in enumerate(operations):
|
|
||||||
for round_index in range(args.rounds):
|
|
||||||
order = names if round_index % 2 == 0 else tuple(reversed(names))
|
|
||||||
for name in order:
|
|
||||||
timings[name][operation_index].append(operation(name, apis[name]))
|
|
||||||
timings = {
|
|
||||||
name: tuple(statistics.median(samples) for samples in operation_samples)
|
|
||||||
for name, operation_samples in timings.items()
|
|
||||||
}
|
|
||||||
# Run validation after timing: forked cold samples must inherit an empty
|
|
||||||
# decoded-set cache from the parent process.
|
|
||||||
for name, api in apis.items():
|
for name, api in apis.items():
|
||||||
verify_complete_decoding(api, *encoded[name])
|
verify_complete_decoding(api, *encoded[name])
|
||||||
|
|
||||||
|
operations = (
|
||||||
|
("set_fini child", "fini", args.fini_calls),
|
||||||
|
("new+add child", "add", args.fini_calls),
|
||||||
|
("new+add+fini child", "build", args.fini_calls),
|
||||||
|
("rpmsetcmp cold", "cmp-cold", args.cold_calls),
|
||||||
|
("rpmsetcmp warm", "cmp-warm", args.cmp_calls),
|
||||||
|
)
|
||||||
|
timings = {name: [] for name in apis}
|
||||||
|
for label, operation, calls in operations:
|
||||||
|
for name in apis:
|
||||||
|
timings[name].append(measure_with_time(args, name, operation, calls))
|
||||||
finally:
|
finally:
|
||||||
gc.enable()
|
gc.enable()
|
||||||
|
|
||||||
@@ -253,18 +289,15 @@ def main():
|
|||||||
print("implementation set_chars format")
|
print("implementation set_chars format")
|
||||||
for name in apis:
|
for name in apis:
|
||||||
print(f"{name:<14} {lengths[name][0]:>9} {lengths[name][1]}")
|
print(f"{name:<14} {lengths[name][0]:>9} {lengths[name][1]}")
|
||||||
print("\noperation set9 direct direct/set9")
|
print("\noperation set9_user set9_sys direct_user direct_sys user_ratio sys_ratio")
|
||||||
labels = (
|
for index, (label, _operation, _calls) in enumerate(operations):
|
||||||
"set_fini only",
|
old_user, old_sys = timings["set9"][index]
|
||||||
"new+add (ctypes)",
|
new_user, new_sys = timings["direct"][index]
|
||||||
"new+add+fini (ctypes)",
|
print(
|
||||||
"rpmsetcmp cold",
|
f"{label:<22} {format_cpu_time(old_user):>10} {format_cpu_time(old_sys):>9} "
|
||||||
"rpmsetcmp warm",
|
f"{format_cpu_time(new_user):>11} {format_cpu_time(new_sys):>10} "
|
||||||
|
f"{format_ratio(new_user, old_user):>10} {format_ratio(new_sys, old_sys):>9}"
|
||||||
)
|
)
|
||||||
for index, label in enumerate(labels):
|
|
||||||
old = timings["set9"][index]
|
|
||||||
new = timings["direct"][index]
|
|
||||||
print(f"{label:<22} {format_time(old):>10} {format_time(new):>10} {new / old:>12.2f}x")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+25
-18
@@ -16,6 +16,7 @@ PERF_EVENTS='task-clock,context-switches,cpu-migrations,page-faults,minor-faults
|
|||||||
PACKAGER='krosh <gudovdo@my.msu.ru>'
|
PACKAGER='krosh <gudovdo@my.msu.ru>'
|
||||||
APT_SOURCE=/etc/apt/sources.list.d/alt.list
|
APT_SOURCE=/etc/apt/sources.list.d/alt.list
|
||||||
APT_GET=/usr/lib/apt/apt-get
|
APT_GET=/usr/lib/apt/apt-get
|
||||||
|
TIME_COMMAND=/usr/bin/time
|
||||||
RPM_BUILD_GIT=https://git.altlinux.org/gears/r/rpm-build.git
|
RPM_BUILD_GIT=https://git.altlinux.org/gears/r/rpm-build.git
|
||||||
RPM_GIT=https://git.altlinux.org/gears/r/rpm.git
|
RPM_GIT=https://git.altlinux.org/gears/r/rpm.git
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ operation_label()
|
|||||||
|
|
||||||
run_once()
|
run_once()
|
||||||
{
|
{
|
||||||
local operation=$1 variant=$2 run=$3 start end status perf_stat
|
local operation=$1 variant=$2 run=$3 status perf_stat time_file
|
||||||
local libdir="$variant/lib/usr/lib64"
|
local libdir="$variant/lib/usr/lib64"
|
||||||
local root="$COMMON/root"
|
local root="$COMMON/root"
|
||||||
local raw="$variant/raw"
|
local raw="$variant/raw"
|
||||||
@@ -117,8 +118,9 @@ run_once()
|
|||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
start=$(date +%s%N)
|
time_file="$raw/$operation.$run.time.tsv"
|
||||||
if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \
|
if "$TIME_COMMAND" -f $'%U\t%S' -o "$time_file" -- \
|
||||||
|
env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \
|
||||||
LD_LIBRARY_PATH="$libdir" \
|
LD_LIBRARY_PATH="$libdir" \
|
||||||
taskset -c "$CPU" "${command[@]}" \
|
taskset -c "$CPU" "${command[@]}" \
|
||||||
>"$raw/$operation.$run.stdout" \
|
>"$raw/$operation.$run.stdout" \
|
||||||
@@ -127,10 +129,7 @@ run_once()
|
|||||||
else
|
else
|
||||||
status=$?
|
status=$?
|
||||||
fi
|
fi
|
||||||
end=$(date +%s%N)
|
read -r RUN_USER_TIME RUN_SYSTEM_TIME <"$time_file"
|
||||||
|
|
||||||
RUN_TIME=$(awk -v start="$start" -v end="$end" \
|
|
||||||
'BEGIN { printf "%.6f", (end - start) / 1000000000 }')
|
|
||||||
RUN_STATUS=$status
|
RUN_STATUS=$status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +227,8 @@ benchmark_variant()
|
|||||||
{
|
{
|
||||||
local variant=$1 result=$2 debug_file=$3 debuginfo_rpm=$4
|
local variant=$1 result=$2 debug_file=$3 debuginfo_rpm=$4
|
||||||
local runtime_file=$5 dso_name=$6
|
local runtime_file=$5 dso_name=$6
|
||||||
local operation run average label status_text perf_result perf_dir
|
local operation run average_user average_system label status_text perf_result perf_dir
|
||||||
local -a times statuses
|
local -a user_times system_times statuses
|
||||||
local -a operations=(
|
local -a operations=(
|
||||||
check
|
check
|
||||||
autoremove
|
autoremove
|
||||||
@@ -241,7 +240,7 @@ benchmark_variant()
|
|||||||
)
|
)
|
||||||
|
|
||||||
mkdir -p "$variant/raw/perf-stat"
|
mkdir -p "$variant/raw/perf-stat"
|
||||||
printf 'command\taverage_seconds\trun1_seconds\trun2_seconds\trun3_seconds\texit_status\n' \
|
printf 'command\taverage_user_seconds\taverage_system_seconds\trun1_user_seconds\trun1_system_seconds\trun2_user_seconds\trun2_system_seconds\trun3_user_seconds\trun3_system_seconds\texit_status\n' \
|
||||||
>"$result"
|
>"$result"
|
||||||
perf_result=${result%.tsv}.perf-stat.tsv
|
perf_result=${result%.tsv}.perf-stat.tsv
|
||||||
perf_dir=${result%.tsv}.perf
|
perf_dir=${result%.tsv}.perf
|
||||||
@@ -257,22 +256,27 @@ benchmark_variant()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for operation in "${operations[@]}"; do
|
for operation in "${operations[@]}"; do
|
||||||
times=()
|
user_times=()
|
||||||
|
system_times=()
|
||||||
statuses=()
|
statuses=()
|
||||||
|
|
||||||
for ((run = 1; run <= RUNS; ++run)); do
|
for ((run = 1; run <= RUNS; ++run)); do
|
||||||
run_once "$operation" "$variant" "$run"
|
run_once "$operation" "$variant" "$run"
|
||||||
times+=("$RUN_TIME")
|
user_times+=("$RUN_USER_TIME")
|
||||||
|
system_times+=("$RUN_SYSTEM_TIME")
|
||||||
statuses+=("$RUN_STATUS")
|
statuses+=("$RUN_STATUS")
|
||||||
if ((COLLECT_PERF)); then
|
if ((COLLECT_PERF)); then
|
||||||
append_perf_stat "$operation" "$run" \
|
append_perf_stat "$operation" "$run" \
|
||||||
"$variant/raw/perf-stat/$operation.$run.tsv" "$perf_result"
|
"$variant/raw/perf-stat/$operation.$run.tsv" "$perf_result"
|
||||||
fi
|
fi
|
||||||
printf '%s: run %d/%d: %ss, status=%s\n' \
|
printf '%s: run %d/%d: user=%ss system=%ss status=%s\n' \
|
||||||
"$operation" "$run" "$RUNS" "$RUN_TIME" "$RUN_STATUS"
|
"$operation" "$run" "$RUNS" \
|
||||||
|
"$RUN_USER_TIME" "$RUN_SYSTEM_TIME" "$RUN_STATUS"
|
||||||
done
|
done
|
||||||
|
|
||||||
average=$(printf '%s\n' "${times[@]}" | \
|
average_user=$(printf '%s\n' "${user_times[@]}" | \
|
||||||
|
awk '{ total += $1 } END { printf "%.6f", total / NR }')
|
||||||
|
average_system=$(printf '%s\n' "${system_times[@]}" | \
|
||||||
awk '{ total += $1 } END { printf "%.6f", total / NR }')
|
awk '{ total += $1 } END { printf "%.6f", total / NR }')
|
||||||
|
|
||||||
status_text=${statuses[0]}
|
status_text=${statuses[0]}
|
||||||
@@ -284,9 +288,11 @@ benchmark_variant()
|
|||||||
done
|
done
|
||||||
|
|
||||||
label=$(operation_label "$operation")
|
label=$(operation_label "$operation")
|
||||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||||
"$label" "$average" \
|
"$label" "$average_user" "$average_system" \
|
||||||
"${times[0]}" "${times[1]}" "${times[2]}" "$status_text" \
|
"${user_times[0]}" "${system_times[0]}" \
|
||||||
|
"${user_times[1]}" "${system_times[1]}" \
|
||||||
|
"${user_times[2]}" "${system_times[2]}" "$status_text" \
|
||||||
>>"$result"
|
>>"$result"
|
||||||
|
|
||||||
if ((COLLECT_PERF && PERF_RECORD)); then
|
if ((COLLECT_PERF && PERF_RECORD)); then
|
||||||
@@ -300,6 +306,7 @@ for command in git gear-hsh hsh rpm rpmquery rpm2cpio cpio apt-get apt-cache \
|
|||||||
taskset awk sed date sha256sum ldd readelf readlink eu-unstrip; do
|
taskset awk sed date sha256sum ldd readelf readlink eu-unstrip; do
|
||||||
command -v "$command" >/dev/null || fail "required command not found: $command"
|
command -v "$command" >/dev/null || fail "required command not found: $command"
|
||||||
done
|
done
|
||||||
|
[[ -x $TIME_COMMAND ]] || fail "time executable not found: $TIME_COMMAND"
|
||||||
if ((COLLECT_PERF)); then
|
if ((COLLECT_PERF)); then
|
||||||
command -v perf >/dev/null || \
|
command -v perf >/dev/null || \
|
||||||
fail "perf is not installed; install the ALT package: apt-get install perf"
|
fail "perf is not installed; install the ALT package: apt-get install perf"
|
||||||
|
|||||||
Reference in New Issue
Block a user