diff --git a/CMakeLists.txt b/CMakeLists.txt
index 35783dfa..9dedbf76 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -624,9 +624,15 @@ endif()
if(WITH_BENCHMARKS)
pkg_check_modules(BENCHMARK IMPORTED_TARGET benchmark)
- add_executable(dwarfs_benchmark test/dwarfs_benchmark.cpp)
- target_link_libraries(dwarfs_benchmark test_helpers PkgConfig::BENCHMARK)
- list(APPEND BINARY_TARGETS dwarfs_benchmark)
+ if(BENCHMARK_FOUND)
+ add_executable(dwarfs_benchmark test/dwarfs_benchmark.cpp)
+ target_link_libraries(dwarfs_benchmark test_helpers PkgConfig::BENCHMARK)
+ list(APPEND BINARY_TARGETS dwarfs_benchmark)
+ endif()
+
+ add_executable(segmenter_benchmark test/segmenter_benchmark.cpp)
+ target_link_libraries(segmenter_benchmark follybenchmark test_helpers)
+ list(APPEND BINARY_TARGETS segmenter_benchmark)
endif()
if(WITH_FUZZ)
diff --git a/test/segmenter_benchmark.cpp b/test/segmenter_benchmark.cpp
new file mode 100644
index 00000000..f9ed8b0a
--- /dev/null
+++ b/test/segmenter_benchmark.cpp
@@ -0,0 +1,272 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/**
+ * \author Marcus Holland-Moritz (github@mhxnet.de)
+ * \copyright Copyright (c) Marcus Holland-Moritz
+ *
+ * This file is part of dwarfs.
+ *
+ * dwarfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dwarfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dwarfs. If not, see .
+ */
+
+#include
+#include
+
+#include
+
+#include "dwarfs/block_data.h"
+#include "dwarfs/block_manager.h"
+#include "dwarfs/chunkable.h"
+#include "dwarfs/compression_constraints.h"
+#include "dwarfs/progress.h"
+#include "dwarfs/segmenter.h"
+
+#include "loremipsum.h"
+#include "test_logger.h"
+
+namespace {
+
+class bench_chunkable : public dwarfs::chunkable {
+ public:
+ bench_chunkable(std::vector data)
+ : data_{std::move(data)} {}
+
+ dwarfs::file const* get_file() const override { return nullptr; }
+
+ size_t size() const override { return data_.size(); }
+
+ std::string description() const override { return std::string(); }
+
+ std::span span() const override { return data_; }
+
+ void
+ add_chunk(size_t /*block*/, size_t /*offset*/, size_t /*size*/) override {}
+
+ void release_until(size_t /*offset*/) override {}
+
+ private:
+ std::vector data_;
+};
+
+std::vector
+build_data(size_t total_size, size_t granularity, double dupe_fraction,
+ std::initializer_list dupe_sizes) {
+ std::vector data;
+ data.reserve(total_size);
+
+ std::independent_bits_engine::digits, uint16_t>
+ rng;
+
+ auto granular_size = [granularity](size_t size) {
+ return size - (size % granularity);
+ };
+
+ auto make_random = [&rng](size_t size) {
+ std::vector v;
+ v.resize(size);
+ std::generate(begin(v), end(v), std::ref(rng));
+ return v;
+ };
+
+ std::vector> dupes;
+ size_t total_dupe_size{0};
+ for (auto s : dupe_sizes) {
+ auto gs = granular_size(s);
+ dupes.emplace_back(make_random(gs));
+ total_dupe_size += gs;
+ }
+
+ size_t num_dupes = (total_size * dupe_fraction) / total_dupe_size;
+ size_t rand_size = total_size - num_dupes * total_dupe_size;
+ size_t avg_rand_size =
+ num_dupes > 0 ? granular_size(rand_size / (num_dupes * dupe_sizes.size()))
+ : 0;
+
+ // std::cerr << num_dupes << std::endl;
+
+ auto append_data = [&data](std::vector const& tmp) {
+ data.resize(data.size() + tmp.size());
+ std::copy(begin(tmp), end(tmp), end(data) - tmp.size());
+ };
+
+ for (size_t i = 0; i < num_dupes * dupe_sizes.size(); ++i) {
+ append_data(dupes[i % dupe_sizes.size()]);
+ append_data(make_random(avg_rand_size));
+ }
+
+ if (data.size() > total_size) {
+ throw std::runtime_error(
+ fmt::format("internal error: {} > {}", data.size(), total_size));
+ }
+
+ append_data(make_random(total_size - data.size()));
+
+ return data;
+}
+
+void run_segmenter_test(unsigned iters, unsigned granularity,
+ unsigned window_size, unsigned block_size,
+ unsigned bloom_filter_size, unsigned lookback,
+ double dupe_fraction) {
+ folly::BenchmarkSuspender suspender;
+
+ dwarfs::segmenter::config cfg;
+ cfg.blockhash_window_size = window_size;
+ cfg.window_increment_shift = 1;
+ cfg.max_active_blocks = lookback;
+ cfg.bloom_filter_size = bloom_filter_size;
+ cfg.block_size_bits = block_size;
+
+ dwarfs::compression_constraints cc;
+ cc.granularity = granularity;
+
+ size_t total_size = 512 * 1024 * 1024;
+
+ bench_chunkable bc(
+ build_data(total_size, granularity, dupe_fraction,
+ {2 * granularity * (size_t(1) << window_size)}));
+
+ for (unsigned i = 0; i < iters; ++i) {
+ dwarfs::test::test_logger lgr;
+ dwarfs::progress prog([](dwarfs::progress const&, bool) {}, 1000);
+ auto blkmgr = std::make_shared();
+
+ std::vector> written;
+
+ dwarfs::segmenter seg(lgr, prog, blkmgr, cfg, cc, total_size,
+ [&written](std::shared_ptr blk) {
+ size_t num = written.size();
+ written.push_back(blk);
+ return num;
+ });
+
+ suspender.dismiss();
+
+ seg.add_chunkable(bc);
+ seg.finish();
+
+ suspender.rehire();
+
+ size_t segmented [[maybe_unused]]{0};
+
+ for (auto const& blk : written) {
+ segmented += blk->size();
+ }
+
+ // std::cerr << total_size << " -> " << segmented << fmt::format("
+ // ({:.1f}%)", 100.0*segmented/total_size) << std::endl;
+ }
+}
+
+constexpr unsigned const kDefaultGranularity{1};
+constexpr unsigned const kDefaultWindowSize{12};
+constexpr unsigned const kDefaultBlockSize{24};
+constexpr unsigned const kDefaultBloomFilterSize{4};
+constexpr unsigned const kDefaultLookback{1};
+constexpr double const kDefaultDupeFraction{0.3};
+
+void run_granularity(unsigned iters, unsigned granularity) {
+ run_segmenter_test(iters, granularity, kDefaultWindowSize, kDefaultBlockSize,
+ kDefaultBloomFilterSize, kDefaultLookback,
+ kDefaultDupeFraction);
+}
+
+void run_window_size(unsigned iters, unsigned window_size) {
+ run_segmenter_test(iters, kDefaultGranularity, window_size, kDefaultBlockSize,
+ kDefaultBloomFilterSize, kDefaultLookback,
+ kDefaultDupeFraction);
+}
+
+void run_block_size(unsigned iters, unsigned block_size) {
+ run_segmenter_test(iters, kDefaultGranularity, kDefaultWindowSize, block_size,
+ kDefaultBloomFilterSize, kDefaultLookback,
+ kDefaultDupeFraction);
+}
+
+void run_bloom_filter_size(unsigned iters, unsigned bloom_filter_size) {
+ run_segmenter_test(iters, kDefaultGranularity, kDefaultWindowSize,
+ kDefaultBlockSize, bloom_filter_size, kDefaultLookback,
+ kDefaultDupeFraction);
+}
+
+void run_lookback(unsigned iters, unsigned lookback) {
+ run_segmenter_test(iters, kDefaultGranularity, kDefaultWindowSize,
+ kDefaultBlockSize, kDefaultBloomFilterSize, lookback,
+ kDefaultDupeFraction);
+}
+
+void run_dupe_fraction(unsigned iters, unsigned dupe_fraction) {
+ run_segmenter_test(iters, kDefaultGranularity, kDefaultWindowSize,
+ kDefaultBlockSize, kDefaultBloomFilterSize,
+ kDefaultLookback, 0.01 * dupe_fraction);
+}
+
+} // namespace
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_granularity, 1)
+BENCHMARK_RELATIVE_PARAM(run_granularity, 2)
+BENCHMARK_RELATIVE_PARAM(run_granularity, 3)
+BENCHMARK_RELATIVE_PARAM(run_granularity, 4)
+BENCHMARK_RELATIVE_PARAM(run_granularity, 5)
+BENCHMARK_RELATIVE_PARAM(run_granularity, 6)
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_window_size, 8)
+BENCHMARK_RELATIVE_PARAM(run_window_size, 10)
+BENCHMARK_RELATIVE_PARAM(run_window_size, 12)
+BENCHMARK_RELATIVE_PARAM(run_window_size, 14)
+BENCHMARK_RELATIVE_PARAM(run_window_size, 16)
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_block_size, 18)
+BENCHMARK_RELATIVE_PARAM(run_block_size, 20)
+BENCHMARK_RELATIVE_PARAM(run_block_size, 22)
+BENCHMARK_RELATIVE_PARAM(run_block_size, 24)
+BENCHMARK_RELATIVE_PARAM(run_block_size, 26)
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_bloom_filter_size, 1)
+BENCHMARK_RELATIVE_PARAM(run_bloom_filter_size, 2)
+BENCHMARK_RELATIVE_PARAM(run_bloom_filter_size, 3)
+BENCHMARK_RELATIVE_PARAM(run_bloom_filter_size, 4)
+BENCHMARK_RELATIVE_PARAM(run_bloom_filter_size, 5)
+BENCHMARK_RELATIVE_PARAM(run_bloom_filter_size, 6)
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_lookback, 1)
+BENCHMARK_RELATIVE_PARAM(run_lookback, 2)
+BENCHMARK_RELATIVE_PARAM(run_lookback, 4)
+BENCHMARK_RELATIVE_PARAM(run_lookback, 8)
+BENCHMARK_RELATIVE_PARAM(run_lookback, 16)
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK_PARAM(run_dupe_fraction, 0)
+BENCHMARK_RELATIVE_PARAM(run_dupe_fraction, 20)
+BENCHMARK_RELATIVE_PARAM(run_dupe_fraction, 40)
+BENCHMARK_RELATIVE_PARAM(run_dupe_fraction, 60)
+BENCHMARK_RELATIVE_PARAM(run_dupe_fraction, 80)
+
+BENCHMARK_DRAW_LINE();
+
+int main(int argc, char* argv[]) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ folly::runBenchmarks();
+}