check for performance degradation in CI (#270)
* Add -n and -w arguments * Add Dockerfile that compares perf against master * Add checkperf to .drone.yml * Clone from github instead of .git since CI doesn't have .git
This commit is contained in:
parent
3fb82502f7
commit
875e2f9d0d
|
@ -12,6 +12,10 @@ steps:
|
||||||
- make
|
- make
|
||||||
- make quiettest
|
- make quiettest
|
||||||
- make amalgamate
|
- make amalgamate
|
||||||
|
- name: checkperf
|
||||||
|
image: gcc:8
|
||||||
|
commands:
|
||||||
|
- make checkperf
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: arm64
|
name: arm64
|
||||||
|
@ -27,6 +31,10 @@ steps:
|
||||||
- make
|
- make
|
||||||
- make quiettest
|
- make quiettest
|
||||||
- make amalgamate
|
- make amalgamate
|
||||||
|
- name: checkperf
|
||||||
|
image: gcc:8
|
||||||
|
commands:
|
||||||
|
- make checkperf
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: stylecheck
|
name: stylecheck
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -123,6 +123,12 @@ $(JSON_INCLUDE) $(SAJSON_INCLUDE) $(RAPIDJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJ
|
||||||
parse: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
parse: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||||
$(CXX) $(CXXFLAGS) -o parse $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
$(CXX) $(CXXFLAGS) -o parse $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||||
|
|
||||||
|
perfdiff: benchmark/perfdiff.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) -o perfdiff benchmark/perfdiff.cpp $(LIBFLAGS)
|
||||||
|
|
||||||
|
checkperf:
|
||||||
|
bash ./scripts/checkperf.sh
|
||||||
|
|
||||||
statisticalmodel: benchmark/statisticalmodel.cpp $(HEADERS) $(LIBFILES)
|
statisticalmodel: benchmark/statisticalmodel.cpp $(HEADERS) $(LIBFILES)
|
||||||
$(CXX) $(CXXFLAGS) -o statisticalmodel $(LIBFILES) benchmark/statisticalmodel.cpp $(LIBFLAGS)
|
$(CXX) $(CXXFLAGS) -o statisticalmodel $(LIBFILES) benchmark/statisticalmodel.cpp $(LIBFLAGS)
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,4 @@ target_include_directories(${SIMDJSON_LIB_NAME}
|
||||||
|
|
||||||
add_cpp_benchmark(parse)
|
add_cpp_benchmark(parse)
|
||||||
add_cpp_benchmark(statisticalmodel)
|
add_cpp_benchmark(statisticalmodel)
|
||||||
|
add_executable(perfdiff perfdiff.cpp)
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# From the ROOT, run:
|
||||||
|
# docker build -t simdjsonbench -f benchmark/Dockerfile . && docker run --privileged -t simdjsonbench
|
||||||
|
FROM gcc:8.3
|
||||||
|
|
||||||
|
# # Build latest
|
||||||
|
# ENV latest_release=v0.2.1
|
||||||
|
# WORKDIR /usr/src/$latest_release/
|
||||||
|
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ -b $latest_release .
|
||||||
|
# RUN make parse
|
||||||
|
|
||||||
|
# # Build master
|
||||||
|
# WORKDIR /usr/src/master/
|
||||||
|
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ .
|
||||||
|
# RUN make parse
|
||||||
|
|
||||||
|
# Build the current source
|
||||||
|
COPY . /usr/src/current/
|
||||||
|
WORKDIR /usr/src/current/
|
||||||
|
RUN make checkperf
|
|
@ -124,11 +124,20 @@ int main(int argc, char *argv[]) {
|
||||||
bool json_output = false;
|
bool json_output = false;
|
||||||
bool force_one_iteration = false;
|
bool force_one_iteration = false;
|
||||||
bool just_data = false;
|
bool just_data = false;
|
||||||
|
int32_t iterations = -1;
|
||||||
|
int32_t warmup_iterations = -1;
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "1vdt")) != -1) {
|
while ((c = getopt(argc, argv, "1vdtn:w:")) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
case 'n':
|
||||||
|
iterations = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
warmup_iterations = atoi(optarg);
|
||||||
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
just_data = true;
|
just_data = true;
|
||||||
break;
|
break;
|
||||||
|
@ -174,12 +183,21 @@ int main(int argc, char *argv[]) {
|
||||||
std::cout << "[verbose] loaded " << filename << " (" << p.size()
|
std::cout << "[verbose] loaded " << filename << " (" << p.size()
|
||||||
<< " bytes)" << std::endl;
|
<< " bytes)" << std::endl;
|
||||||
}
|
}
|
||||||
#if defined(DEBUG)
|
if (iterations == -1) {
|
||||||
const uint32_t iterations = 1;
|
#if defined(DEBUG)
|
||||||
#else
|
iterations = 1;
|
||||||
const uint32_t iterations =
|
#else
|
||||||
force_one_iteration ? 1 : (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
iterations = force_one_iteration ? 1 : (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
if (warmup_iterations == -1) {
|
||||||
|
#if defined(DEBUG)
|
||||||
|
warmup_iterations = 0;
|
||||||
|
#else
|
||||||
|
warmup_iterations = (p.size() < 1 * 1000 * 1000) ? 10 : 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<double> res;
|
std::vector<double> res;
|
||||||
res.resize(iterations);
|
res.resize(iterations);
|
||||||
if (!just_data)
|
if (!just_data)
|
||||||
|
@ -224,9 +242,33 @@ int main(int argc, char *argv[]) {
|
||||||
unsigned long cref0 = 0, cref1 = 0, cref2 = 0;
|
unsigned long cref0 = 0, cref1 = 0, cref2 = 0;
|
||||||
unsigned long cmis0 = 0, cmis1 = 0, cmis2 = 0;
|
unsigned long cmis0 = 0, cmis1 = 0, cmis2 = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Do warmup iterations
|
||||||
bool isok = true;
|
bool isok = true;
|
||||||
|
for (int32_t i = 0; i < warmup_iterations; i++) {
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "[verbose] warmup iteration # " << i << std::endl;
|
||||||
|
}
|
||||||
|
simdjson::ParsedJson pj;
|
||||||
|
bool allocok = pj.allocate_capacity(p.size());
|
||||||
|
if (!allocok) {
|
||||||
|
std::cerr << "failed to allocate memory" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
isok = (simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj) ==
|
||||||
|
simdjson::SUCCESS);
|
||||||
|
isok = isok &&
|
||||||
|
(simdjson::SUCCESS ==
|
||||||
|
simdjson::unified_ptr((const uint8_t *)p.data(), p.size(), pj));
|
||||||
|
if (!isok) {
|
||||||
|
std::cerr << pj.get_error_message() << std::endl;
|
||||||
|
std::cerr << "Could not parse. " << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef SQUASH_COUNTERS
|
#ifndef SQUASH_COUNTERS
|
||||||
for (uint32_t i = 0; i < iterations; i++) {
|
for (int32_t i = 0; i < iterations; i++) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||||
}
|
}
|
||||||
|
@ -275,8 +317,9 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// we do it again, this time just measuring the elapsed time
|
// we do it again, this time just measuring the elapsed time
|
||||||
for (uint32_t i = 0; i < iterations; i++) {
|
for (int32_t i = 0; i < iterations; i++) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define popen _popen
|
||||||
|
#define pclose _pclose
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string exec(const char* cmd) {
|
||||||
|
std::array<char, 128> buffer;
|
||||||
|
std::string result;
|
||||||
|
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
|
||||||
|
if (!pipe) {
|
||||||
|
throw std::runtime_error("popen() failed!");
|
||||||
|
}
|
||||||
|
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||||
|
result += buffer.data();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
double readThroughput(std::string parseOutput) {
|
||||||
|
std::istringstream output(parseOutput);
|
||||||
|
std::string line;
|
||||||
|
double result = 0;
|
||||||
|
int numResults = 0;
|
||||||
|
while (std::getline(output, line)) {
|
||||||
|
int pos = 0;
|
||||||
|
for (int i=0; i<5; i++) {
|
||||||
|
pos = line.find('\t', pos);
|
||||||
|
if (pos < 0) {
|
||||||
|
std::cerr << "Command printed out a line with less than 5 fields in it:\n" << line << std::endl;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
result += std::stod(line.substr(pos));
|
||||||
|
numResults++;
|
||||||
|
}
|
||||||
|
return result / numResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double ERROR_MARGIN = 10; // 10%
|
||||||
|
const double INTERLEAVED_ATTEMPTS = 4;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <new parse cmd> <reference parse cmd>";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
double newThroughput = 0;
|
||||||
|
double referenceThroughput = 0;
|
||||||
|
for (int attempt=0; attempt < INTERLEAVED_ATTEMPTS; attempt++) {
|
||||||
|
newThroughput += readThroughput(exec(argv[1]));
|
||||||
|
referenceThroughput += readThroughput(exec(argv[2]));
|
||||||
|
}
|
||||||
|
newThroughput /= INTERLEAVED_ATTEMPTS;
|
||||||
|
referenceThroughput /= INTERLEAVED_ATTEMPTS;
|
||||||
|
|
||||||
|
std::cout << "New throughput: " << newThroughput << std::endl;
|
||||||
|
std::cout << "Ref throughput: " << referenceThroughput << std::endl;
|
||||||
|
double percentDifference = ((newThroughput / referenceThroughput) - 1.0) * 100;
|
||||||
|
std::cout << "Difference: " << percentDifference << "%" << std::endl;
|
||||||
|
if (percentDifference < -ERROR_MARGIN) {
|
||||||
|
std::cerr << "New throughput is more than " << ERROR_MARGIN << "% degraded from reference throughput!" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
|
||||||
|
|
||||||
|
# Arguments: perfdiff.sh <branch> <test json files>
|
||||||
|
if [ -z "$1" ]; then reference_branch="master"; else reference_branch=$1; shift; fi
|
||||||
|
if [ -z "$*" ]; then perftests="jsonexamples/twitter.json"; else perftests=$*; fi
|
||||||
|
|
||||||
|
# Clone and build the reference branch's parse
|
||||||
|
echo "Cloning and build the reference branch ($reference_branch) ..."
|
||||||
|
reference=$current/benchbranch/$reference_branch
|
||||||
|
rm -rf $reference
|
||||||
|
mkdir -p $reference
|
||||||
|
git clone --depth 1 -b $reference_branch https://github.com/lemire/simdjson $reference
|
||||||
|
cd $reference
|
||||||
|
make parse
|
||||||
|
|
||||||
|
# Build the current branch's parse
|
||||||
|
echo "Building the current branch ..."
|
||||||
|
current=$SCRIPTPATH/..
|
||||||
|
cd $current
|
||||||
|
make clean
|
||||||
|
make parse
|
||||||
|
|
||||||
|
# Run them and diff performance
|
||||||
|
make perfdiff
|
||||||
|
|
||||||
|
echo "Running perfdiff:"
|
||||||
|
./perfdiff "$current/parse -t $perftests" "$reference/parse -t $perftests"
|
Loading…
Reference in New Issue