Skip to content

Commit

Permalink
utlity: Add benchmark for string-utility functions (#2348)
Browse files Browse the repository at this point in the history
Adds a benchmarking for new string utility functions added in #2368 using the google microbenchmarking library (https://github.com/google/benchmark), which was added in #2350. This serves as an example and proof of concept on how to use the benchmarking library, and as a reference point for the approximate performance of some string-parsing functions.

Risk Level: Very Low

Testing:
This is just a new speed-test, so running it on its own is sufficient to test it.

Results:

2018-01-18 08:01:44
Run on (12 X 3800 MHz CPU s)
CPU Caches:
  L1 Data 32K (x6)
  L1 Instruction 32K (x6)
  L2 Unified 256K (x6)
  L3 Unified 15360K (x1)
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
-------------------------------------------------------------------------------------
Benchmark                                              Time           CPU Iterations
-------------------------------------------------------------------------------------
BM_RTrimStringView                                    11 ns         11 ns   54467092
BM_RTrimStringViewAlreadyTrimmed                       9 ns          9 ns   77143046
BM_RTrimStringViewAlreadyTrimmedAndMakeString         24 ns         24 ns   29094696
BM_FindToken                                         165 ns        165 ns    4230849
BM_FindTokenValueNestedSplit                         427 ns        427 ns    1653627
BM_FindTokenValueSearchForEqual                      180 ns        180 ns    4046401

In practice I did not find this benchmark to be noisy on repeated runs.

Signed-off-by: Joshua Marantz <[email protected]>
  • Loading branch information
jmarantz authored and htuch committed Jan 23, 2018
1 parent c680300 commit bfc6408
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
references:
envoy-build-image: &envoy-build-image
envoyproxy/envoy-build:52f6880ffbf761c9b809fc3ac208900956ff16b4
envoyproxy/envoy-build:61b38528d7e46ced9d749d278ba185332310ca95

version: 2
jobs:
Expand Down
14 changes: 14 additions & 0 deletions test/common/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_binary",
"envoy_cc_test",
"envoy_package",
)
Expand Down Expand Up @@ -84,3 +85,16 @@ envoy_cc_test(
"//source/common/common:shared_memory_hash_set_lib",
],
)

envoy_cc_binary(
name = "utility_speed_test",
srcs = ["utility_speed_test.cc"],
external_deps = [
"abseil_strings",
"benchmark",
],
deps = [
"//source/common/common:assert_lib",
"//source/common/common:utility_lib",
],
)
157 changes: 157 additions & 0 deletions test/common/common/utility_speed_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Note: this should be run with --compilation_mode=opt, and would benefit from a
// quiescent system with disabled cstate power management.

#include "common/common/assert.h"
#include "common/common/utility.h"

#include "absl/strings/string_view.h"
#include "testing/base/public/benchmark.h"

static const char TextToTrim[] = "\t the quick brown fox jumps over the lazy dog\n\r\n";
static size_t TextToTrimLength = sizeof(TextToTrim) - 1;

static const char AlreadyTrimmed[] = "the quick brown fox jumps over the lazy dog";
static size_t AlreadyTrimmedLength = sizeof(AlreadyTrimmed) - 1;

static const char CacheControl[] = "private, max-age=300, no-transform";
static size_t CacheControlLength = sizeof(CacheControl) - 1;

// NOLINT(namespace-envoy)

static void BM_RTrimStringView(benchmark::State& state) {
int accum = 0;
for (auto _ : state) {
absl::string_view text(TextToTrim, TextToTrimLength);
text = Envoy::StringUtil::rtrim(text);
accum += TextToTrimLength - text.size();
}
benchmark::DoNotOptimize(accum);
}
BENCHMARK(BM_RTrimStringView);

static void BM_RTrimStringViewAlreadyTrimmed(benchmark::State& state) {
int accum = 0;
for (auto _ : state) {
absl::string_view text(AlreadyTrimmed, AlreadyTrimmedLength);
text = Envoy::StringUtil::rtrim(text);
accum += AlreadyTrimmedLength - text.size();
}
benchmark::DoNotOptimize(accum);
}
BENCHMARK(BM_RTrimStringViewAlreadyTrimmed);

static void BM_RTrimStringViewAlreadyTrimmedAndMakeString(benchmark::State& state) {
int accum = 0;
for (auto _ : state) {
absl::string_view text(AlreadyTrimmed, AlreadyTrimmedLength);
std::string string_copy = std::string(Envoy::StringUtil::rtrim(text));
accum += AlreadyTrimmedLength - string_copy.size();
}
benchmark::DoNotOptimize(accum);
}
BENCHMARK(BM_RTrimStringViewAlreadyTrimmedAndMakeString);

static void BM_FindToken(benchmark::State& state) {
const absl::string_view cache_control(CacheControl, CacheControlLength);
for (auto _ : state) {
RELEASE_ASSERT(Envoy::StringUtil::findToken(cache_control, ",", "no-transform"));
}
}
BENCHMARK(BM_FindToken);

static bool nextToken(absl::string_view& str, char delim, bool stripWhitespace,
absl::string_view* token) {
while (!str.empty()) {
absl::string_view::size_type pos = str.find(delim);
if (pos == absl::string_view::npos) {
*token = str.substr(0, str.size());
str.remove_prefix(str.size()); // clears str
} else {
*token = str.substr(0, pos);
str.remove_prefix(pos + 1); // move past token and delim
}
if (stripWhitespace) {
*token = Envoy::StringUtil::trim(*token);
}
if (!token->empty()) {
return true;
}
}
return false;
}

// Experimental alternative implementation of StringUtil::findToken which doesn't create
// a temp vector, but just iterates through the string_view, tokenizing, and matching against
// the token we want. It appears to be about 2.5x to 3x faster on this testcase.
static bool findTokenWithoutSplitting(absl::string_view str, char delim, absl::string_view token,
bool stripWhitespace) {
for (absl::string_view tok; nextToken(str, delim, stripWhitespace, &tok);) {
if (tok == token) {
return true;
}
}
return false;
}

static void BM_FindTokenWithoutSplitting(benchmark::State& state) {
const absl::string_view cache_control(CacheControl, CacheControlLength);
for (auto _ : state) {
RELEASE_ASSERT(findTokenWithoutSplitting(cache_control, ',', "no-transform", true));
}
}
BENCHMARK(BM_FindTokenWithoutSplitting);

static void BM_FindTokenValueNestedSplit(benchmark::State& state) {
const absl::string_view cache_control(CacheControl, CacheControlLength);
absl::string_view max_age;
for (auto _ : state) {
for (absl::string_view token : Envoy::StringUtil::splitToken(cache_control, ",")) {
auto name_value = Envoy::StringUtil::splitToken(token, "=");
if ((name_value.size() == 2) && (Envoy::StringUtil::trim(name_value[0]) == "max-age")) {
max_age = Envoy::StringUtil::trim(name_value[1]);
}
}
RELEASE_ASSERT(max_age == "300");
}
}
BENCHMARK(BM_FindTokenValueNestedSplit);

static void BM_FindTokenValueSearchForEqual(benchmark::State& state) {
for (auto _ : state) {
const absl::string_view cache_control(CacheControl, CacheControlLength);
absl::string_view max_age;
for (absl::string_view token : Envoy::StringUtil::splitToken(cache_control, ",")) {
absl::string_view::size_type equals = token.find('=');
if (equals != absl::string_view::npos &&
Envoy::StringUtil::trim(token.substr(0, equals)) == "max-age") {
max_age = Envoy::StringUtil::trim(token.substr(equals + 1));
}
}
RELEASE_ASSERT(max_age == "300");
}
}
BENCHMARK(BM_FindTokenValueSearchForEqual);

static void BM_FindTokenValueNoSplit(benchmark::State& state) {
for (auto _ : state) {
absl::string_view cache_control(CacheControl, CacheControlLength);
absl::string_view max_age;
for (absl::string_view token; nextToken(cache_control, ',', true, &token);) {
absl::string_view name;
if (nextToken(token, '=', true, &name) && (name == "max-age")) {
max_age = Envoy::StringUtil::trim(token);
}
}
RELEASE_ASSERT(max_age == "300");
}
}
BENCHMARK(BM_FindTokenValueNoSplit);

// Boilerplate main(), which discovers benchmarks in the same file and runs them.
int main(int argc, char** argv) {
benchmark::Initialize(&argc, argv);
if (benchmark::ReportUnrecognizedArguments(argc, argv)) {
return 1;
}
benchmark::RunSpecifiedBenchmarks();
}

0 comments on commit bfc6408

Please sign in to comment.