tp: implement an implicit segment-tree like data structure
This CL implements an implicit variant of a segment-tree [1] most
closely described by [2].
This data structure will be used in upcoming CLs to compute aggregations
over events in large traces for the UI, significantly speeding up
the queries UI makes against trace processor.
[1] https://en.algorithmica.org/hpc/data-structures/segment-trees/
[2] https://thume.ca/2021/03/14/iforests/
Change-Id: I9fe71b67985b1c777733407136e95900f6e9c5a3
diff --git a/Android.bp b/Android.bp
index 6cb0053..db1ef89 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11304,6 +11304,7 @@
name: "perfetto_src_trace_processor_containers_unittests",
srcs: [
"src/trace_processor/containers/bit_vector_unittest.cc",
+ "src/trace_processor/containers/implicit_segment_forest_unittest.cc",
"src/trace_processor/containers/null_term_string_view_unittest.cc",
"src/trace_processor/containers/row_map_unittest.cc",
"src/trace_processor/containers/string_pool_unittest.cc",
diff --git a/BUILD b/BUILD
index 12fe725..0608cb1 100644
--- a/BUILD
+++ b/BUILD
@@ -1366,6 +1366,7 @@
":include_perfetto_public_base",
":include_perfetto_public_protozero",
"src/trace_processor/containers/bit_vector.h",
+ "src/trace_processor/containers/implicit_segment_forest.h",
"src/trace_processor/containers/null_term_string_view.h",
"src/trace_processor/containers/row_map.h",
"src/trace_processor/containers/row_map_algorithms.h",
diff --git a/src/trace_processor/containers/BUILD.gn b/src/trace_processor/containers/BUILD.gn
index 43e9603..c724df8 100644
--- a/src/trace_processor/containers/BUILD.gn
+++ b/src/trace_processor/containers/BUILD.gn
@@ -22,6 +22,7 @@
perfetto_component("containers") {
public = [
"bit_vector.h",
+ "implicit_segment_forest.h",
"null_term_string_view.h",
"row_map.h",
"row_map_algorithms.h",
@@ -44,6 +45,7 @@
testonly = true
sources = [
"bit_vector_unittest.cc",
+ "implicit_segment_forest_unittest.cc",
"null_term_string_view_unittest.cc",
"row_map_unittest.cc",
"string_pool_unittest.cc",
diff --git a/src/trace_processor/containers/implicit_segment_forest.h b/src/trace_processor/containers/implicit_segment_forest.h
new file mode 100644
index 0000000..f699181
--- /dev/null
+++ b/src/trace_processor/containers/implicit_segment_forest.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto::trace_processor {
+
+// An implementation of a segment tree data structure [1] with:
+// 1) parent-child relationships are implicit, saving memory.
+// 2) the requirement for the number of values being a power of two, turning
+// the tree into a forest.
+//
+// Segment trees are a very powerful data structure allowing O(log(n)) aggregate
+// queries to be performed on an arbitrary range of elements in an array.
+// Specifically, for `T x[n]`, and an associative and commutative operation
+// AggOp (e.g. +, *, min, max, etc.), segment trees can compute
+// ```
+// T y = AggOp()(x[i], x[i + 1], x[i + 2], ..., x[j])
+// ```
+// in O(log(n)) time.
+//
+// Practically, in trace processor, this is useful for computing aggregations
+// over events in a trace. For example:
+// ```
+// struct Slice { int64_t ts; int64_t dur; };
+// struct MaxDurSlice {
+// Slice operator()(const Slice& a, const Slice& b) {
+// return a.dur < b.dur ? b : a;
+// }
+// }
+// using MipMap = ImplicitSegmentForest<Slice, MaxDurSlice>;
+// ```
+// allows building a "mipmap" [2] of a track in a trace in a UI. The UI can show
+// a representation of the items in the track when very zoomed out while
+// skipping the rendering slices which are smaller than one pixel.
+//
+// The design and implementation of this class takes heavy inspiration from
+// Tristan Hume's "IForestIndex" data structure [3] as described in his blog
+// post [4].
+//
+// [1] https://en.algorithmica.org/hpc/data-structures/segment-trees/
+// [2] https://en.wikipedia.org/wiki/Mipmap
+// [3]
+// https://github.com/trishume/gigatrace/blob/dfde0d7244f356bdc9aeefb387d904dd8b09d94a/src/iforest.rs
+// [4] https://thume.ca/2021/03/14/iforests/
+template <typename T, typename AggOp>
+class ImplicitSegmentForest {
+ public:
+ // Computes the aggregation (as specified by operator() in AggOp) over all
+ // elements in the tree between the indices [start, end). Requires that
+ // start < end.
+ //
+ // Complexity:
+ // This function performs O(log(n)) operations (n = end - start).
+ //
+ // Returns:
+ // 1) values[start]: if start + 1 == end
+ // 2) AggOp()(values[start], ..., values[end - 1]) otherwise
+ T Query(uint32_t start, uint32_t end) const {
+ PERFETTO_DCHECK(start < end);
+
+ const uint32_t in_start = start * 2;
+ const uint32_t in_end = end * 2;
+
+ uint32_t first_skip = LargestPrefixInsideSkip(in_start, in_end);
+ T aggregated = values_[AggNode(in_start, first_skip)];
+ for (uint32_t i = in_start + first_skip; i < in_end;) {
+ uint32_t skip = LargestPrefixInsideSkip(i, in_end);
+ aggregated = AggOp()(aggregated, values_[AggNode(i, skip)]);
+ i += skip;
+ }
+ return aggregated;
+ }
+
+ // Pushes a new element to right-most part of the tree. This index of this
+ // element can be used in future calls to |Query|.
+ void Push(T v) {
+ values_.emplace_back(std::move(v));
+
+ size_t len = values_.size();
+ auto levels_to_index = static_cast<uint32_t>(__builtin_ctzl(~len)) - 1;
+
+ size_t cur = len - 1;
+ for (uint32_t level = 0; level < levels_to_index; ++level) {
+ size_t prev_higher_level = cur - (1 << level);
+ values_[prev_higher_level] =
+ AggOp()(values_[prev_higher_level], values_[cur]);
+ cur = prev_higher_level;
+ }
+ values_.emplace_back(values_[len - (1 << levels_to_index)]);
+ }
+
+ // Returns the value at |n| in the tree: this corresponds to the |n|th
+ // element |Push|-ed into the tree.
+ const T& operator[](uint32_t n) { return values_[n * 2]; }
+
+ private:
+ static uint32_t Lsp(uint32_t x) { return x & -x; }
+ static uint32_t Msp(uint32_t x) {
+ return (1u << (sizeof(x) * 8 - 1)) >> __builtin_clz(x);
+ }
+ static uint32_t LargestPrefixInsideSkip(uint32_t min, uint32_t max) {
+ return Lsp(min | Msp(max - min));
+ }
+ static uint32_t AggNode(uint32_t i, uint32_t offset) {
+ return i + (offset >> 1) - 1;
+ }
+
+ std::vector<T> values_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
diff --git a/src/trace_processor/containers/implicit_segment_forest_unittest.cc b/src/trace_processor/containers/implicit_segment_forest_unittest.cc
new file mode 100644
index 0000000..16dd262
--- /dev/null
+++ b/src/trace_processor/containers/implicit_segment_forest_unittest.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <numeric>
+#include <random>
+#include <vector>
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+struct Value {
+ uint32_t value;
+};
+
+struct Sum {
+ Value operator()(const Value& a, const Value& b) {
+ return Value{a.value + b.value};
+ }
+};
+
+TEST(ImplicitSegmentTree, SimpleSum) {
+ std::vector<uint32_t> res = {209, 330, 901, 3, 10, 0, 3903, 309, 490};
+
+ ImplicitSegmentForest<Value, Sum> forest;
+ for (uint32_t x : res) {
+ forest.Push(Value{x});
+ }
+
+ for (uint32_t i = 0; i < res.size(); ++i) {
+ for (uint32_t j = i + 1; j < res.size(); ++j) {
+ ASSERT_EQ(forest.Query(i, j).value,
+ std::accumulate(res.begin() + i, res.begin() + j, 0u));
+ }
+ }
+}
+
+TEST(ImplicitSegmentTree, Stress) {
+ static constexpr size_t kCount = 9249;
+ std::minstd_rand0 rng(42);
+
+ std::vector<uint32_t> res;
+ ImplicitSegmentForest<Value, Sum> forest;
+ for (uint32_t i = 0; i < kCount; ++i) {
+ res.push_back(static_cast<uint32_t>(rng()));
+ forest.Push(Value{res.back()});
+ }
+
+ for (uint32_t i = 0; i < 10000; ++i) {
+ uint32_t s = rng() % kCount;
+ uint32_t e = s + 1 + (rng() % (kCount - s));
+ ASSERT_EQ(forest.Query(s, e).value,
+ std::accumulate(res.begin() + s, res.begin() + e, 0u));
+ }
+}
+
+} // namespace
+} // namespace perfetto::trace_processor