2022-01-24 19:16:47 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
set -e
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
cat <<EOF >&2
|
2022-01-28 13:43:25 +00:00
|
|
|
USAGE: c++expr [-c CXX | -C | -I] [-i INCLUDE] [-l LIB] [-b STEPS] [-t TESTS] [-o FILE] [-O CXX_OPTS...] [-g 'GLOBAL CODE'] 'MAIN CODE'
|
2022-01-24 19:16:47 +00:00
|
|
|
OPTIONS:
|
|
|
|
-c CXX use specified c++ compiler
|
|
|
|
-C use cmake
|
|
|
|
-I integrate into ClickHouse build tree in current directory
|
|
|
|
-i INC add #include <INC>
|
|
|
|
-l LIB link against LIB (only for -I or -C)
|
|
|
|
-b STEPS_NUM make program to benchmark specified code snippet and run tests with STEPS_NUM each
|
|
|
|
-b perf-top run infinite benchmark and show perf top
|
|
|
|
-t TESTS_NUM make program to benchmark specified code snippet and run TESTS_NUM tests
|
|
|
|
-o FILE do not run, just save binary executable file
|
|
|
|
-O CXX_OPTS forward option compiler (e.g. -O "-O3 -std=c++20")
|
2022-01-28 13:43:25 +00:00
|
|
|
EXAMPLES:
|
|
|
|
$ c++expr -g 'int fib(int n) { return n < 2 ? n : fib(n-2) + fib(n-1); }' 'OUT(fib(10)) OUT(fib(20)) OUT(fib(30))'
|
|
|
|
fib(10) -> 55
|
|
|
|
fib(20) -> 6765
|
|
|
|
fib(30) -> 832040
|
|
|
|
$ c++expr -I -i Interpreters/Context.h 'OUT(sizeof(DB::Context))'
|
|
|
|
sizeof(DB::Context) -> 7776
|
|
|
|
$ c++expr -I -i Common/Stopwatch.h -b 10000 'Stopwatch sw;'
|
|
|
|
Steps per test: 10000
|
|
|
|
Test #0: 0.0178 us 5.61798e+07 sps
|
|
|
|
...
|
|
|
|
Test #4: 0.0179 us 5.58659e+07 sps
|
|
|
|
Average: 0.0179 us 5.58659e+07 sps
|
2022-01-24 19:16:47 +00:00
|
|
|
EOF
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
SOURCE_FILE=main.cpp
|
|
|
|
GLOBAL=
|
|
|
|
OUTPUT_EXECUTABLE=
|
|
|
|
INCS="vector iostream typeinfo cstdlib cmath sys/time.h"
|
|
|
|
LIBS=""
|
|
|
|
BENCHMARK_STEPS=0
|
|
|
|
RUN_PERFTOP=
|
|
|
|
BENCHMARK_TESTS=5
|
|
|
|
USE_CMAKE=
|
|
|
|
USE_CLICKHOUSE=
|
|
|
|
CXX=g++
|
|
|
|
CXX_OPTS=
|
|
|
|
CMD_PARAMS=
|
|
|
|
|
|
|
|
#
|
|
|
|
# Parse command line
|
|
|
|
#
|
|
|
|
|
2022-01-28 13:43:25 +00:00
|
|
|
if [ "$1" == "--help" ] || [ -z "$1" ]; then usage; fi
|
2022-01-24 19:16:47 +00:00
|
|
|
while getopts "vc:CIi:l:b:t:o:O:g:" OPT; do
|
|
|
|
case "$OPT" in
|
|
|
|
v) set -x; ;;
|
|
|
|
c) CXX="$OPTARG"; ;;
|
|
|
|
C) USE_CMAKE=y; ;;
|
|
|
|
I) USE_CLICKHOUSE=y; LIBS="$LIBS clickhouse_common_io"; ;;
|
|
|
|
i) INCS="$INCS $OPTARG"; ;;
|
|
|
|
l) LIBS="$LIBS $OPTARG"; ;;
|
|
|
|
b) if [ "$OPTARG" = perf-top ]; then BENCHMARK_STEPS=-1; RUN_PERFTOP=y; else BENCHMARK_STEPS="$OPTARG"; fi; ;;
|
|
|
|
t) BENCHMARK_TESTS="$OPTARG"; ;;
|
|
|
|
o) OUTPUT_EXECUTABLE="$OPTARG"; ;;
|
|
|
|
O) CXX_OPTS="$CXX_OPTS $OPTARG"; ;;
|
|
|
|
g) GLOBAL="$OPTARG"; ;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
shift $(( $OPTIND - 1 ))
|
|
|
|
|
|
|
|
#
|
|
|
|
# Positional arguments
|
|
|
|
#
|
|
|
|
|
|
|
|
EXPR=$1
|
|
|
|
shift
|
|
|
|
|
|
|
|
if [ -z "$EXPR" ]; then usage; fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Arguments forwarded to program should go after main code and before --
|
|
|
|
#
|
|
|
|
|
|
|
|
while [ -n "$1" ] && [ "$1" != "--" ]; do
|
|
|
|
CMD_PARAMS="$CMD_PARAMS $1"
|
|
|
|
shift
|
|
|
|
done
|
|
|
|
if [ "$1" == "--" ]; then shift; fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Setup workdir
|
|
|
|
#
|
|
|
|
|
|
|
|
find_clickhouse_root () {
|
|
|
|
local DIR="`pwd`"
|
|
|
|
while [ $DIR != "/" ]; do
|
|
|
|
if [ ! -e "$DIR/CMakeLists.txt" ]; then
|
|
|
|
echo "error: $DIR has no CMakeLists.txt"
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
if grep "project(ClickHouse)" "$DIR/CMakeLists.txt" >/dev/null 2>&1; then
|
|
|
|
echo $DIR
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
DIR="`dirname $DIR`"
|
|
|
|
done
|
|
|
|
echo "error: unable to find Clickhouse root folder"
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
find_clickhouse_build () {
|
|
|
|
local CLICKHOUSE_ROOT="`find_clickhouse_root`"
|
|
|
|
if [ -e "$CLICKHOUSE_ROOT/build/CMakeCache.txt" ]; then
|
|
|
|
echo "$CLICKHOUSE_ROOT/build"
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
echo "error: $CLICKHOUSE_ROOT/build/CMakeCache.txt doesn't exist"
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
CALL_DIR=`pwd`
|
|
|
|
EXECUTABLE=cppexpr_$$
|
|
|
|
EXECUTABLE_DIR=.
|
|
|
|
|
|
|
|
if [ -n "$USE_CLICKHOUSE" ]; then
|
|
|
|
SUBDIR=cppexpr_$$
|
|
|
|
WORKDIR=$CALL_DIR/$SUBDIR
|
|
|
|
if [ ! -e $CALL_DIR/CMakeLists.txt ]; then
|
|
|
|
echo "error: $CALL_DIR/CMakeLists.txt is required for integration" >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
CLICKHOUSE_ROOT="`find_clickhouse_root`"
|
|
|
|
BUILD_ROOT="`find_clickhouse_build`"
|
|
|
|
CLICKHOUSE_PATH="${WORKDIR/$CLICKHOUSE_ROOT}"
|
|
|
|
EXECUTABLE_DIR="${BUILD_ROOT}${CLICKHOUSE_PATH}"
|
|
|
|
|
|
|
|
if [ -z "$CLICKHOUSE_ROOT" ] || [ -z "$BUILD_ROOT" ] || [ -z "$CLICKHOUSE_PATH" ]; then
|
|
|
|
echo "error: unable to locate ClickHouse" >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
cp $CALL_DIR/CMakeLists.txt $CALL_DIR/CMakeLists.txt.backup.$$
|
|
|
|
echo "add_subdirectory ($SUBDIR)" >>$CALL_DIR/CMakeLists.txt
|
|
|
|
cleanup() {
|
|
|
|
mv $CALL_DIR/CMakeLists.txt.backup.$$ $CALL_DIR/CMakeLists.txt
|
|
|
|
rm -rf $WORKDIR
|
|
|
|
rm -rf ${BUILD_ROOT}${CLICKHOUSE_PATH}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
WORKDIR=/var/tmp/cppexpr_$$
|
|
|
|
cleanup() {
|
|
|
|
rm -rf $WORKDIR
|
|
|
|
}
|
|
|
|
fi
|
|
|
|
|
|
|
|
mkdir -p $WORKDIR
|
|
|
|
cd $WORKDIR
|
|
|
|
|
|
|
|
#
|
|
|
|
# Generate CMakeLists.txt
|
|
|
|
#
|
|
|
|
if [ -n "$USE_CMAKE" ]; then
|
|
|
|
cat <<EOF >>CMakeLists.txt
|
|
|
|
project(CppExpr)
|
|
|
|
SET(PROJECT_NAME CppExpr)
|
|
|
|
SET(CMAKE_INCLUDE_CURRENT_DIR TRUE)
|
|
|
|
cmake_minimum_required(VERSION 2.8)
|
|
|
|
set(CMAKE_CXX_FLAGS -fPIC)
|
|
|
|
set(CMAKE_C_FLAGS -fPIC)
|
|
|
|
set(CMAKE_BUILD_TYPE Release)
|
|
|
|
set(SOURCES $SOURCE_FILE)
|
|
|
|
add_executable($EXECUTABLE \${SOURCES})
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Generate CMakeLists.txt for integration
|
|
|
|
#
|
|
|
|
if [ -n "$USE_CLICKHOUSE" ]; then
|
|
|
|
cat <<EOF >>CMakeLists.txt
|
|
|
|
add_executable($EXECUTABLE $SOURCE_FILE)
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Add libraries to CMakeLists.txt
|
|
|
|
#
|
|
|
|
if [ -n "$LIBS" ]; then
|
|
|
|
cat <<EOF >>CMakeLists.txt
|
|
|
|
target_link_libraries($EXECUTABLE PRIVATE $LIBS)
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Generate source code
|
|
|
|
#
|
|
|
|
>$SOURCE_FILE
|
|
|
|
for INC in $INCS; do
|
|
|
|
echo "#include <$INC>" >> $SOURCE_FILE
|
|
|
|
done
|
|
|
|
cat <<EOF >>$SOURCE_FILE
|
|
|
|
|
|
|
|
#define OUT(expr) std::cout << #expr << " -> " << (expr) << std::endl;
|
|
|
|
size_t max_tests = $BENCHMARK_TESTS;
|
|
|
|
size_t max_steps = $BENCHMARK_STEPS;
|
|
|
|
$GLOBAL
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
(void)argc; (void)argv;
|
|
|
|
try {
|
|
|
|
EOF
|
|
|
|
|
|
|
|
if [ $BENCHMARK_STEPS -eq 0 ]; then
|
|
|
|
cat <<EOF >>$SOURCE_FILE
|
|
|
|
$EXPR
|
|
|
|
EOF
|
|
|
|
else
|
|
|
|
cat <<EOF >>$SOURCE_FILE
|
|
|
|
std::cout << "Steps per test: " << max_steps << std::endl;
|
|
|
|
if (max_steps == 0) max_steps = 1;
|
|
|
|
double total = 0.0;
|
|
|
|
for (size_t test = 0; test < max_tests; test++) {
|
|
|
|
timeval beg, end;
|
|
|
|
gettimeofday(&beg, nullptr);
|
|
|
|
for (size_t step = 0; step < max_steps; step++) {
|
|
|
|
asm volatile("" ::: "memory");
|
|
|
|
$EXPR
|
|
|
|
}
|
|
|
|
gettimeofday(&end, nullptr);
|
|
|
|
double interval = (end.tv_sec - beg.tv_sec)*1e6 + (end.tv_usec - beg.tv_usec);
|
|
|
|
std::cout << "Test #" << test << ": " << interval / max_steps << " us\t" << max_steps * 1e6 / interval << " sps" << std::endl;
|
|
|
|
total += interval;
|
|
|
|
}
|
|
|
|
std::cout << "Average: " << total / max_tests / max_steps << " us\t" << max_steps * 1e6 / (total / max_tests) << " sps" << std::endl;
|
|
|
|
EOF
|
|
|
|
fi
|
|
|
|
|
|
|
|
cat <<EOF >>$SOURCE_FILE
|
|
|
|
return 0;
|
|
|
|
} catch (std::exception& e) {
|
|
|
|
std::cerr << "unhandled exception (" << typeid(e).name() << "):" << e.what() << std::endl;
|
|
|
|
} catch (...) {
|
|
|
|
std::cerr << "unknown unhandled exception\n";
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
#ifdef OUT
|
|
|
|
#undef OUT
|
|
|
|
#endif
|
|
|
|
EOF
|
|
|
|
|
|
|
|
#
|
|
|
|
# Compile
|
|
|
|
#
|
|
|
|
if [ -n "$USE_CMAKE" ]; then
|
|
|
|
if ! (cmake . && make); then
|
|
|
|
cat -n $SOURCE_FILE
|
|
|
|
cleanup
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
elif [ -n "$USE_CLICKHOUSE" ]; then
|
|
|
|
if ! (cd $BUILD_ROOT && ninja $EXECUTABLE) >stdout.log 2>stderr.log; then
|
|
|
|
cat stdout.log
|
|
|
|
cat stderr.log >&2
|
|
|
|
cat -n $SOURCE_FILE
|
|
|
|
cleanup
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
RET=0
|
|
|
|
$CXX $CXX_OPTS -I$CALL_DIR -o $EXECUTABLE $SOURCE_FILE || RET=$?
|
|
|
|
if [ $RET -ne 0 ]; then
|
|
|
|
cat -n $SOURCE_FILE
|
|
|
|
cleanup
|
|
|
|
exit $RET
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Execute
|
|
|
|
#
|
|
|
|
RET=0
|
|
|
|
if [ -z "$OUTPUT_EXECUTABLE" ]; then
|
|
|
|
if [ -z "$RUN_PERFTOP" ]; then
|
|
|
|
"$@" $EXECUTABLE_DIR/$EXECUTABLE $CMD_PARAMS || RET=$?
|
|
|
|
else
|
|
|
|
"$@" $EXECUTABLE_DIR/$EXECUTABLE $CMD_PARAMS &
|
|
|
|
PID=$!
|
|
|
|
perf top -p $PID
|
|
|
|
kill $PID
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
cp $EXECUTABLE_DIR/$EXECUTABLE $CALL_DIR/$OUTPUT_EXECUTABLE
|
|
|
|
fi
|
|
|
|
|
|
|
|
#
|
|
|
|
# Cleanup
|
|
|
|
#
|
|
|
|
cleanup
|
|
|
|
echo "Exit code: $RET"
|
|
|
|
exit $RET
|