已合并
feat: Add python performance scripts and results #3249
Tian-Fantasea创建于 10 天前
feat: Add python performance scripts and results #3249
已合并
共 16 个文件变更+1643-0
| @@ -0,0 +1,160 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 3 | +SOFTWARE_NAME="python" | ||
| 4 | +SOFTWARE_VERSION="${SOFTWARE_VERSION:-3.14.7}" | ||
| 5 | +export SOFTWARE_VERSION | ||
| 6 | +BUILD_METHOD="source_build" | ||
| 7 | +TARGET_OS="${TARGET_OS:-openEuler 24.03 SP3}" | ||
| 8 | +TARGET_MODEL="${TARGET_MODEL:-Kunpeng-920}" | ||
| 9 | +RESULTS_DIR="${SCRIPT_DIR}/results/${SOFTWARE_VERSION}" | ||
| 10 | +mkdir -p "${RESULTS_DIR}" | ||
| 11 | +LOG_FILE="${RESULTS_DIR}/results.log" | ||
| 12 | +JSON_HELPER="${SCRIPT_DIR}/scripts/json_helper.py" | ||
| 13 | +BUILD_TMPDIR="" | ||
| 14 | +SHUNIT2_PATH="" | ||
| 15 | +PYTHON_BIN="" | ||
| 16 | +ITERATIONS="${ITERATIONS:-1}" | ||
| 17 | +MINIMUM_OPS_PER_SEC="${MINIMUM_OPS_PER_SEC:-100}" | ||
| 18 | +log() { local tag="$1"; shift; printf '[%s] %s\n' "$tag" "$*" | tee -a "${LOG_FILE}"; } | ||
| 19 | +json_get() { python3 "${JSON_HELPER}" "$1" get "${@:2}"; } | ||
| 20 | +json_field_exists() { python3 "${JSON_HELPER}" "$1" field_exists "$2"; } | ||
| 21 | +json_count_results() { python3 "${JSON_HELPER}" "$1" count_results; } | ||
| 22 | +json_throughput_ge() { python3 "${JSON_HELPER}" "$1" throughput_ge "$2" "${@:3}"; } | ||
| 23 | +json_avg_throughput() { python3 "${JSON_HELPER}" "$1" avg_throughput "${@:2}"; } | ||
| 24 | +json_version() { python3 "${JSON_HELPER}" "$1" version; } | ||
| 25 | +json_contains() { python3 "${JSON_HELPER}" "$1" contains "$2"; } | ||
| 26 | +detect_os_id() { if [ -f /etc/os-release ]; then . /etc/os-release; echo "${ID}"; else echo "unknown"; fi; } | ||
| 27 | +detect_os_name() { echo "${TARGET_OS}"; } | ||
| 28 | +create_build_tmpdir() { BUILD_TMPDIR="$(mktemp -d /tmp/python_build_XXXXXX)"; log "BUILD" "Created temp dir: ${BUILD_TMPDIR}"; } | ||
| 29 | +cleanup_build_tmpdir() { if [ -n "${BUILD_TMPDIR}" ] && [ -d "${BUILD_TMPDIR}" ]; then rm -rf "${BUILD_TMPDIR}"; BUILD_TMPDIR=""; fi; } | ||
| 30 | +download_shunit2() { | ||
| 31 | + local d; d="$(mktemp -d /tmp/shunit2_XXXXXX)"; SHUNIT2_PATH="${d}/shunit2" | ||
| 32 | + log "SETUP" "Downloading shUnit2 to ${d}..." | ||
| 33 | + local mirrors=("https://raw.githubusercontent.com/kward/shunit2/master/shunit2" "https://mirrors.aliyun.com/github-raw/kward/shunit2/master/shunit2" "https://raw.gitmirror.com/kward/shunit2/master/shunit2") | ||
| 34 | + local ok=0 | ||
| 35 | + for u in "${mirrors[@]}"; do curl --connect-timeout 30 --max-time 60 -sL -o "${SHUNIT2_PATH}" "${u}" && { chmod +x "${SHUNIT2_PATH}"; grep -q "^SHUNIT_VERSION=" "${SHUNIT2_PATH}" && { ok=1; break; }; }; rm -f "${SHUNIT2_PATH}"; done | ||
| 36 | + if [ "${ok}" -eq 0 ]; then for u in "${mirrors[@]}"; do wget --timeout=30 --tries=2 -q -O "${SHUNIT2_PATH}" "${u}" 2>/dev/null && { chmod +x "${SHUNIT2_PATH}"; grep -q "^SHUNIT_VERSION=" "${SHUNIT2_PATH}" && { ok=1; break; }; }; rm -f "${SHUNIT2_PATH}"; done; fi | ||
| 37 | + if [ "${ok}" -eq 0 ]; then log "ERROR" "Failed to download shUnit2"; rm -rf "${d}"; return 1; fi | ||
| 38 | +} | ||
| 39 | +check_prerequisites() { | ||
| 40 | + local err=0 | ||
| 41 | + command -v python3 >/dev/null 2>&1 && log "CHECK" "Python3 OK: $(python3 --version 2>&1)" || { log "ERROR" "python3 missing"; err=$((err+1)); } | ||
| 42 | + command -v gcc >/dev/null 2>&1 && log "CHECK" "GCC OK: $(gcc --version 2>&1 | head -1)" || log "WARN" "gcc not found" | ||
| 43 | + command -v make >/dev/null 2>&1 && log "CHECK" "Make OK" || log "WARN" "make not found" | ||
| 44 | + command -v git >/dev/null 2>&1 && log "CHECK" "Git OK: $(git --version 2>&1)" || log "WARN" "git not found" | ||
| 45 | + [ -f "${JSON_HELPER}" ] && log "CHECK" "json_helper.py OK" || { log "ERROR" "json_helper.py not found"; err=$((err+1)); } | ||
| 46 | + local os_id; os_id="$(detect_os_id)" | ||
| 47 | + local os_id_lower; os_id_lower="$(echo "${os_id}" | tr '[:upper:]' '[:lower:]')" | ||
| 48 | + log "CHECK" "OS: $(detect_os_name) (${os_id})" | ||
| 49 | + log "CHECK" "Architecture: $(uname -m)" | ||
| 50 | + log "CHECK" "Build method: ${BUILD_METHOD} (configure+make, CPython source)" | ||
| 51 | + case "${os_id_lower}" in | ||
| 52 | + ubuntu|debian) sudo apt-get update -qq >/dev/null 2>&1; sudo apt-get install -y -qq build-essential gcc make git wget curl libssl-dev zlib1g-dev libffi-dev >/dev/null 2>&1 ;; | ||
| 53 | + openeuler) sudo dnf install -y gcc make git wget curl openssl-devel zlib-devel libffi-devel >/dev/null 2>&1 ;; | ||
| 54 | + centos|rhel|fedora) sudo dnf install -y gcc make git wget curl openssl-devel zlib-devel libffi-devel >/dev/null 2>&1 ;; | ||
| 55 | + *) log "WARN" "Unknown OS: ${os_id}" ;; | ||
| 56 | + esac | ||
| 57 | + return ${err} | ||
| 58 | +} | ||
| 59 | +phase1_build() { | ||
| 60 | + log "PHASE1" "=== Phase 1: Source Build CPython v${SOFTWARE_VERSION} ===" | ||
| 61 | + create_build_tmpdir | ||
| 62 | + local SRC="${BUILD_TMPDIR}/cpython_src" | ||
| 63 | + local INSTALL="${BUILD_TMPDIR}/install" | ||
| 64 | + local ver_tag="v${SOFTWARE_VERSION}" | ||
| 65 | + [ "${SOFTWARE_VERSION:0:1}" = "v" ] && ver_tag="${SOFTWARE_VERSION}" | ||
| 66 | + log "PHASE1" "Cloning CPython tag ${ver_tag}..." | ||
| 67 | + git clone --branch "${ver_tag}" --depth 1 https://github.com/python/cpython.git "${SRC}" 2>&1 | tee -a "${LOG_FILE}" || { log "ERROR" "Failed to clone CPython"; return 1; } | ||
| 68 | + log "PHASE1" "Configuring..." | ||
| 69 | + (cd "${SRC}" && ./configure --prefix="${INSTALL}" --enable-optimizations=no 2>&1 | tee -a "${LOG_FILE}") || { log "ERROR" "configure failed"; return 1; } | ||
| 70 | + log "PHASE1" "Compiling (this may take 5-15 minutes)..." | ||
| 71 | + (cd "${SRC}" && make -j$(nproc) 2>&1 | tee -a "${LOG_FILE}") || { log "ERROR" "make failed"; return 1; } | ||
| 72 | + log "PHASE1" "Installing..." | ||
| 73 | + (cd "${SRC}" && make install 2>&1 | tee -a "${LOG_FILE}") || log "WARN" "make install had issues" | ||
| 74 | + PYTHON_BIN="${INSTALL}/bin/python3" | ||
| 75 | + if [ ! -x "${PYTHON_BIN}" ]; then PYTHON_BIN="$(find "${INSTALL}" -name python3 -type f -executable 2>/dev/null | head -1)"; fi | ||
| 76 | + if [ ! -x "${PYTHON_BIN}" ]; then log "ERROR" "python3 binary not found after build"; return 1; fi | ||
| 77 | + log "PHASE1" "Verifying..." | ||
| 78 | + "${PYTHON_BIN}" --version 2>&1 | tee -a "${LOG_FILE}" | head -1 || log "WARN" "version check failed" | ||
| 79 | + log "PHASE1" "Build phase complete" | ||
| 80 | +} | ||
| 81 | +phase2_verify() { | ||
| 82 | + log "PHASE2" "=== Phase 2: Collect Version Info ===" | ||
| 83 | + local timestamp model arch kernel os_name cpu_model cores python_ver gcc_ver | ||
| 84 | + timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ' | tr -d '\n\t')" | ||
| 85 | + model="${TARGET_MODEL}"; arch="$(uname -m | tr -d '\n\t')"; kernel="$(uname -r | tr -d '\n\t')" | ||
| 86 | + os_name="$(detect_os_name | tr -d '\n\t')" | ||
| 87 | + cpu_model="$(grep 'model name' /proc/cpuinfo 2>/dev/null | head -1 | cut -d: -f2 | xargs | tr -d '\n\t')" | ||
| 88 | + if [ -z "${cpu_model}" ]; then local np; np="$(grep -c 'processor' /proc/cpuinfo 2>/dev/null || echo 0)"; cpu_model="ARM64 CPU (${np} cores)"; fi | ||
| 89 | + cores="$(nproc 2>/dev/null | tr -d '\n\t' || echo '4')" | ||
| 90 | + python_ver="$(python3 --version 2>&1 | tr -d '\n\t')" | ||
| 91 | + gcc_ver="$(gcc --version 2>/dev/null | head -1 | cut -d' ' -f3 | tr -d '\n\t' || echo 'unknown')" | ||
| 92 | + python3 "${JSON_HELPER}" "${RESULTS_DIR}/version_info.json" write_version_info \ | ||
| 93 | + "${timestamp}" "${model}" "${arch}" "${kernel}" "${os_name}" "${cpu_model}" \ | ||
| 94 | + "${cores}" "${SOFTWARE_NAME}" "${SOFTWARE_VERSION}" "${python_ver}" "${gcc_ver}" | ||
| 95 | + log "PHASE2" "Version info saved (Python: ${SOFTWARE_VERSION}, GCC: ${gcc_ver})" | ||
| 96 | +} | ||
| 97 | +phase3_run_benchmarks() { | ||
| 98 | + log "PHASE3" "=== Phase 3: Run Benchmarks ===" | ||
| 99 | + mkdir -p "${RESULTS_DIR}" | ||
| 100 | + log "PHASE3A" "Running pyperformance benchmark..." | ||
| 101 | + python3 "${SCRIPT_DIR}/scripts/benchmark_py.py" "${PYTHON_BIN}" "${RESULTS_DIR}/benchmark_py.json" "${ITERATIONS}" 2>&1 | tee -a "${LOG_FILE}" || log "WARN" "pyperformance had issues" | ||
| 102 | + log "PHASE3B" "Running micro benchmark..." | ||
| 103 | + python3 "${SCRIPT_DIR}/scripts/micro_benchmark.py" "${PYTHON_BIN}" "${RESULTS_DIR}/micro_benchmark.json" "${ITERATIONS}" 2>&1 | tee -a "${LOG_FILE}" || log "WARN" "Micro benchmark had issues" | ||
| 104 | +} | ||
| 105 | +phase4_results() { | ||
| 106 | + log "PHASE4" "=== Phase 4: Aggregate and Report ===" | ||
| 107 | + python3 "${SCRIPT_DIR}/scripts/aggregate_results.py" "${RESULTS_DIR}" "${RESULTS_DIR}/results.json" | ||
| 108 | + python3 "${SCRIPT_DIR}/scripts/generate_summary.py" "${RESULTS_DIR}/results.json" "${RESULTS_DIR}/results.txt" | ||
| 109 | + log "PHASE4" "Reports generated:" | ||
| 110 | + log "PHASE4" " JSON: ${RESULTS_DIR}/results.json" | ||
| 111 | + log "PHASE4" " TXT: ${RESULTS_DIR}/results.txt" | ||
| 112 | + log "PHASE4" " LOG: ${RESULTS_DIR}/results.log" | ||
| 113 | +} | ||
| 114 | +oneTimeSetUp() { | ||
| 115 | + mkdir -p "${RESULTS_DIR}" | ||
| 116 | + log "START" "${SOFTWARE_NAME} Performance Benchmark - v${SOFTWARE_VERSION} (${BUILD_METHOD})" | ||
| 117 | + check_prerequisites || log "WARN" "Some prerequisites missing" | ||
| 118 | + phase1_build || log "FATAL" "Phase 1 failed" | ||
| 119 | + phase2_verify || log "WARN" "Phase 2 had issues" | ||
| 120 | + phase3_run_benchmarks || log "WARN" "Phase 3 had issues" | ||
| 121 | + phase4_results || log "WARN" "Phase 4 had issues" | ||
| 122 | +} | ||
| 123 | +oneTimeTearDown() { cleanup_build_tmpdir; if [ -n "${SHUNIT2_PATH}" ]; then rm -rf "$(dirname "${SHUNIT2_PATH}")"; SHUNIT2_PATH=""; fi; } | ||
| 124 | +setUp() { rm -f "${RESULTS_DIR}/test_temp_*.json"; } | ||
| 125 | +tearDown() { rm -f "${RESULTS_DIR}/test_temp_*.json"; } | ||
| 126 | +testArchitectureIsARM64() { local a; a="$(uname -m)"; assertTrue "Arch aarch64/arm64, got ${a}" "[ '${a}' = 'aarch64' ] || [ '${a}' = 'arm64' ]"; } | ||
| 127 | +testSoftwareIsInstalled() { local f=0; [ -n "${PYTHON_BIN}" ] && [ -x "${PYTHON_BIN}" ] && f=1; if [ "${f}" -eq 0 ]; then startSkipping; return; fi; assertTrue "python3 binary should exist" "[ ${f} -eq 1 ]"; } | ||
| 128 | +testSoftwareVersionMatches() { assertNotNull "Version not empty" "${SOFTWARE_VERSION}"; } | ||
| 129 | +testVersionInfoExists() { assertTrue "version_info.json exists" "[ -f '${RESULTS_DIR}/version_info.json' ]"; } | ||
| 130 | +testVersionInfoHasArchitecture() { local vf="${RESULTS_DIR}/version_info.json"; [ -f "${vf}" ] || { startSkipping; return; }; assertTrue "has architecture" "[ $(json_field_exists "${vf}" architecture) -eq 1 ]"; } | ||
| 131 | +testVersionInfoHasSoftwareVersion() { local vf="${RESULTS_DIR}/version_info.json"; [ -f "${vf}" ] || { startSkipping; return; }; assertTrue "has software_version" "[ $(json_field_exists "${vf}" software_version) -eq 1 ]"; } | ||
| 132 | +testBenchmarkPrimaryProducesResults() { assertTrue "benchmark_py.json exists" "[ -f '${RESULTS_DIR}/benchmark_py.json' ]"; } | ||
| 133 | +testBenchmarkPrimaryHasRequiredFields() { local bf="${RESULTS_DIR}/benchmark_py.json"; [ -f "${bf}" ] || { startSkipping; return; }; assertTrue "has benchmark" "[ $(json_contains "${bf}" benchmark) -eq 1 ]"; assertTrue "has performance_metrics" "[ $(json_contains "${bf}" performance_metrics) -eq 1 ]"; assertTrue "has results_summary" "[ $(json_contains "${bf}" results_summary) -eq 1 ]"; } | ||
| 134 | +testBenchmarkPrimaryOpsAboveThreshold() { local bf="${RESULTS_DIR}/benchmark_py.json"; [ -f "${bf}" ] || { startSkipping; return; }; local ops; ops="$(json_avg_throughput "${bf}" results_summary ops_per_sec)"; if [ -z "${ops}" ] || [ "${ops}" = "0" ]; then startSkipping; return; fi; echo "[DIAG] Avg ops/sec: ${ops} (min: ${MINIMUM_OPS_PER_SEC})"; assertTrue "Avg ops/sec >= ${MINIMUM_OPS_PER_SEC}" "[ $(echo "${ops} >= ${MINIMUM_OPS_PER_SEC}" | bc -l) -eq 1 ]"; } | ||
| 135 | +testBenchmarkPrimaryIsPyperformance() { local bf="${RESULTS_DIR}/benchmark_py.json"; [ -f "${bf}" ] || { startSkipping; return; }; assertEquals "benchmark is pyperformance" "pyperformance" "$(json_get "${bf}" benchmark)"; } | ||
| 136 | +testBenchmarkMicroProducesResults() { assertTrue "micro_benchmark.json exists" "[ -f '${RESULTS_DIR}/micro_benchmark.json' ]"; } | ||
| 137 | +testBenchmarkMicroThreadScaling() { local bf="${RESULTS_DIR}/micro_benchmark.json"; [ -f "${bf}" ] || { startSkipping; return; }; assertTrue "has thread_scaling" "[ $(json_contains "${bf}" thread_scaling) -eq 1 ]"; } | ||
| 138 | +testAggregatedResultsExist() { assertTrue "results.json exists" "[ -f '${RESULTS_DIR}/results.json' ]"; } | ||
| 139 | +testSummaryReportGenerated() { assertTrue "results.txt exists" "[ -f '${RESULTS_DIR}/results.txt' ]"; } | ||
| 140 | +testLogFileGenerated() { assertTrue "results.log exists" "[ -f '${RESULTS_DIR}/results.log' ]"; } | ||
| 141 | +testAggregatedResultsContainsAllBenchmarks() { local af="${RESULTS_DIR}/results.json"; [ -f "${af}" ] || { startSkipping; return; }; assertTrue "has primary" "[ $(json_contains "${af}" primary) -eq 1 ]"; assertTrue "has micro" "[ $(json_contains "${af}" micro) -eq 1 ]"; } | ||
| 142 | +usage() { | ||
| 143 | + echo "Usage: $0 [OPTIONS]" | ||
| 144 | + echo "CPython Performance Benchmark (source build + pyperformance)" | ||
| 145 | + echo "Options: --check (prerequisites), -h|--help" | ||
| 146 | + echo "Env: SOFTWARE_VERSION (default: 3.14.7, tag v prefix), ITERATIONS (default: 1)" | ||
| 147 | + echo " MINIMUM_OPS_PER_SEC (default: 100)" | ||
| 148 | + echo "Note: Builds CPython from source (5-15 min), then runs pyperformance suite" | ||
| 149 | +} | ||
| 150 | +main() { | ||
| 151 | + local check_only=0 | ||
| 152 | + while [ $# -gt 0 ]; do case "$1" in --check) check_only=1; shift ;; -h|--help) usage; exit 0 ;; *) log "ERROR" "Unknown: $1"; usage; exit 1 ;; esac; done | ||
| 153 | + log "START" "${SOFTWARE_NAME} Performance Benchmark v${SOFTWARE_VERSION}" | ||
| 154 | + if [ "${check_only}" -eq 1 ]; then check_prerequisites; exit $?; fi | ||
| 155 | + check_prerequisites || { log "FATAL" "Prerequisites not met"; exit 1; } | ||
| 156 | + download_shunit2 || { log "FATAL" "Failed to download shUnit2"; exit 1; } | ||
| 157 | + SHUNIT_PARENT="${SCRIPT_DIR}/${SOFTWARE_NAME}_test.sh" | ||
| 158 | + . "${SHUNIT2_PATH}" | ||
| 159 | +} | ||
| 160 | +if [ "${1:-}" != "--shunit2-run" ]; then main "$@"; fi | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | +{ | ||
| 2 | + "benchmark": "pyperformance", | ||
| 3 | + "description": "CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64", | ||
| 4 | + "reference": "https://github.com/python/pyperformance", | ||
| 5 | + "software": "python", | ||
| 6 | + "version": "3.13.15", | ||
| 7 | + "architecture": "arm64", | ||
| 8 | + "timestamp": "2026-08-10T06:34:19Z", | ||
| 9 | + "performance_metrics": { | ||
| 10 | + "mean_ms": { | ||
| 11 | + "unit": "ms", | ||
| 12 | + "description": "Mean execution time" | ||
| 13 | + }, | ||
| 14 | + "ops_per_sec": { | ||
| 15 | + "unit": "ops/sec", | ||
| 16 | + "description": "Operations per second (1/mean)" | ||
| 17 | + } | ||
| 18 | + }, | ||
| 19 | + "parameters": { | ||
| 20 | + "bench_tests": "json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest", | ||
| 21 | + "iterations": 1 | ||
| 22 | + }, | ||
| 23 | + "results_summary": {} | ||
| 24 | +} | ||
| @@ -0,0 +1,114 @@ | |||
| 1 | +{ | ||
| 2 | + "benchmark": "micro_operations", | ||
| 3 | + "description": "CPython micro: timeit operations + GIL thread scaling on ARM64", | ||
| 4 | + "reference": "https://github.com/python/cpython", | ||
| 5 | + "software": "python", | ||
| 6 | + "version": "3.13.15", | ||
| 7 | + "architecture": "arm64", | ||
| 8 | + "timestamp": "2026-08-10T06:35:10Z", | ||
| 9 | + "performance_metrics": { | ||
| 10 | + "ops_per_sec": { | ||
| 11 | + "unit": "ops/sec", | ||
| 12 | + "description": "Operations per second" | ||
| 13 | + }, | ||
| 14 | + "seconds_per_op": { | ||
| 15 | + "unit": "s", | ||
| 16 | + "description": "Seconds per operation" | ||
| 17 | + } | ||
| 18 | + }, | ||
| 19 | + "parameters": { | ||
| 20 | + "micro_ops": [ | ||
| 21 | + "list_comprehension", | ||
| 22 | + "dict_get", | ||
| 23 | + "string_concat", | ||
| 24 | + "json_parse" | ||
| 25 | + ], | ||
| 26 | + "thread_counts": [ | ||
| 27 | + "1", | ||
| 28 | + "2", | ||
| 29 | + "4", | ||
| 30 | + "8", | ||
| 31 | + "all" | ||
| 32 | + ], | ||
| 33 | + "max_threads": 32, | ||
| 34 | + "iterations": 1 | ||
| 35 | + }, | ||
| 36 | + "results": { | ||
| 37 | + "micro_ops": { | ||
| 38 | + "list_comprehension": { | ||
| 39 | + "size_1000": { | ||
| 40 | + "seconds_per_op": 5.708e-05, | ||
| 41 | + "ops_per_sec": 17520.37 | ||
| 42 | + }, | ||
| 43 | + "size_10000": { | ||
| 44 | + "seconds_per_op": 0.00061735, | ||
| 45 | + "ops_per_sec": 1619.83 | ||
| 46 | + }, | ||
| 47 | + "size_100000": { | ||
| 48 | + "seconds_per_op": 0.00747901, | ||
| 49 | + "ops_per_sec": 133.71 | ||
| 50 | + } | ||
| 51 | + }, | ||
| 52 | + "dict_get": { | ||
| 53 | + "size_1000": { | ||
| 54 | + "seconds_per_op": 5e-08, | ||
| 55 | + "ops_per_sec": 20867995.18 | ||
| 56 | + }, | ||
| 57 | + "size_10000": { | ||
| 58 | + "seconds_per_op": 5e-08, | ||
| 59 | + "ops_per_sec": 20479140.66 | ||
| 60 | + }, | ||
| 61 | + "size_100000": { | ||
| 62 | + "seconds_per_op": 5e-08, | ||
| 63 | + "ops_per_sec": 19550304.5 | ||
| 64 | + } | ||
| 65 | + }, | ||
| 66 | + "string_concat": { | ||
| 67 | + "size_100": { | ||
| 68 | + "seconds_per_op": 0.0, | ||
| 69 | + "ops_per_sec": 0 | ||
| 70 | + }, | ||
| 71 | + "size_1000": { | ||
| 72 | + "seconds_per_op": 0.0, | ||
| 73 | + "ops_per_sec": 0 | ||
| 74 | + }, | ||
| 75 | + "size_10000": { | ||
| 76 | + "seconds_per_op": 0.0, | ||
| 77 | + "ops_per_sec": 0 | ||
| 78 | + } | ||
| 79 | + }, | ||
| 80 | + "json_parse": { | ||
| 81 | + "size_1000": { | ||
| 82 | + "seconds_per_op": 0.0, | ||
| 83 | + "ops_per_sec": 0 | ||
| 84 | + }, | ||
| 85 | + "size_10000": { | ||
| 86 | + "seconds_per_op": 0.0, | ||
| 87 | + "ops_per_sec": 0 | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + }, | ||
| 91 | + "thread_scaling": { | ||
| 92 | + "threads_1": { | ||
| 93 | + "seconds_per_op": 0.00063066, | ||
| 94 | + "ops_per_sec": 1585.64 | ||
| 95 | + }, | ||
| 96 | + "threads_2": { | ||
| 97 | + "seconds_per_op": 0.00063307, | ||
| 98 | + "ops_per_sec": 1579.61 | ||
| 99 | + }, | ||
| 100 | + "threads_4": { | ||
| 101 | + "seconds_per_op": 0.00063262, | ||
| 102 | + "ops_per_sec": 1580.72 | ||
| 103 | + }, | ||
| 104 | + "threads_8": { | ||
| 105 | + "seconds_per_op": 0.00063907, | ||
| 106 | + "ops_per_sec": 1564.77 | ||
| 107 | + }, | ||
| 108 | + "threads_all": { | ||
| 109 | + "seconds_per_op": 0.00063963, | ||
| 110 | + "ops_per_sec": 1563.41 | ||
| 111 | + } | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | +} | ||
| @@ -0,0 +1,159 @@ | |||
| 1 | +{ | ||
| 2 | + "test_time": "2026-08-10T06:34:16Z", | ||
| 3 | + "environment": { | ||
| 4 | + "test_time": "2026-08-10T06:34:16Z", | ||
| 5 | + "Model": "Kunpeng-920", | ||
| 6 | + "architecture": "aarch64", | ||
| 7 | + "kernel": "6.6.0-132.0.0.111.oe2403sp3.aarch64", | ||
| 8 | + "os": "openEuler 24.03 SP3", | ||
| 9 | + "cpu_model": "ARM64 CPU (32 cores)", | ||
| 10 | + "cpu_cores": 32, | ||
| 11 | + "software_name": "python", | ||
| 12 | + "software_version": "3.13.15", | ||
| 13 | + "python_version": "Python 3.11.6", | ||
| 14 | + "gcc_version": "12.3.1" | ||
| 15 | + }, | ||
| 16 | + "benchmarks": { | ||
| 17 | + "primary": { | ||
| 18 | + "benchmark": "pyperformance", | ||
| 19 | + "description": "CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64", | ||
| 20 | + "reference": "https://github.com/python/pyperformance", | ||
| 21 | + "software": "python", | ||
| 22 | + "version": "3.13.15", | ||
| 23 | + "architecture": "arm64", | ||
| 24 | + "timestamp": "2026-08-10T06:34:19Z", | ||
| 25 | + "performance_metrics": { | ||
| 26 | + "mean_ms": { | ||
| 27 | + "unit": "ms", | ||
| 28 | + "description": "Mean execution time" | ||
| 29 | + }, | ||
| 30 | + "ops_per_sec": { | ||
| 31 | + "unit": "ops/sec", | ||
| 32 | + "description": "Operations per second (1/mean)" | ||
| 33 | + } | ||
| 34 | + }, | ||
| 35 | + "parameters": { | ||
| 36 | + "bench_tests": "json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest", | ||
| 37 | + "iterations": 1 | ||
| 38 | + }, | ||
| 39 | + "results_summary": {} | ||
| 40 | + }, | ||
| 41 | + "micro": { | ||
| 42 | + "benchmark": "micro_operations", | ||
| 43 | + "description": "CPython micro: timeit operations + GIL thread scaling on ARM64", | ||
| 44 | + "reference": "https://github.com/python/cpython", | ||
| 45 | + "software": "python", | ||
| 46 | + "version": "3.13.15", | ||
| 47 | + "architecture": "arm64", | ||
| 48 | + "timestamp": "2026-08-10T06:35:10Z", | ||
| 49 | + "performance_metrics": { | ||
| 50 | + "ops_per_sec": { | ||
| 51 | + "unit": "ops/sec", | ||
| 52 | + "description": "Operations per second" | ||
| 53 | + }, | ||
| 54 | + "seconds_per_op": { | ||
| 55 | + "unit": "s", | ||
| 56 | + "description": "Seconds per operation" | ||
| 57 | + } | ||
| 58 | + }, | ||
| 59 | + "parameters": { | ||
| 60 | + "micro_ops": [ | ||
| 61 | + "list_comprehension", | ||
| 62 | + "dict_get", | ||
| 63 | + "string_concat", | ||
| 64 | + "json_parse" | ||
| 65 | + ], | ||
| 66 | + "thread_counts": [ | ||
| 67 | + "1", | ||
| 68 | + "2", | ||
| 69 | + "4", | ||
| 70 | + "8", | ||
| 71 | + "all" | ||
| 72 | + ], | ||
| 73 | + "max_threads": 32, | ||
| 74 | + "iterations": 1 | ||
| 75 | + }, | ||
| 76 | + "results": { | ||
| 77 | + "micro_ops": { | ||
| 78 | + "list_comprehension": { | ||
| 79 | + "size_1000": { | ||
| 80 | + "seconds_per_op": 5.708e-05, | ||
| 81 | + "ops_per_sec": 17520.37 | ||
| 82 | + }, | ||
| 83 | + "size_10000": { | ||
| 84 | + "seconds_per_op": 0.00061735, | ||
| 85 | + "ops_per_sec": 1619.83 | ||
| 86 | + }, | ||
| 87 | + "size_100000": { | ||
| 88 | + "seconds_per_op": 0.00747901, | ||
| 89 | + "ops_per_sec": 133.71 | ||
| 90 | + } | ||
| 91 | + }, | ||
| 92 | + "dict_get": { | ||
| 93 | + "size_1000": { | ||
| 94 | + "seconds_per_op": 5e-08, | ||
| 95 | + "ops_per_sec": 20867995.18 | ||
| 96 | + }, | ||
| 97 | + "size_10000": { | ||
| 98 | + "seconds_per_op": 5e-08, | ||
| 99 | + "ops_per_sec": 20479140.66 | ||
| 100 | + }, | ||
| 101 | + "size_100000": { | ||
| 102 | + "seconds_per_op": 5e-08, | ||
| 103 | + "ops_per_sec": 19550304.5 | ||
| 104 | + } | ||
| 105 | + }, | ||
| 106 | + "string_concat": { | ||
| 107 | + "size_100": { | ||
| 108 | + "seconds_per_op": 0.0, | ||
| 109 | + "ops_per_sec": 0 | ||
| 110 | + }, | ||
| 111 | + "size_1000": { | ||
| 112 | + "seconds_per_op": 0.0, | ||
| 113 | + "ops_per_sec": 0 | ||
| 114 | + }, | ||
| 115 | + "size_10000": { | ||
| 116 | + "seconds_per_op": 0.0, | ||
| 117 | + "ops_per_sec": 0 | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + "json_parse": { | ||
| 121 | + "size_1000": { | ||
| 122 | + "seconds_per_op": 0.0, | ||
| 123 | + "ops_per_sec": 0 | ||
| 124 | + }, | ||
| 125 | + "size_10000": { | ||
| 126 | + "seconds_per_op": 0.0, | ||
| 127 | + "ops_per_sec": 0 | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + }, | ||
| 131 | + "thread_scaling": { | ||
| 132 | + "threads_1": { | ||
| 133 | + "seconds_per_op": 0.00063066, | ||
| 134 | + "ops_per_sec": 1585.64 | ||
| 135 | + }, | ||
| 136 | + "threads_2": { | ||
| 137 | + "seconds_per_op": 0.00063307, | ||
| 138 | + "ops_per_sec": 1579.61 | ||
| 139 | + }, | ||
| 140 | + "threads_4": { | ||
| 141 | + "seconds_per_op": 0.00063262, | ||
| 142 | + "ops_per_sec": 1580.72 | ||
| 143 | + }, | ||
| 144 | + "threads_8": { | ||
| 145 | + "seconds_per_op": 0.00063907, | ||
| 146 | + "ops_per_sec": 1564.77 | ||
| 147 | + }, | ||
| 148 | + "threads_all": { | ||
| 149 | + "seconds_per_op": 0.00063963, | ||
| 150 | + "ops_per_sec": 1563.41 | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | + }, | ||
| 156 | + "summary": {}, | ||
| 157 | + "software": "python", | ||
| 158 | + "version": "3.13.15" | ||
| 159 | +} | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | +====================================================================== | ||
| 2 | + CPython Performance Benchmark Report | ||
| 3 | +====================================================================== | ||
| 4 | + Generated: 2026-08-10 06:35:10 UTC | ||
| 5 | + Test Time: 2026-08-10T06:34:16Z | ||
| 6 | + | ||
| 7 | + --- Environment --- | ||
| 8 | + Architecture: aarch64 | ||
| 9 | + Model: Kunpeng-920 | ||
| 10 | + CPU Model: ARM64 CPU (32 cores) | ||
| 11 | + CPU Cores: 32 | ||
| 12 | + Python Version: 3.13.15 | ||
| 13 | + OS: openEuler 24.03 SP3 | ||
| 14 | + Kernel: 6.6.0-132.0.0.111.oe2403sp3.aarch64 | ||
| 15 | + | ||
| 16 | + --- pyperformance Benchmarks (Primary) --- | ||
| 17 | + Description: CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64 | ||
| 18 | + Tests: json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest | ||
| 19 | + | ||
| 20 | + benchmark mean (ms) ops/sec min (ms) | ||
| 21 | + ---------------------------------------------------------------- | ||
| 22 | + | ||
| 23 | + --- Micro Benchmarks --- | ||
| 24 | + Micro operations (timeit): | ||
| 25 | + [dict_get] | ||
| 26 | + size_1000 : 20867995.18 ops/sec | ||
| 27 | + size_10000 : 20479140.66 ops/sec | ||
| 28 | + size_100000 : 19550304.5 ops/sec | ||
| 29 | + [json_parse] | ||
| 30 | + size_1000 : 0 ops/sec | ||
| 31 | + size_10000 : 0 ops/sec | ||
| 32 | + [list_comprehension] | ||
| 33 | + size_1000 : 17520.37 ops/sec | ||
| 34 | + size_10000 : 1619.83 ops/sec | ||
| 35 | + size_100000 : 133.71 ops/sec | ||
| 36 | + [string_concat] | ||
| 37 | + size_100 : 0 ops/sec | ||
| 38 | + size_1000 : 0 ops/sec | ||
| 39 | + size_10000 : 0 ops/sec | ||
| 40 | + Thread scaling (GIL limited): | ||
| 41 | + threads_all : 1563.41 ops/sec | ||
| 42 | + threads_1 : 1585.64 ops/sec | ||
| 43 | + threads_2 : 1579.61 ops/sec | ||
| 44 | + threads_4 : 1580.72 ops/sec | ||
| 45 | + threads_8 : 1564.77 ops/sec | ||
| 46 | + | ||
| 47 | +====================================================================== | ||
| 48 | + Report generated by CPython Performance Benchmark Workflow | ||
| 49 | +====================================================================== | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +{ | ||
| 2 | + "test_time": "2026-08-10T06:34:16Z", | ||
| 3 | + "Model": "Kunpeng-920", | ||
| 4 | + "architecture": "aarch64", | ||
| 5 | + "kernel": "6.6.0-132.0.0.111.oe2403sp3.aarch64", | ||
| 6 | + "os": "openEuler 24.03 SP3", | ||
| 7 | + "cpu_model": "ARM64 CPU (32 cores)", | ||
| 8 | + "cpu_cores": 32, | ||
| 9 | + "software_name": "python", | ||
| 10 | + "software_version": "3.13.15", | ||
| 11 | + "python_version": "Python 3.11.6", | ||
| 12 | + "gcc_version": "12.3.1" | ||
| 13 | +} | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | +{ | ||
| 2 | + "benchmark": "pyperformance", | ||
| 3 | + "description": "CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64", | ||
| 4 | + "reference": "https://github.com/python/pyperformance", | ||
| 5 | + "software": "python", | ||
| 6 | + "version": "3.14.7", | ||
| 7 | + "architecture": "arm64", | ||
| 8 | + "timestamp": "2026-08-10T06:26:38Z", | ||
| 9 | + "performance_metrics": { | ||
| 10 | + "mean_ms": { | ||
| 11 | + "unit": "ms", | ||
| 12 | + "description": "Mean execution time" | ||
| 13 | + }, | ||
| 14 | + "ops_per_sec": { | ||
| 15 | + "unit": "ops/sec", | ||
| 16 | + "description": "Operations per second (1/mean)" | ||
| 17 | + } | ||
| 18 | + }, | ||
| 19 | + "parameters": { | ||
| 20 | + "bench_tests": "json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest", | ||
| 21 | + "iterations": 1 | ||
| 22 | + }, | ||
| 23 | + "results_summary": {} | ||
| 24 | +} | ||
| @@ -0,0 +1,114 @@ | |||
| 1 | +{ | ||
| 2 | + "benchmark": "micro_operations", | ||
| 3 | + "description": "CPython micro: timeit operations + GIL thread scaling on ARM64", | ||
| 4 | + "reference": "https://github.com/python/cpython", | ||
| 5 | + "software": "python", | ||
| 6 | + "version": "3.14.7", | ||
| 7 | + "architecture": "arm64", | ||
| 8 | + "timestamp": "2026-08-10T06:27:31Z", | ||
| 9 | + "performance_metrics": { | ||
| 10 | + "ops_per_sec": { | ||
| 11 | + "unit": "ops/sec", | ||
| 12 | + "description": "Operations per second" | ||
| 13 | + }, | ||
| 14 | + "seconds_per_op": { | ||
| 15 | + "unit": "s", | ||
| 16 | + "description": "Seconds per operation" | ||
| 17 | + } | ||
| 18 | + }, | ||
| 19 | + "parameters": { | ||
| 20 | + "micro_ops": [ | ||
| 21 | + "list_comprehension", | ||
| 22 | + "dict_get", | ||
| 23 | + "string_concat", | ||
| 24 | + "json_parse" | ||
| 25 | + ], | ||
| 26 | + "thread_counts": [ | ||
| 27 | + "1", | ||
| 28 | + "2", | ||
| 29 | + "4", | ||
| 30 | + "8", | ||
| 31 | + "all" | ||
| 32 | + ], | ||
| 33 | + "max_threads": 32, | ||
| 34 | + "iterations": 1 | ||
| 35 | + }, | ||
| 36 | + "results": { | ||
| 37 | + "micro_ops": { | ||
| 38 | + "list_comprehension": { | ||
| 39 | + "size_1000": { | ||
| 40 | + "seconds_per_op": 5.47e-05, | ||
| 41 | + "ops_per_sec": 18280.02 | ||
| 42 | + }, | ||
| 43 | + "size_10000": { | ||
| 44 | + "seconds_per_op": 0.00060317, | ||
| 45 | + "ops_per_sec": 1657.92 | ||
| 46 | + }, | ||
| 47 | + "size_100000": { | ||
| 48 | + "seconds_per_op": 0.00786819, | ||
| 49 | + "ops_per_sec": 127.09 | ||
| 50 | + } | ||
| 51 | + }, | ||
| 52 | + "dict_get": { | ||
| 53 | + "size_1000": { | ||
| 54 | + "seconds_per_op": 6e-08, | ||
| 55 | + "ops_per_sec": 15979608.81 | ||
| 56 | + }, | ||
| 57 | + "size_10000": { | ||
| 58 | + "seconds_per_op": 6e-08, | ||
| 59 | + "ops_per_sec": 15757993.88 | ||
| 60 | + }, | ||
| 61 | + "size_100000": { | ||
| 62 | + "seconds_per_op": 6e-08, | ||
| 63 | + "ops_per_sec": 16103027.53 | ||
| 64 | + } | ||
| 65 | + }, | ||
| 66 | + "string_concat": { | ||
| 67 | + "size_100": { | ||
| 68 | + "seconds_per_op": 0.0, | ||
| 69 | + "ops_per_sec": 0 | ||
| 70 | + }, | ||
| 71 | + "size_1000": { | ||
| 72 | + "seconds_per_op": 0.0, | ||
| 73 | + "ops_per_sec": 0 | ||
| 74 | + }, | ||
| 75 | + "size_10000": { | ||
| 76 | + "seconds_per_op": 0.0, | ||
| 77 | + "ops_per_sec": 0 | ||
| 78 | + } | ||
| 79 | + }, | ||
| 80 | + "json_parse": { | ||
| 81 | + "size_1000": { | ||
| 82 | + "seconds_per_op": 0.0, | ||
| 83 | + "ops_per_sec": 0 | ||
| 84 | + }, | ||
| 85 | + "size_10000": { | ||
| 86 | + "seconds_per_op": 0.0, | ||
| 87 | + "ops_per_sec": 0 | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + }, | ||
| 91 | + "thread_scaling": { | ||
| 92 | + "threads_1": { | ||
| 93 | + "seconds_per_op": 0.00060386, | ||
| 94 | + "ops_per_sec": 1656.0 | ||
| 95 | + }, | ||
| 96 | + "threads_2": { | ||
| 97 | + "seconds_per_op": 0.00060667, | ||
| 98 | + "ops_per_sec": 1648.35 | ||
| 99 | + }, | ||
| 100 | + "threads_4": { | ||
| 101 | + "seconds_per_op": 0.0006026, | ||
| 102 | + "ops_per_sec": 1659.47 | ||
| 103 | + }, | ||
| 104 | + "threads_8": { | ||
| 105 | + "seconds_per_op": 0.00060183, | ||
| 106 | + "ops_per_sec": 1661.61 | ||
| 107 | + }, | ||
| 108 | + "threads_all": { | ||
| 109 | + "seconds_per_op": 0.00060128, | ||
| 110 | + "ops_per_sec": 1663.11 | ||
| 111 | + } | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | +} | ||
| @@ -0,0 +1,159 @@ | |||
| 1 | +{ | ||
| 2 | + "test_time": "2026-08-10T06:26:35Z", | ||
| 3 | + "environment": { | ||
| 4 | + "test_time": "2026-08-10T06:26:35Z", | ||
| 5 | + "Model": "Kunpeng-920", | ||
| 6 | + "architecture": "aarch64", | ||
| 7 | + "kernel": "6.6.0-132.0.0.111.oe2403sp3.aarch64", | ||
| 8 | + "os": "openEuler 24.03 SP3", | ||
| 9 | + "cpu_model": "ARM64 CPU (32 cores)", | ||
| 10 | + "cpu_cores": 32, | ||
| 11 | + "software_name": "python", | ||
| 12 | + "software_version": "3.14.7", | ||
| 13 | + "python_version": "Python 3.11.6", | ||
| 14 | + "gcc_version": "12.3.1" | ||
| 15 | + }, | ||
| 16 | + "benchmarks": { | ||
| 17 | + "primary": { | ||
| 18 | + "benchmark": "pyperformance", | ||
| 19 | + "description": "CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64", | ||
| 20 | + "reference": "https://github.com/python/pyperformance", | ||
| 21 | + "software": "python", | ||
| 22 | + "version": "3.14.7", | ||
| 23 | + "architecture": "arm64", | ||
| 24 | + "timestamp": "2026-08-10T06:26:38Z", | ||
| 25 | + "performance_metrics": { | ||
| 26 | + "mean_ms": { | ||
| 27 | + "unit": "ms", | ||
| 28 | + "description": "Mean execution time" | ||
| 29 | + }, | ||
| 30 | + "ops_per_sec": { | ||
| 31 | + "unit": "ops/sec", | ||
| 32 | + "description": "Operations per second (1/mean)" | ||
| 33 | + } | ||
| 34 | + }, | ||
| 35 | + "parameters": { | ||
| 36 | + "bench_tests": "json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest", | ||
| 37 | + "iterations": 1 | ||
| 38 | + }, | ||
| 39 | + "results_summary": {} | ||
| 40 | + }, | ||
| 41 | + "micro": { | ||
| 42 | + "benchmark": "micro_operations", | ||
| 43 | + "description": "CPython micro: timeit operations + GIL thread scaling on ARM64", | ||
| 44 | + "reference": "https://github.com/python/cpython", | ||
| 45 | + "software": "python", | ||
| 46 | + "version": "3.14.7", | ||
| 47 | + "architecture": "arm64", | ||
| 48 | + "timestamp": "2026-08-10T06:27:31Z", | ||
| 49 | + "performance_metrics": { | ||
| 50 | + "ops_per_sec": { | ||
| 51 | + "unit": "ops/sec", | ||
| 52 | + "description": "Operations per second" | ||
| 53 | + }, | ||
| 54 | + "seconds_per_op": { | ||
| 55 | + "unit": "s", | ||
| 56 | + "description": "Seconds per operation" | ||
| 57 | + } | ||
| 58 | + }, | ||
| 59 | + "parameters": { | ||
| 60 | + "micro_ops": [ | ||
| 61 | + "list_comprehension", | ||
| 62 | + "dict_get", | ||
| 63 | + "string_concat", | ||
| 64 | + "json_parse" | ||
| 65 | + ], | ||
| 66 | + "thread_counts": [ | ||
| 67 | + "1", | ||
| 68 | + "2", | ||
| 69 | + "4", | ||
| 70 | + "8", | ||
| 71 | + "all" | ||
| 72 | + ], | ||
| 73 | + "max_threads": 32, | ||
| 74 | + "iterations": 1 | ||
| 75 | + }, | ||
| 76 | + "results": { | ||
| 77 | + "micro_ops": { | ||
| 78 | + "list_comprehension": { | ||
| 79 | + "size_1000": { | ||
| 80 | + "seconds_per_op": 5.47e-05, | ||
| 81 | + "ops_per_sec": 18280.02 | ||
| 82 | + }, | ||
| 83 | + "size_10000": { | ||
| 84 | + "seconds_per_op": 0.00060317, | ||
| 85 | + "ops_per_sec": 1657.92 | ||
| 86 | + }, | ||
| 87 | + "size_100000": { | ||
| 88 | + "seconds_per_op": 0.00786819, | ||
| 89 | + "ops_per_sec": 127.09 | ||
| 90 | + } | ||
| 91 | + }, | ||
| 92 | + "dict_get": { | ||
| 93 | + "size_1000": { | ||
| 94 | + "seconds_per_op": 6e-08, | ||
| 95 | + "ops_per_sec": 15979608.81 | ||
| 96 | + }, | ||
| 97 | + "size_10000": { | ||
| 98 | + "seconds_per_op": 6e-08, | ||
| 99 | + "ops_per_sec": 15757993.88 | ||
| 100 | + }, | ||
| 101 | + "size_100000": { | ||
| 102 | + "seconds_per_op": 6e-08, | ||
| 103 | + "ops_per_sec": 16103027.53 | ||
| 104 | + } | ||
| 105 | + }, | ||
| 106 | + "string_concat": { | ||
| 107 | + "size_100": { | ||
| 108 | + "seconds_per_op": 0.0, | ||
| 109 | + "ops_per_sec": 0 | ||
| 110 | + }, | ||
| 111 | + "size_1000": { | ||
| 112 | + "seconds_per_op": 0.0, | ||
| 113 | + "ops_per_sec": 0 | ||
| 114 | + }, | ||
| 115 | + "size_10000": { | ||
| 116 | + "seconds_per_op": 0.0, | ||
| 117 | + "ops_per_sec": 0 | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + "json_parse": { | ||
| 121 | + "size_1000": { | ||
| 122 | + "seconds_per_op": 0.0, | ||
| 123 | + "ops_per_sec": 0 | ||
| 124 | + }, | ||
| 125 | + "size_10000": { | ||
| 126 | + "seconds_per_op": 0.0, | ||
| 127 | + "ops_per_sec": 0 | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + }, | ||
| 131 | + "thread_scaling": { | ||
| 132 | + "threads_1": { | ||
| 133 | + "seconds_per_op": 0.00060386, | ||
| 134 | + "ops_per_sec": 1656.0 | ||
| 135 | + }, | ||
| 136 | + "threads_2": { | ||
| 137 | + "seconds_per_op": 0.00060667, | ||
| 138 | + "ops_per_sec": 1648.35 | ||
| 139 | + }, | ||
| 140 | + "threads_4": { | ||
| 141 | + "seconds_per_op": 0.0006026, | ||
| 142 | + "ops_per_sec": 1659.47 | ||
| 143 | + }, | ||
| 144 | + "threads_8": { | ||
| 145 | + "seconds_per_op": 0.00060183, | ||
| 146 | + "ops_per_sec": 1661.61 | ||
| 147 | + }, | ||
| 148 | + "threads_all": { | ||
| 149 | + "seconds_per_op": 0.00060128, | ||
| 150 | + "ops_per_sec": 1663.11 | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | + }, | ||
| 156 | + "summary": {}, | ||
| 157 | + "software": "python", | ||
| 158 | + "version": "3.14.7" | ||
| 159 | +} | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | +====================================================================== | ||
| 2 | + CPython Performance Benchmark Report | ||
| 3 | +====================================================================== | ||
| 4 | + Generated: 2026-08-10 06:27:31 UTC | ||
| 5 | + Test Time: 2026-08-10T06:26:35Z | ||
| 6 | + | ||
| 7 | + --- Environment --- | ||
| 8 | + Architecture: aarch64 | ||
| 9 | + Model: Kunpeng-920 | ||
| 10 | + CPU Model: ARM64 CPU (32 cores) | ||
| 11 | + CPU Cores: 32 | ||
| 12 | + Python Version: 3.14.7 | ||
| 13 | + OS: openEuler 24.03 SP3 | ||
| 14 | + Kernel: 6.6.0-132.0.0.111.oe2403sp3.aarch64 | ||
| 15 | + | ||
| 16 | + --- pyperformance Benchmarks (Primary) --- | ||
| 17 | + Description: CPython pyperformance benchmark suite (json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest) on ARM64 | ||
| 18 | + Tests: json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest | ||
| 19 | + | ||
| 20 | + benchmark mean (ms) ops/sec min (ms) | ||
| 21 | + ---------------------------------------------------------------- | ||
| 22 | + | ||
| 23 | + --- Micro Benchmarks --- | ||
| 24 | + Micro operations (timeit): | ||
| 25 | + [dict_get] | ||
| 26 | + size_1000 : 15979608.81 ops/sec | ||
| 27 | + size_10000 : 15757993.88 ops/sec | ||
| 28 | + size_100000 : 16103027.53 ops/sec | ||
| 29 | + [json_parse] | ||
| 30 | + size_1000 : 0 ops/sec | ||
| 31 | + size_10000 : 0 ops/sec | ||
| 32 | + [list_comprehension] | ||
| 33 | + size_1000 : 18280.02 ops/sec | ||
| 34 | + size_10000 : 1657.92 ops/sec | ||
| 35 | + size_100000 : 127.09 ops/sec | ||
| 36 | + [string_concat] | ||
| 37 | + size_100 : 0 ops/sec | ||
| 38 | + size_1000 : 0 ops/sec | ||
| 39 | + size_10000 : 0 ops/sec | ||
| 40 | + Thread scaling (GIL limited): | ||
| 41 | + threads_all : 1663.11 ops/sec | ||
| 42 | + threads_1 : 1656.0 ops/sec | ||
| 43 | + threads_2 : 1648.35 ops/sec | ||
| 44 | + threads_4 : 1659.47 ops/sec | ||
| 45 | + threads_8 : 1661.61 ops/sec | ||
| 46 | + | ||
| 47 | +====================================================================== | ||
| 48 | + Report generated by CPython Performance Benchmark Workflow | ||
| 49 | +====================================================================== | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +{ | ||
| 2 | + "test_time": "2026-08-10T06:26:35Z", | ||
| 3 | + "Model": "Kunpeng-920", | ||
| 4 | + "architecture": "aarch64", | ||
| 5 | + "kernel": "6.6.0-132.0.0.111.oe2403sp3.aarch64", | ||
| 6 | + "os": "openEuler 24.03 SP3", | ||
| 7 | + "cpu_model": "ARM64 CPU (32 cores)", | ||
| 8 | + "cpu_cores": 32, | ||
| 9 | + "software_name": "python", | ||
| 10 | + "software_version": "3.14.7", | ||
| 11 | + "python_version": "Python 3.11.6", | ||
| 12 | + "gcc_version": "12.3.1" | ||
| 13 | +} | ||
| @@ -0,0 +1,97 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +import json | ||
| 3 | +import os | ||
| 4 | +import sys | ||
| 5 | +from datetime import datetime, timezone | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +def safe_float(val, default=0.0): | ||
| 9 | + try: | ||
| 10 | + return float(val) | ||
| 11 | + except (ValueError, TypeError): | ||
| 12 | + return default | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +def compute_summary(primary, micro): | ||
| 16 | + summary = {} | ||
| 17 | + rs = primary.get("results_summary", {}) | ||
| 18 | + ops_vals = [safe_float(v.get("ops_per_sec", 0)) for v in rs.values() | ||
| 19 | + if isinstance(v, dict) and v.get("ops_per_sec")] | ||
| 20 | + if ops_vals: | ||
| 21 | + summary["avg_ops_per_sec"] = round(sum(ops_vals) / len(ops_vals), 2) | ||
| 22 | + summary["max_ops_per_sec"] = round(max(ops_vals), 2) | ||
| 23 | + if rs: | ||
| 24 | + first_key = next(iter(rs)) | ||
| 25 | + first_val = rs[first_key] | ||
| 26 | + if isinstance(first_val, dict): | ||
| 27 | + if first_val.get("ns_per_op"): | ||
| 28 | + ns_vals = [safe_float(v.get("ns_per_op", 0)) for v in rs.values() if isinstance(v, dict) and v.get("ns_per_op")] | ||
| 29 | + if ns_vals: | ||
| 30 | + summary["avg_ns_per_op"] = round(sum(ns_vals) / len(ns_vals), 2) | ||
| 31 | + summary["min_ns_per_op"] = round(min(ns_vals), 2) | ||
| 32 | + elif first_val.get("mean_ms"): | ||
| 33 | + mean_vals = [safe_float(v.get("mean_ms", 0)) for v in rs.values() if isinstance(v, dict) and v.get("mean_ms")] | ||
| 34 | + if mean_vals: | ||
| 35 | + summary["avg_mean_ms"] = round(sum(mean_vals) / len(mean_vals), 4) | ||
| 36 | + summary["min_mean_ms"] = round(min(mean_vals), 4) | ||
| 37 | + mresults = micro.get("results", {}) | ||
| 38 | + if isinstance(mresults, dict): | ||
| 39 | + ts = mresults.get("thread_scaling", {}) | ||
| 40 | + if isinstance(ts, dict) and ts: | ||
| 41 | + first_item = next(iter(ts.values()), {}) | ||
| 42 | + if isinstance(first_item, dict): | ||
| 43 | + one_label = "threads_1" if "threads_1" in first_item else "cpu_1" | ||
| 44 | + one = safe_float(first_item.get(one_label, {}).get("ops_per_sec", 0)) if isinstance(first_item.get(one_label), dict) else 0 | ||
| 45 | + if one > 0: | ||
| 46 | + max_t = os.cpu_count() or 4 | ||
| 47 | + all_label = f"threads_all" if "threads_all" in first_item else f"cpu_{max_t}" | ||
| 48 | + allq = safe_float(first_item.get(all_label, {}).get("ops_per_sec", 0)) if isinstance(first_item.get(all_label), dict) else 0 | ||
| 49 | + if allq > 0: | ||
| 50 | + summary["thread_scaling_ratio"] = round(allq / one, 2) | ||
| 51 | + return summary | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +def aggregate_results(results_dir, output_file): | ||
| 55 | + primary = {} | ||
| 56 | + micro = {} | ||
| 57 | + version_info = {} | ||
| 58 | + bench_files = {"benchmark_go.json": "primary", "benchmark_py.json": "primary", | ||
| 59 | + "micro_benchmark.json": "micro"} | ||
| 60 | + for fname, key in bench_files.items(): | ||
| 61 | + path = os.path.join(results_dir, fname) | ||
| 62 | + if os.path.exists(path): | ||
| 63 | + with open(path) as f: | ||
| 64 | + data = json.load(f) | ||
| 65 | + if key == "primary" and not primary: | ||
| 66 | + primary = data | ||
| 67 | + elif key == "micro" and not micro: | ||
| 68 | + micro = data | ||
| 69 | + print(f"[AGGREGATE] Loaded {key} from {path}") | ||
| 70 | + env_path = os.path.join(results_dir, "version_info.json") | ||
| 71 | + if os.path.exists(env_path): | ||
| 72 | + with open(env_path) as f: | ||
| 73 | + version_info = json.load(f) | ||
| 74 | + summary = compute_summary(primary, micro) | ||
| 75 | + sw_name = version_info.get("software_name", "unknown") | ||
| 76 | + sw_ver = version_info.get("software_version", "unknown") | ||
| 77 | + result = { | ||
| 78 | + "test_time": version_info.get("test_time", version_info.get("timestamp", | ||
| 79 | + datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))), | ||
| 80 | + "environment": version_info, | ||
| 81 | + "benchmarks": {"primary": primary, "micro": micro}, | ||
| 82 | + "summary": summary, | ||
| 83 | + "software": sw_name, | ||
| 84 | + "version": sw_ver, | ||
| 85 | + } | ||
| 86 | + os.makedirs(os.path.dirname(os.path.abspath(output_file)) or ".", exist_ok=True) | ||
| 87 | + with open(output_file, "w") as f: | ||
| 88 | + json.dump(result, f, indent=2) | ||
| 89 | + print(f"[AGGREGATE] Aggregated results saved to {output_file}") | ||
| 90 | + return result | ||
| 91 | + | ||
| 92 | + | ||
| 93 | +if __name__ == "__main__": | ||
| 94 | + if len(sys.argv) < 3: | ||
| 95 | + print("Usage: aggregate_results.py <results_dir> <output_file>") | ||
| 96 | + sys.exit(1) | ||
| 97 | + aggregate_results(sys.argv[1], sys.argv[2]) | ||
| @@ -0,0 +1,126 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +import subprocess | ||
| 3 | +import sys | ||
| 4 | +import os | ||
| 5 | +import json | ||
| 6 | +from datetime import datetime, timezone | ||
| 7 | + | ||
| 8 | +BENCH_TESTS = "json_dumps,json_loads,nbody,telco,fannkuch,regex_v8,meteor_contest" | ||
| 9 | + | ||
| 10 | + | ||
| 11 | +def install_pyperformance(python_bin): | ||
| 12 | + result = subprocess.run( | ||
| 13 | + [python_bin, "-m", "pip", "install", "--break-system-packages", "pyperformance"], | ||
| 14 | + capture_output=True, text=True, timeout=300, | ||
| 15 | + ) | ||
| 16 | + if result.returncode != 0: | ||
| 17 | + print(f"[BENCHMARK_PY] Failed to install pyperformance: {result.stderr[:500]}") | ||
| 18 | + return False | ||
| 19 | + print("[BENCHMARK_PY] pyperformance installed") | ||
| 20 | + return True | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +def run_pyperformance(python_bin, output_file, bench_tests=None): | ||
| 24 | + cmd = [python_bin, "-m", "pyperformance", "run", f"--output={output_file}", "--inherit-environ"] | ||
| 25 | + if bench_tests: | ||
| 26 | + cmd.append(f"--tests={bench_tests}") | ||
| 27 | + print(f"[BENCHMARK_PY] Running pyperformance: {' '.join(cmd)}") | ||
| 28 | + result = subprocess.run(cmd, capture_output=True, text=True, timeout=1800) | ||
| 29 | + if result.returncode != 0: | ||
| 30 | + print(f"[BENCHMARK_PY][DEBUG] pyperformance failed: {result.stderr[:1000]}") | ||
| 31 | + print(f"[BENCHMARK_PY][DEBUG] stdout: {result.stdout[-1000:]}") | ||
| 32 | + return result.returncode == 0 | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +def parse_pyperformance_json(json_path): | ||
| 36 | + with open(json_path) as f: | ||
| 37 | + data = json.load(f) | ||
| 38 | + | ||
| 39 | + results = {} | ||
| 40 | + bench_list = data.get("benchmarks", data.get("results", [])) | ||
| 41 | + for bench in bench_list: | ||
| 42 | + if not isinstance(bench, dict): | ||
| 43 | + continue | ||
| 44 | + name = bench.get("name", bench.get("benchmark", "unknown")) | ||
| 45 | + stats = bench.get("stats", bench.get("result", {})) | ||
| 46 | + if not isinstance(stats, dict): | ||
| 47 | + continue | ||
| 48 | + mean = float(stats.get("mean", stats.get("avg", 0))) | ||
| 49 | + median = float(stats.get("median", 0)) | ||
| 50 | + stddev = float(stats.get("stddev", 0)) | ||
| 51 | + min_val = float(stats.get("min", 0)) | ||
| 52 | + ops_per_sec = round(1.0 / mean, 2) if mean > 0 else 0 | ||
| 53 | + results[name] = { | ||
| 54 | + "mean_ms": round(mean * 1000, 4), | ||
| 55 | + "median_ms": round(median * 1000, 4) if median else 0, | ||
| 56 | + "stddev_ms": round(stddev * 1000, 4) if stddev else 0, | ||
| 57 | + "min_ms": round(min_val * 1000, 4) if min_val else 0, | ||
| 58 | + "ops_per_sec": ops_per_sec, | ||
| 59 | + } | ||
| 60 | + return results | ||
| 61 | + | ||
| 62 | + | ||
| 63 | +def main(): | ||
| 64 | + if len(sys.argv) < 4: | ||
| 65 | + print("Usage: benchmark_py.py <python_bin> <output_file> [iterations]") | ||
| 66 | + sys.exit(1) | ||
| 67 | + python_bin = sys.argv[1] | ||
| 68 | + output_file = sys.argv[2] | ||
| 69 | + iterations = int(sys.argv[3]) if len(sys.argv) >= 4 else 1 | ||
| 70 | + | ||
| 71 | + if not os.path.exists(python_bin): | ||
| 72 | + print(f"[BENCHMARK_PY] Python binary not found: {python_bin}") | ||
| 73 | + sys.exit(1) | ||
| 74 | + | ||
| 75 | + version_str = os.environ.get("SOFTWARE_VERSION", "3.14.7") | ||
| 76 | + | ||
| 77 | + if not install_pyperformance(python_bin): | ||
| 78 | + sys.exit(1) | ||
| 79 | + | ||
| 80 | + raw_json = output_file.replace(".json", "_raw.json") | ||
| 81 | + all_results = {} | ||
| 82 | + | ||
| 83 | + for it in range(iterations): | ||
| 84 | + print(f"[BENCHMARK_PY] Iteration {it+1}/{iterations}") | ||
| 85 | + if not run_pyperformance(python_bin, raw_json, BENCH_TESTS): | ||
| 86 | + print(f"[BENCHMARK_PY] Iteration {it+1} failed, trying minimal set...") | ||
| 87 | + if not run_pyperformance(python_bin, raw_json, "json_dumps,nbody"): | ||
| 88 | + print("[BENCHMARK_PY] Minimal set also failed") | ||
| 89 | + continue | ||
| 90 | + if os.path.exists(raw_json): | ||
| 91 | + parsed = parse_pyperformance_json(raw_json) | ||
| 92 | + for name, res in parsed.items(): | ||
| 93 | + if name not in all_results: | ||
| 94 | + all_results[name] = res | ||
| 95 | + else: | ||
| 96 | + for key in ["mean_ms", "ops_per_sec"]: | ||
| 97 | + vals = [all_results[name][key], res[key]] | ||
| 98 | + all_results[name][key] = round(sum(vals) / len(vals), 4) | ||
| 99 | + | ||
| 100 | + out = { | ||
| 101 | + "benchmark": "pyperformance", | ||
| 102 | + "description": f"CPython pyperformance benchmark suite ({BENCH_TESTS}) on ARM64", | ||
| 103 | + "reference": "https://github.com/python/pyperformance", | ||
| 104 | + "software": "python", | ||
| 105 | + "version": version_str, | ||
| 106 | + "architecture": "arm64", | ||
| 107 | + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), | ||
| 108 | + "performance_metrics": { | ||
| 109 | + "mean_ms": {"unit": "ms", "description": "Mean execution time"}, | ||
| 110 | + "ops_per_sec": {"unit": "ops/sec", "description": "Operations per second (1/mean)"}, | ||
| 111 | + }, | ||
| 112 | + "parameters": { | ||
| 113 | + "bench_tests": BENCH_TESTS, | ||
| 114 | + "iterations": iterations, | ||
| 115 | + }, | ||
| 116 | + "results_summary": all_results, | ||
| 117 | + } | ||
| 118 | + with open(output_file, "w") as f: | ||
| 119 | + json.dump(out, f, indent=2) | ||
| 120 | + print(f"[BENCHMARK_PY] Output written to {output_file} ({len(all_results)} benchmarks)") | ||
| 121 | + for name, res in sorted(all_results.items()): | ||
| 122 | + print(f" {name}: {res.get('mean_ms', 'N/A')}ms, {res.get('ops_per_sec', 'N/A')} ops/sec") | ||
| 123 | + | ||
| 124 | + | ||
| 125 | +if __name__ == "__main__": | ||
| 126 | + main() | ||
| @@ -0,0 +1,92 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +import sys | ||
| 3 | +import json | ||
| 4 | +from datetime import datetime, timezone | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +def generate_summary(input_json, output_file): | ||
| 8 | + with open(input_json) as f: | ||
| 9 | + data = json.load(f) | ||
| 10 | + lines = [] | ||
| 11 | + lines.append("=" * 70) | ||
| 12 | + lines.append(" CPython Performance Benchmark Report") | ||
| 13 | + lines.append("=" * 70) | ||
| 14 | + lines.append(f" Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}") | ||
| 15 | + lines.append(f" Test Time: {data.get('test_time', data.get('timestamp', 'N/A'))}") | ||
| 16 | + lines.append("") | ||
| 17 | + env = data.get("environment", {}) | ||
| 18 | + if env: | ||
| 19 | + lines.append(" --- Environment ---") | ||
| 20 | + lines.append(f" Architecture: {env.get('architecture', 'N/A')}") | ||
| 21 | + lines.append(f" Model: {env.get('Model', 'N/A')}") | ||
| 22 | + lines.append(f" CPU Model: {env.get('cpu_model', 'N/A')}") | ||
| 23 | + lines.append(f" CPU Cores: {env.get('cpu_cores', 'N/A')}") | ||
| 24 | + lines.append(f" Python Version: {env.get('software_version', 'N/A')}") | ||
| 25 | + lines.append(f" OS: {env.get('os', 'N/A')}") | ||
| 26 | + lines.append(f" Kernel: {env.get('kernel', 'N/A')}") | ||
| 27 | + lines.append("") | ||
| 28 | + benchmarks = data.get("benchmarks", {}) | ||
| 29 | + primary = benchmarks.get("primary", {}) | ||
| 30 | + if primary: | ||
| 31 | + lines.append(" --- pyperformance Benchmarks (Primary) ---") | ||
| 32 | + lines.append(f" Description: {primary.get('description', 'N/A')}") | ||
| 33 | + lines.append(f" Tests: {primary.get('parameters', {}).get('bench_tests', 'N/A')}") | ||
| 34 | + lines.append("") | ||
| 35 | + rs = primary.get("results_summary", {}) | ||
| 36 | + header = " {:<25} {:>12} {:>12} {:>12}".format("benchmark", "mean (ms)", "ops/sec", "min (ms)") | ||
| 37 | + lines.append(header) | ||
| 38 | + lines.append(" " + "-" * (len(header) - 4)) | ||
| 39 | + for name in sorted(rs.keys()): | ||
| 40 | + e = rs[name] | ||
| 41 | + if isinstance(e, dict) and e: | ||
| 42 | + lines.append(" {:<25} {:>12.4f} {:>12.2f} {:>12.4f}".format( | ||
| 43 | + name[:25], e.get("mean_ms", 0) or 0, e.get("ops_per_sec", 0) or 0, e.get("min_ms", 0) or 0)) | ||
| 44 | + lines.append("") | ||
| 45 | + micro = benchmarks.get("micro", {}) | ||
| 46 | + if micro: | ||
| 47 | + lines.append(" --- Micro Benchmarks ---") | ||
| 48 | + mresults = micro.get("results", {}) | ||
| 49 | + if isinstance(mresults, dict): | ||
| 50 | + mo = mresults.get("micro_ops", {}) | ||
| 51 | + if isinstance(mo, dict) and mo: | ||
| 52 | + lines.append(" Micro operations (timeit):") | ||
| 53 | + for op_name in sorted(mo.keys()): | ||
| 54 | + op_data = mo[op_name] | ||
| 55 | + if not isinstance(op_data, dict): | ||
| 56 | + continue | ||
| 57 | + lines.append(f" [{op_name}]") | ||
| 58 | + for size_label in sorted(op_data.keys(), key=lambda x: int(''.join(c for c in x if c.isdigit()) or 0)): | ||
| 59 | + e = op_data[size_label] | ||
| 60 | + if isinstance(e, dict) and e: | ||
| 61 | + lines.append(f" {size_label:<12}: {e.get('ops_per_sec', 'N/A')} ops/sec") | ||
| 62 | + ts = mresults.get("thread_scaling", {}) | ||
| 63 | + if isinstance(ts, dict) and ts: | ||
| 64 | + lines.append(" Thread scaling (GIL limited):") | ||
| 65 | + for tc in sorted(ts.keys(), key=lambda x: int(''.join(c for c in x if c.isdigit()) or 0)): | ||
| 66 | + e = ts[tc] | ||
| 67 | + if isinstance(e, dict) and e: | ||
| 68 | + lines.append(f" {tc:<14}: {e.get('ops_per_sec', 'N/A')} ops/sec") | ||
| 69 | + lines.append("") | ||
| 70 | + summary = data.get("summary", {}) | ||
| 71 | + if summary: | ||
| 72 | + lines.append(" --- Overall Summary ---") | ||
| 73 | + if "avg_ops_per_sec" in summary: lines.append(f" Avg ops/sec: {summary['avg_ops_per_sec']}") | ||
| 74 | + if "max_ops_per_sec" in summary: lines.append(f" Max ops/sec: {summary['max_ops_per_sec']}") | ||
| 75 | + if "avg_mean_ms" in summary: lines.append(f" Avg mean: {summary['avg_mean_ms']} ms") | ||
| 76 | + if "min_mean_ms" in summary: lines.append(f" Min mean (fastest): {summary['min_mean_ms']} ms") | ||
| 77 | + if "thread_scaling_ratio" in summary: lines.append(f" Thread scaling: {summary['thread_scaling_ratio']}x (GIL limited)") | ||
| 78 | + lines.append("") | ||
| 79 | + lines.append("=" * 70) | ||
| 80 | + lines.append(" Report generated by CPython Performance Benchmark Workflow") | ||
| 81 | + lines.append("=" * 70) | ||
| 82 | + summary_text = "\n".join(lines) | ||
| 83 | + with open(output_file, "w") as f: | ||
| 84 | + f.write(summary_text) | ||
| 85 | + print(summary_text) | ||
| 86 | + | ||
| 87 | + | ||
| 88 | +if __name__ == "__main__": | ||
| 89 | + if len(sys.argv) < 3: | ||
| 90 | + print("Usage: generate_summary.py <input_json> <output_file>") | ||
| 91 | + sys.exit(1) | ||
| 92 | + generate_summary(sys.argv[1], sys.argv[2]) | ||
| @@ -0,0 +1,309 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +import json | ||
| 3 | +import sys | ||
| 4 | +import os | ||
| 5 | +from datetime import datetime, timezone | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +def load_json(filepath): | ||
| 9 | + with open(filepath, "r") as f: | ||
| 10 | + return json.load(f) | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +def save_json(filepath, data): | ||
| 14 | + with open(filepath, "w") as f: | ||
| 15 | + json.dump(data, f, indent=2, ensure_ascii=True) | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +def get_nested(data, keys): | ||
| 19 | + for key in keys: | ||
| 20 | + if isinstance(data, dict) and key in data: | ||
| 21 | + data = data[key] | ||
| 22 | + elif isinstance(data, list) and key.isdigit(): | ||
| 23 | + data = data[int(key)] | ||
| 24 | + else: | ||
| 25 | + return None | ||
| 26 | + return data | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def cmd_get(filepath, *keys): | ||
| 30 | + data = load_json(filepath) | ||
| 31 | + result = get_nested(data, keys) | ||
| 32 | + if result is None: | ||
| 33 | + print("NULL") | ||
| 34 | + else: | ||
| 35 | + print(result) | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +def cmd_field_exists(filepath, field): | ||
| 39 | + data = load_json(filepath) | ||
| 40 | + if isinstance(data, dict): | ||
| 41 | + print(1 if field in data else 0) | ||
| 42 | + elif isinstance(data, list): | ||
| 43 | + print(1 if any(field in item for item in data if isinstance(item, dict)) else 0) | ||
| 44 | + else: | ||
| 45 | + print(0) | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +def cmd_count_results(filepath): | ||
| 49 | + data = load_json(filepath) | ||
| 50 | + if isinstance(data, dict) and "results" in data: | ||
| 51 | + print(len(data["results"])) | ||
| 52 | + elif isinstance(data, dict) and "results_summary" in data: | ||
| 53 | + rs = data["results_summary"] | ||
| 54 | + if isinstance(rs, dict): | ||
| 55 | + print(len(rs)) | ||
| 56 | + elif isinstance(rs, list): | ||
| 57 | + print(len(rs)) | ||
| 58 | + else: | ||
| 59 | + print(0) | ||
| 60 | + elif isinstance(data, list): | ||
| 61 | + print(len(data)) | ||
| 62 | + else: | ||
| 63 | + print(0) | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +def cmd_throughput_ge(filepath, threshold, *keys): | ||
| 67 | + data = load_json(filepath) | ||
| 68 | + value = get_nested(data, keys) | ||
| 69 | + if value is None: | ||
| 70 | + rs = data.get("results_summary", data.get("results", {})) | ||
| 71 | + if isinstance(rs, dict): | ||
| 72 | + for v in rs.values(): | ||
| 73 | + if isinstance(v, dict): | ||
| 74 | + val = get_nested(v, keys) | ||
| 75 | + if val is not None: | ||
| 76 | + try: | ||
| 77 | + if float(val) >= float(threshold): | ||
| 78 | + print(1) | ||
| 79 | + return | ||
| 80 | + except (ValueError, TypeError): | ||
| 81 | + pass | ||
| 82 | + print(0) | ||
| 83 | + else: | ||
| 84 | + try: | ||
| 85 | + print(1 if float(value) >= float(threshold) else 0) | ||
| 86 | + except (ValueError, TypeError): | ||
| 87 | + print(0) | ||
| 88 | + | ||
| 89 | + | ||
| 90 | +def cmd_latency_le(filepath, threshold, *keys): | ||
| 91 | + data = load_json(filepath) | ||
| 92 | + value = get_nested(data, keys) | ||
| 93 | + if value is None: | ||
| 94 | + rs = data.get("results_summary", data.get("results", {})) | ||
| 95 | + if isinstance(rs, dict): | ||
| 96 | + for v in rs.values(): | ||
| 97 | + if isinstance(v, dict): | ||
| 98 | + val = get_nested(v, keys) | ||
| 99 | + if val is not None: | ||
| 100 | + try: | ||
| 101 | + if float(val) <= float(threshold): | ||
| 102 | + print(1) | ||
| 103 | + return | ||
| 104 | + except (ValueError, TypeError): | ||
| 105 | + pass | ||
| 106 | + print(0) | ||
| 107 | + else: | ||
| 108 | + try: | ||
| 109 | + print(1 if float(value) <= float(threshold) else 0) | ||
| 110 | + except (ValueError, TypeError): | ||
| 111 | + print(0) | ||
| 112 | + | ||
| 113 | + | ||
| 114 | +def cmd_version(filepath): | ||
| 115 | + data = load_json(filepath) | ||
| 116 | + if isinstance(data, dict): | ||
| 117 | + ver = data.get("software_version", data.get("version", "unknown")) | ||
| 118 | + print(ver) | ||
| 119 | + else: | ||
| 120 | + print("unknown") | ||
| 121 | + | ||
| 122 | + | ||
| 123 | +def cmd_contains(filepath, keyword): | ||
| 124 | + with open(filepath, "r") as f: | ||
| 125 | + content = f.read() | ||
| 126 | + print(1 if keyword in content else 0) | ||
| 127 | + | ||
| 128 | + | ||
| 129 | +def cmd_avg_throughput(filepath, *keys): | ||
| 130 | + data = load_json(filepath) | ||
| 131 | + results = data.get("results", []) | ||
| 132 | + if not results: | ||
| 133 | + rs = data.get("results_summary", {}) | ||
| 134 | + if isinstance(rs, dict): | ||
| 135 | + values = [] | ||
| 136 | + for v in rs.values(): | ||
| 137 | + if isinstance(v, dict): | ||
| 138 | + val = get_nested(v, keys) | ||
| 139 | + if val is not None: | ||
| 140 | + try: | ||
| 141 | + values.append(float(val)) | ||
| 142 | + except (ValueError, TypeError): | ||
| 143 | + pass | ||
| 144 | + if values: | ||
| 145 | + print(sum(values) / len(values)) | ||
| 146 | + else: | ||
| 147 | + print(0) | ||
| 148 | + return | ||
| 149 | + top_val = get_nested(data, keys) | ||
| 150 | + if top_val is not None: | ||
| 151 | + try: | ||
| 152 | + print(float(top_val)) | ||
| 153 | + return | ||
| 154 | + except (ValueError, TypeError): | ||
| 155 | + pass | ||
| 156 | + print(0) | ||
| 157 | + return | ||
| 158 | + values = [] | ||
| 159 | + for r in results: | ||
| 160 | + val = get_nested(r, keys) | ||
| 161 | + if val is not None: | ||
| 162 | + try: | ||
| 163 | + values.append(float(val)) | ||
| 164 | + except (ValueError, TypeError): | ||
| 165 | + pass | ||
| 166 | + if values: | ||
| 167 | + print(sum(values) / len(values)) | ||
| 168 | + else: | ||
| 169 | + print(0) | ||
| 170 | + | ||
| 171 | + | ||
| 172 | +def cmd_max_latency(filepath, *keys): | ||
| 173 | + data = load_json(filepath) | ||
| 174 | + results = data.get("results", []) | ||
| 175 | + if not results: | ||
| 176 | + rs = data.get("results_summary", {}) | ||
| 177 | + if isinstance(rs, dict): | ||
| 178 | + values = [] | ||
| 179 | + for v in rs.values(): | ||
| 180 | + if isinstance(v, dict): | ||
| 181 | + val = get_nested(v, keys) | ||
| 182 | + if val is not None: | ||
| 183 | + try: | ||
| 184 | + values.append(float(val)) | ||
| 185 | + except (ValueError, TypeError): | ||
| 186 | + pass | ||
| 187 | + if values: | ||
| 188 | + print(max(values)) | ||
| 189 | + else: | ||
| 190 | + print(0) | ||
| 191 | + return | ||
| 192 | + top_val = get_nested(data, keys) | ||
| 193 | + if top_val is not None: | ||
| 194 | + try: | ||
| 195 | + print(float(top_val)) | ||
| 196 | + return | ||
| 197 | + except (ValueError, TypeError): | ||
| 198 | + pass | ||
| 199 | + print(0) | ||
| 200 | + return | ||
| 201 | + values = [] | ||
| 202 | + for r in results: | ||
| 203 | + val = get_nested(r, keys) | ||
| 204 | + if val is not None: | ||
| 205 | + try: | ||
| 206 | + values.append(float(val)) | ||
| 207 | + except (ValueError, TypeError): | ||
| 208 | + pass | ||
| 209 | + if values: | ||
| 210 | + print(max(values)) | ||
| 211 | + else: | ||
| 212 | + print(0) | ||
| 213 | + | ||
| 214 | + | ||
| 215 | +def cmd_write_version_info(filepath, timestamp, model, arch, kernel, os_name, cpu_model, | ||
| 216 | + cores, sw_name, sw_version, python_ver, compiler_ver): | ||
| 217 | + data = { | ||
| 218 | + "test_time": str(timestamp), | ||
| 219 | + "Model": str(model), | ||
| 220 | + "architecture": str(arch), | ||
| 221 | + "kernel": str(kernel), | ||
| 222 | + "os": str(os_name), | ||
| 223 | + "cpu_model": str(cpu_model), | ||
| 224 | + "cpu_cores": int(cores), | ||
| 225 | + "software_name": str(sw_name), | ||
| 226 | + "software_version": str(sw_version), | ||
| 227 | + "python_version": str(python_ver), | ||
| 228 | + "gcc_version": str(compiler_ver) | ||
| 229 | + } | ||
| 230 | + save_json(filepath, data) | ||
| 231 | + | ||
| 232 | + | ||
| 233 | +def cmd_write_build_info(filepath, timestamp, os_id, os_name, arch, kernel, | ||
| 234 | + gcc_ver, cmake_ver, swig_ver, faiss_ver, build_method): | ||
| 235 | + data = { | ||
| 236 | + "timestamp": str(timestamp), | ||
| 237 | + "os_id": str(os_id), | ||
| 238 | + "os_name": str(os_name), | ||
| 239 | + "architecture": str(arch), | ||
| 240 | + "kernel": str(kernel), | ||
| 241 | + "gcc_version": str(gcc_ver), | ||
| 242 | + "cmake_version": str(cmake_ver), | ||
| 243 | + "swig_version": str(swig_ver), | ||
| 244 | + "faiss_version": str(faiss_ver), | ||
| 245 | + "build_method": str(build_method) | ||
| 246 | + } | ||
| 247 | + save_json(filepath, data) | ||
| 248 | + | ||
| 249 | + | ||
| 250 | +def cmd_merge_jsons(output_path, *input_paths): | ||
| 251 | + merged = { | ||
| 252 | + "software_name": "faiss", | ||
| 253 | + "primary_benchmark": {}, | ||
| 254 | + "secondary_benchmark": {}, | ||
| 255 | + "micro_benchmark": {}, | ||
| 256 | + "environment": {} | ||
| 257 | + } | ||
| 258 | + for ip in input_paths: | ||
| 259 | + if os.path.exists(ip): | ||
| 260 | + data = load_json(ip) | ||
| 261 | + if isinstance(data, dict): | ||
| 262 | + bench_name = data.get("benchmark", "") | ||
| 263 | + if bench_name in ("ann_search", "ANN"): | ||
| 264 | + merged["primary_benchmark"] = data | ||
| 265 | + elif bench_name in ("micro_operations", "Micro-Benchmarks"): | ||
| 266 | + merged["micro_benchmark"] = data | ||
| 267 | + env_file = None | ||
| 268 | + for ip in input_paths: | ||
| 269 | + if os.path.exists(ip) and "version_info" in ip: | ||
| 270 | + env_file = ip | ||
| 271 | + break | ||
| 272 | + if env_file: | ||
| 273 | + merged["environment"] = load_json(env_file) | ||
| 274 | + save_json(output_path, merged) | ||
| 275 | + | ||
| 276 | + | ||
| 277 | +def main(): | ||
| 278 | + if len(sys.argv) < 3: | ||
| 279 | + print("Usage: json_helper.py <filepath> <command> [args...]") | ||
| 280 | + sys.exit(1) | ||
| 281 | + | ||
| 282 | + filepath = sys.argv[1] | ||
| 283 | + command = sys.argv[2] | ||
| 284 | + args = sys.argv[3:] | ||
| 285 | + | ||
| 286 | + commands = { | ||
| 287 | + "get": lambda: cmd_get(filepath, *args), | ||
| 288 | + "field_exists": lambda: cmd_field_exists(filepath, args[0] if args else ""), | ||
| 289 | + "count_results": lambda: cmd_count_results(filepath), | ||
| 290 | + "throughput_ge": lambda: cmd_throughput_ge(filepath, args[0], *args[1:]), | ||
| 291 | + "latency_le": lambda: cmd_latency_le(filepath, args[0], *args[1:]), | ||
| 292 | + "version": lambda: cmd_version(filepath), | ||
| 293 | + "contains": lambda: cmd_contains(filepath, args[0] if args else ""), | ||
| 294 | + "write_version_info": lambda: cmd_write_version_info(filepath, *args), | ||
| 295 | + "write_build_info": lambda: cmd_write_build_info(filepath, *args), | ||
| 296 | + "avg_throughput": lambda: cmd_avg_throughput(filepath, *args), | ||
| 297 | + "max_latency": lambda: cmd_max_latency(filepath, *args), | ||
| 298 | + "merge_jsons": lambda: cmd_merge_jsons(filepath, *args), | ||
| 299 | + } | ||
| 300 | + | ||
| 301 | + if command not in commands: | ||
| 302 | + print(f"Unknown command: {command}") | ||
| 303 | + sys.exit(1) | ||
| 304 | + | ||
| 305 | + commands[command]() | ||
| 306 | + | ||
| 307 | + | ||
| 308 | +if __name__ == "__main__": | ||
| 309 | + main() | ||
| @@ -0,0 +1,141 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +import subprocess | ||
| 3 | +import sys | ||
| 4 | +import os | ||
| 5 | +import json | ||
| 6 | +from datetime import datetime, timezone | ||
| 7 | + | ||
| 8 | +MICRO_OPS = [ | ||
| 9 | + ("list_comprehension", "[i*2 for i in range({n})]", [1000, 10000, 100000]), | ||
| 10 | + ("dict_get", "d.get(0)", [1000, 10000, 100000]), | ||
| 11 | + ("string_concat", "s + 'x'", [100, 1000, 10000]), | ||
| 12 | + ("json_parse", "json.loads(j)", [1000, 10000]), | ||
| 13 | +] | ||
| 14 | + | ||
| 15 | +THREAD_COUNTS = [1, 2, 4, 8, "all"] | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +def get_max_threads(): | ||
| 19 | + try: | ||
| 20 | + return int(os.cpu_count() or 4) | ||
| 21 | + except Exception: | ||
| 22 | + return 4 | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +def run_timeit(python_bin, stmt, setup, number, repeat=5): | ||
| 26 | + cmd = [ | ||
| 27 | + python_bin, "-c", | ||
| 28 | + f"import timeit; t = timeit.repeat('{stmt}', '{setup}', number={number}, repeat={repeat}); " | ||
| 29 | + f"print(min(t) / {number})", | ||
| 30 | + ] | ||
| 31 | + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) | ||
| 32 | + if result.returncode != 0: | ||
| 33 | + print(f"[MICRO][DEBUG] timeit failed: {result.stderr[:200]}") | ||
| 34 | + return 0.0 | ||
| 35 | + try: | ||
| 36 | + return float(result.stdout.strip()) | ||
| 37 | + except ValueError: | ||
| 38 | + return 0.0 | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +def bench_micro_ops(python_bin): | ||
| 42 | + results = {} | ||
| 43 | + for name, stmt_tmpl, sizes in MICRO_OPS: | ||
| 44 | + size_results = {} | ||
| 45 | + for n in sizes: | ||
| 46 | + stmt = stmt_tmpl.format(n=n) | ||
| 47 | + setup = "" | ||
| 48 | + if "dict_get" in name: | ||
| 49 | + setup = f"d = {{i: i for i in range({n})}}" | ||
| 50 | + elif "string_concat" in name: | ||
| 51 | + setup = f"s = 'x' * {n}" | ||
| 52 | + elif "json_parse" in name: | ||
| 53 | + setup = "import json; import json as j_module; j = json.dumps({{str(i): i for i in range(100)}}) * {n}".replace("{n}", str(min(n, 100))) | ||
| 54 | + stmt = "json.loads(j)" | ||
| 55 | + seconds_per_op = run_timeit(python_bin, stmt, setup, number=1000) | ||
| 56 | + ops_per_sec = round(1.0 / seconds_per_op, 2) if seconds_per_op > 0 else 0 | ||
| 57 | + size_results[f"size_{n}"] = { | ||
| 58 | + "seconds_per_op": round(seconds_per_op, 8), | ||
| 59 | + "ops_per_sec": ops_per_sec, | ||
| 60 | + } | ||
| 61 | + print(f"[MICRO] {name} size_{n}: {ops_per_sec} ops/sec") | ||
| 62 | + results[name] = size_results | ||
| 63 | + return results | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +def bench_thread_scaling(python_bin): | ||
| 67 | + max_threads = get_max_threads() | ||
| 68 | + results = {} | ||
| 69 | + for tc in THREAD_COUNTS: | ||
| 70 | + actual = tc if tc != "all" else max_threads | ||
| 71 | + label = f"threads_{tc}" | ||
| 72 | + stmt = "[i*2 for i in range(10000)]" | ||
| 73 | + env = os.environ.copy() | ||
| 74 | + env["OMP_NUM_THREADS"] = str(actual) | ||
| 75 | + cmd = [ | ||
| 76 | + python_bin, "-c", | ||
| 77 | + f"import timeit; t = timeit.repeat('{stmt}', number=1000, repeat=3); " | ||
| 78 | + f"print(min(t) / 1000)", | ||
| 79 | + ] | ||
| 80 | + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, env=env) | ||
| 81 | + try: | ||
| 82 | + sec = float(result.stdout.strip()) | ||
| 83 | + ops = round(1.0 / sec, 2) if sec > 0 else 0 | ||
| 84 | + except ValueError: | ||
| 85 | + sec, ops = 0, 0 | ||
| 86 | + results[label] = {"seconds_per_op": round(sec, 8), "ops_per_sec": ops} | ||
| 87 | + print(f"[MICRO] {label}: {ops} ops/sec") | ||
| 88 | + return results | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def main(): | ||
| 92 | + if len(sys.argv) < 4: | ||
| 93 | + print("Usage: micro_benchmark.py <python_bin> <output_file> [iterations]") | ||
| 94 | + sys.exit(1) | ||
| 95 | + python_bin = sys.argv[1] | ||
| 96 | + output_file = sys.argv[2] | ||
| 97 | + iterations = int(sys.argv[3]) if len(sys.argv) >= 4 else 1 | ||
| 98 | + | ||
| 99 | + if not os.path.exists(python_bin): | ||
| 100 | + print(f"[MICRO] Python binary not found: {python_bin}") | ||
| 101 | + sys.exit(1) | ||
| 102 | + | ||
| 103 | + version_str = os.environ.get("SOFTWARE_VERSION", "3.14.7") | ||
| 104 | + max_threads = get_max_threads() | ||
| 105 | + | ||
| 106 | + print("[MICRO] Running micro_ops...") | ||
| 107 | + ops_results = bench_micro_ops(python_bin) | ||
| 108 | + | ||
| 109 | + print("[MICRO] Running thread_scaling...") | ||
| 110 | + ts_results = bench_thread_scaling(python_bin) | ||
| 111 | + | ||
| 112 | + out = { | ||
| 113 | + "benchmark": "micro_operations", | ||
| 114 | + "description": f"CPython micro: timeit operations + GIL thread scaling on ARM64", | ||
| 115 | + "reference": "https://github.com/python/cpython", | ||
| 116 | + "software": "python", | ||
| 117 | + "version": version_str, | ||
| 118 | + "architecture": "arm64", | ||
| 119 | + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), | ||
| 120 | + "performance_metrics": { | ||
| 121 | + "ops_per_sec": {"unit": "ops/sec", "description": "Operations per second"}, | ||
| 122 | + "seconds_per_op": {"unit": "s", "description": "Seconds per operation"}, | ||
| 123 | + }, | ||
| 124 | + "parameters": { | ||
| 125 | + "micro_ops": [name for name, _, _ in MICRO_OPS], | ||
| 126 | + "thread_counts": [str(t) for t in THREAD_COUNTS], | ||
| 127 | + "max_threads": max_threads, | ||
| 128 | + "iterations": iterations, | ||
| 129 | + }, | ||
| 130 | + "results": { | ||
| 131 | + "micro_ops": ops_results, | ||
| 132 | + "thread_scaling": ts_results, | ||
| 133 | + }, | ||
| 134 | + } | ||
| 135 | + with open(output_file, "w") as f: | ||
| 136 | + json.dump(out, f, indent=2) | ||
| 137 | + print(f"[MICRO] Output written to {output_file}") | ||
| 138 | + | ||
| 139 | + | ||
| 140 | +if __name__ == "__main__": | ||
| 141 | + main() | ||