-
Notifications
You must be signed in to change notification settings - Fork 326
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: New script to output table of benchmarks for README pasting. (#…
…2780) Benchmarks are cool man. They let you see how fast the thing goes. This adds a tiny benchmark lib to barretenberg. Use it e.g. ```c++ Timer proof_timer; auto proof = acir_composer.create_proof(constraint_system, witness, recursive); write_benchmark("proof_construction_time", proof_timer.milliseconds(), "acir_test", current_dir); ``` The `write_benchmark` function takes a name, value, and then an arbitrary number of label/value pairs. The output data is very raw. The library will auto set the type to number/string/bool. The output data is JSONL in the form e.g. ```jsonl {"timestamp": "2023-10-10T22:17:10Z", "name": "pk_construction_time", "type": "number", "value": 234, "threads": 128, "acir_test": "1_mul"} {"timestamp": "2023-10-10T22:17:10Z", "name": "proof_construction_time", "type": "number", "value": 173, "threads": 128, "acir_test": "1_mul"} {"timestamp": "2023-10-10T22:17:10Z", "name": "vk_construction_time", "type": "number", "value": 74, "threads": 128, "acir_test": "1_mul"} ``` The labels are for ingesters to group/slice the data as they wish. To enable benchmarking, set the env var e.g `BENCHMARK_FD=3`, then redirect the given file descriptor number to wherever you want the benchmarks, e.g. ``` BENCHMARK_FD=3 ./run_acir_tests.sh 1_mul 3>&1 > /dev/null ``` Sends them to FD1 (stdout) and sends stdout to /dev/null. Thus this just prints out the benchmarks like above. Or: ``` BENCHMARK_FD=3 ./run_acir_tests.sh 1_mul 3>>benchmarks.jsonl ``` Appends them to file benchmarks.jsonl Longer term vision for benchmarks is that our TS code (or any other code), outputs benchmarks in the same style. The build-system will expect benchmarks, if enabled, to be have been written to a specific file in the container (or mounted volume), and will then upload the benchmarks to S3 for later, async processing (using e.g. glue -> athena -> quicksight/grafana). Both the container itself could manipulate the JSONL to inject properties specific to the test run, and the build-system could inject properties prior to upload such as `job-name` or `commit-hash` (although structured file-naming might solve this better).
- Loading branch information
1 parent
933f1b2
commit 6c20b45
Showing
14 changed files
with
362 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#!/bin/bash | ||
|
||
TEST_NAMES=("$@") | ||
THREADS=(1 4 16 32 64) | ||
BENCHMARKS=$(mktemp) | ||
|
||
if [ "${#TEST_NAMES[@]}" -eq 0 ]; then | ||
TEST_NAMES=(sha256 ecdsa_secp256k1 ecdsa_secp256r1 schnorr double_verify_proof) | ||
fi | ||
|
||
for TEST in ${TEST_NAMES[@]}; do | ||
for HC in ${THREADS[@]}; do | ||
HARDWARE_CONCURRENCY=$HC BENCHMARK_FD=3 ./run_acir_tests.sh $TEST 3>>$BENCHMARKS | ||
done | ||
done | ||
|
||
# Build results into string with \n delimited rows and space delimited values. | ||
TABLE_DATA="" | ||
for TEST in ${TEST_NAMES[@]}; do | ||
GATE_COUNT=$(jq -r --arg test "$TEST" 'select(.name == "gate_count" and .acir_test == $test) | .value' $BENCHMARKS | uniq) | ||
SUBGROUP_SIZE=$(jq -r --arg test "$TEST" 'select(.name == "subgroup_size" and .acir_test == $test) | .value' $BENCHMARKS | uniq) | ||
# Name in col 1, gate count in col 2, subgroup size in col 3. | ||
TABLE_DATA+="$TEST $GATE_COUNT $SUBGROUP_SIZE" | ||
# Each thread timing in subsequent cols. | ||
for HC in "${THREADS[@]}"; do | ||
RESULT=$(cat $BENCHMARKS | jq -r --arg test "$TEST" --argjson hc $HC 'select(.name == "proof_construction_time" and .acir_test == $test and .threads == $hc) | .value') | ||
TABLE_DATA+=" $RESULT" | ||
done | ||
TABLE_DATA+=$'\n' | ||
done | ||
|
||
# Trim the trailing newline. | ||
TABLE_DATA="${TABLE_DATA%$'\n'}" | ||
|
||
echo | ||
echo Table represents time in ms to build circuit and proof for each test on n threads. | ||
echo Ignores proving key construction. | ||
echo | ||
# Use awk to print the table | ||
echo -e "$TABLE_DATA" | awk -v threads="${THREADS[*]}" 'BEGIN { | ||
split(threads, t, " "); | ||
len_threads = length(t); | ||
print "+--------------------------+------------+---------------+" genseparator(len_threads); | ||
print "| Test | Gate Count | Subgroup Size |" genthreadheaders(t, len_threads); | ||
print "+--------------------------+------------+---------------+" genseparator(len_threads); | ||
} | ||
{ | ||
printf("| %-24s | %-10s | %-13s |", $1, $2, $3); | ||
for (i = 4; i <= len_threads+3; i++) { | ||
printf " %9s |", $(i); | ||
} | ||
print ""; | ||
} | ||
END { | ||
print "+--------------------------+------------+---------------+" genseparator(len_threads); | ||
} | ||
function genseparator(len, res) { | ||
for (i = 1; i <= len; i++) res = res "-----------+"; | ||
return res; | ||
} | ||
function genthreadheaders(t, len, res) { | ||
for (i = 1; i <= len; i++) res = res sprintf(" %9s |", t[i]); | ||
return res; | ||
} | ||
' | ||
|
||
rm $BENCHMARKS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#include "barretenberg/common/throw_or_abort.hpp" | ||
#include "barretenberg/env/hardware_concurrency.hpp" | ||
#include <cstdlib> | ||
#include <ctime> | ||
#include <fcntl.h> | ||
#include <iostream> | ||
#include <sstream> | ||
#include <string> | ||
#include <unistd.h> | ||
|
||
namespace { | ||
/** | ||
* If user provides the env var BENCHMARK_FD write benchmarks to this fd, otherwise default to -1 (disable). | ||
* e.g: | ||
* BENCHMARK_FD=3 bb 3> benchmarks.jsonl | ||
*/ | ||
auto bfd = []() { | ||
try { | ||
static auto bfd_str = std::getenv("BENCHMARK_FD"); | ||
int bfd = bfd_str ? (int)std::stoul(bfd_str) : -1; | ||
if (bfd >= 0 && (fcntl(bfd, F_GETFD) == -1 || errno == EBADF)) { | ||
throw_or_abort("fd is not open. Did you redirect in your shell?"); | ||
} | ||
return bfd; | ||
} catch (std::exception const& e) { | ||
std::string inner_msg = e.what(); | ||
throw_or_abort("Invalid BENCHMARK_FD: " + inner_msg); | ||
} | ||
}(); | ||
} // namespace | ||
|
||
template <typename T, typename Enable = void> struct TypeTraits; | ||
|
||
template <typename T> struct TypeTraits<T, typename std::enable_if<std::is_arithmetic<T>::value>::type> { | ||
static const char* type; | ||
}; | ||
|
||
template <typename T> | ||
const char* TypeTraits<T, typename std::enable_if<std::is_arithmetic<T>::value>::type>::type = "number"; | ||
|
||
template <> struct TypeTraits<std::string> { | ||
static const char* type; | ||
}; | ||
|
||
const char* TypeTraits<std::string>::type = "string"; | ||
|
||
template <> struct TypeTraits<double> { | ||
static const char* type; | ||
}; | ||
|
||
const char* TypeTraits<double>::type = "number"; | ||
|
||
template <> struct TypeTraits<bool> { | ||
static const char* type; | ||
}; | ||
|
||
const char* TypeTraits<bool>::type = "bool"; | ||
|
||
// Helper function to get the current timestamp in the desired format | ||
std::string getCurrentTimestamp() | ||
{ | ||
std::time_t now = std::time(nullptr); | ||
std::tm* now_tm = std::gmtime(&now); | ||
char buf[21] = { 0 }; | ||
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", now_tm); | ||
return std::string(buf); | ||
} | ||
|
||
template <typename T> std::string toString(const T& value) | ||
{ | ||
std::ostringstream oss; | ||
oss << value; | ||
return oss.str(); | ||
} | ||
|
||
void appendToStream(std::ostringstream&) | ||
{ | ||
// base case: do nothing | ||
} | ||
|
||
template <typename K, typename V, typename... Args> | ||
void appendToStream(std::ostringstream& oss, const K& key, const V& value, Args... args) | ||
{ | ||
oss << ", \"" << key << "\": \"" << toString(value) << "\""; | ||
appendToStream(oss, args...); // recursively process the remaining arguments | ||
} | ||
|
||
template <typename T, typename... Args> void write_benchmark(const std::string& name, const T& value, Args... args) | ||
{ | ||
if (bfd == -1) { | ||
return; | ||
} | ||
std::ostringstream oss; | ||
oss << "{\"timestamp\": \"" << getCurrentTimestamp() << "\", " | ||
<< "\"name\": \"" << name << "\", " | ||
<< "\"type\": \"" << TypeTraits<T>::type << "\", " | ||
<< "\"value\": " << value << ", " | ||
<< "\"threads\": " << env_hardware_concurrency(); | ||
|
||
appendToStream(oss, args...); // unpack and append the key-value pairs | ||
|
||
oss << "}" << std::endl; | ||
const std::string& tmp = oss.str(); | ||
write((int)bfd, tmp.c_str(), tmp.size()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 15 additions & 1 deletion
16
barretenberg/cpp/src/barretenberg/env/hardware_concurrency.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,24 @@ | ||
#include "hardware_concurrency.hpp" | ||
#include <barretenberg/common/throw_or_abort.hpp> | ||
#include <cstdlib> | ||
#include <stdexcept> | ||
#include <string> | ||
#include <thread> | ||
|
||
extern "C" { | ||
|
||
uint32_t env_hardware_concurrency() | ||
{ | ||
return std::thread::hardware_concurrency(); | ||
#ifndef __wasm__ | ||
try { | ||
#endif | ||
static auto val = std::getenv("HARDWARE_CONCURRENCY"); | ||
static const uint32_t cores = val ? (uint32_t)std::stoul(val) : std::thread::hardware_concurrency(); | ||
return cores; | ||
#ifndef __wasm__ | ||
} catch (std::exception const&) { | ||
throw std::runtime_error("HARDWARE_CONCURRENCY invalid."); | ||
} | ||
#endif | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.