Merge "tp: dbv2: define finalized StorageOverlay functions"
diff --git a/BUILD b/BUILD
index cfc4829..81affad 100644
--- a/BUILD
+++ b/BUILD
@@ -1289,7 +1289,6 @@
"src/trace_processor/db/storage/numeric_storage.h",
"src/trace_processor/db/storage/storage.cc",
"src/trace_processor/db/storage/storage.h",
- "src/trace_processor/db/storage/storage_variants.h",
"src/trace_processor/db/storage/types.h",
],
)
diff --git a/src/trace_processor/db/null_overlay.cc b/src/trace_processor/db/null_overlay.cc
index db8c572..71b38f4 100644
--- a/src/trace_processor/db/null_overlay.cc
+++ b/src/trace_processor/db/null_overlay.cc
@@ -16,8 +16,6 @@
#include "src/trace_processor/db/null_overlay.h"
-#include "src/trace_processor/db/storage/storage_variants.h"
-
namespace perfetto {
namespace trace_processor {
namespace overlays {
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index b3fc3cf..81d3b5f 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -20,7 +20,6 @@
"numeric_storage.h",
"storage.cc",
"storage.h",
- "storage_variants.h",
"types.h",
]
deps = [
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index 81d40ac..eb1dd08 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -16,65 +16,347 @@
*/
#include "src/trace_processor/db/storage/numeric_storage.h"
-
-#include <variant>
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/types.h"
namespace perfetto {
namespace trace_processor {
namespace storage {
-
namespace {
-// Templated part of FastPathComparison.
+// All viable numeric values for ColumnTypes.
+using NumericValue = std::variant<uint32_t, int32_t, int64_t, double_t>;
+
+// Using the fact that binary operators in std are operators() of classes, we
+// can wrap those classes in variants and use them for std::visit in
+// SerialComparators. This helps prevent excess templating and switches.
template <typename T>
-inline void TypedFastPathComparison(std::optional<NumericValue> val,
- FilterOp op,
- const T* start,
- uint32_t num_elements,
- BitVector::Builder& builder) {
- if (!val) {
- builder.Skip(num_elements);
- return;
+using FilterOpVariant = std::variant<std::greater<T>,
+ std::greater_equal<T>,
+ std::less<T>,
+ std::less_equal<T>,
+ std::equal_to<T>,
+ std::not_equal_to<T>>;
+
+// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
+// std::nullopt if SqlValue can't be cast and should be considered invalid for
+// comparison.
+inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type,
+ SqlValue val) {
+ if (val.is_null())
+ return std::nullopt;
+
+ switch (type) {
+ case ColumnType::kDouble:
+ return val.AsDouble();
+ case ColumnType::kInt64:
+ return val.AsLong();
+ case ColumnType::kInt32:
+ if (val.AsLong() > std::numeric_limits<int32_t>::max() ||
+ val.AsLong() < std::numeric_limits<int32_t>::min())
+ return std::nullopt;
+ return static_cast<int32_t>(val.AsLong());
+ case ColumnType::kUint32:
+ if (val.AsLong() > std::numeric_limits<uint32_t>::max() ||
+ val.AsLong() < std::numeric_limits<uint32_t>::min())
+ return std::nullopt;
+ return static_cast<uint32_t>(val.AsLong());
+ case ColumnType::kString:
+ case ColumnType::kDummy:
+ case ColumnType::kId:
+ return std::nullopt;
}
- std::visit(
- [val, start, num_elements, &builder](auto comparator) {
- T typed_val = std::get<T>(*val);
- for (uint32_t i = 0; i < num_elements; i += BitVector::kBitsInWord) {
- uint64_t word = 0;
- // This part should be optimised by SIMD and is expected to be fast.
- for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k) {
- bool comp_result = comparator(start[i + k], typed_val);
- word |= static_cast<uint64_t>(comp_result) << k;
- }
- builder.AppendWord(word);
- }
- },
- GetFilterOpVariant<T>(op));
+ PERFETTO_FATAL("For GCC");
}
-// Templated part of SlowPathComparison.
+// Fetch std binary comparator class based on FilterOp. Can be used in
+// std::visit for comparison.
template <typename T>
-inline void TypedSlowPathComparison(std::optional<NumericValue> val,
- FilterOp op,
- const T* start,
- uint32_t num_elements,
- BitVector::Builder& builder) {
- if (!val) {
- builder.Skip(num_elements);
- return;
+inline FilterOpVariant<T> GetFilterOpVariant(FilterOp op) {
+ switch (op) {
+ case FilterOp::kEq:
+ return FilterOpVariant<T>(std::equal_to<T>());
+ case FilterOp::kNe:
+ return FilterOpVariant<T>(std::not_equal_to<T>());
+ case FilterOp::kGe:
+ return FilterOpVariant<T>(std::greater_equal<T>());
+ case FilterOp::kGt:
+ return FilterOpVariant<T>(std::greater<T>());
+ case FilterOp::kLe:
+ return FilterOpVariant<T>(std::less_equal<T>());
+ case FilterOp::kLt:
+ return FilterOpVariant<T>(std::less<T>());
+ case FilterOp::kGlob:
+ case FilterOp::kIsNotNull:
+ case FilterOp::kIsNull:
+ PERFETTO_FATAL("Not a valid operation on numeric type.");
}
- std::visit(
- [val, start, num_elements, &builder](auto comparator) {
- T typed_val = std::get<T>(*val);
- for (uint32_t i = 0; i < num_elements; ++i) {
- builder.Append(comparator(start[i], typed_val));
- }
+ PERFETTO_FATAL("For GCC");
+}
+
+uint32_t LowerBoundIntrinsic(const void* data,
+ NumericValue val,
+ RowMap::Range search_range) {
+ return std::visit(
+ [data, search_range](auto val_data) {
+ using T = decltype(val_data);
+ const T* typed_start = static_cast<const T*>(data);
+ auto lower = std::lower_bound(typed_start + search_range.start,
+ typed_start + search_range.end, val_data);
+ return static_cast<uint32_t>(std::distance(typed_start, lower));
},
- GetFilterOpVariant<T>(op));
+ val);
+}
+
+uint32_t UpperBoundIntrinsic(const void* data,
+ NumericValue val,
+ RowMap::Range search_range) {
+ return std::visit(
+ [data, search_range](auto val_data) {
+ using T = decltype(val_data);
+ const T* typed_start = static_cast<const T*>(data);
+ auto upper = std::upper_bound(typed_start + search_range.start,
+ typed_start + search_range.end, val_data);
+ return static_cast<uint32_t>(std::distance(typed_start, upper));
+ },
+ val);
+}
+
+uint32_t LowerBoundExtrinsic(const void* data,
+ NumericValue val,
+ uint32_t* indices,
+ uint32_t indices_count) {
+ return std::visit(
+ [data, indices, indices_count](auto val_data) {
+ using T = decltype(val_data);
+ const T* typed_start = static_cast<const T*>(data);
+ auto lower =
+ std::lower_bound(indices, indices + indices_count, val_data,
+ [typed_start](uint32_t index, T val) {
+ return typed_start[index] < val;
+ });
+ return static_cast<uint32_t>(std::distance(indices, lower));
+ },
+ val);
+}
+
+uint32_t UpperBoundExtrinsic(const void* data,
+ NumericValue val,
+ uint32_t* indices,
+ uint32_t indices_count) {
+ return std::visit(
+ [data, indices, indices_count](auto val_data) {
+ using T = decltype(val_data);
+ const T* typed_start = static_cast<const T*>(data);
+ auto upper =
+ std::upper_bound(indices, indices + indices_count, val_data,
+ [typed_start](T val, uint32_t index) {
+ return val < typed_start[index];
+ });
+ return static_cast<uint32_t>(std::distance(indices, upper));
+ },
+ val);
+}
+
+template <typename T, typename Comparator>
+void TypedLinearSearch(T typed_val,
+ const T* start,
+ Comparator comparator,
+ BitVector::Builder& builder) {
+ // Slow path: we compare <64 elements and append to get us to a word
+ // boundary.
+ const T* ptr = start;
+ uint32_t front_elements = builder.BitsUntilWordBoundaryOrFull();
+ for (uint32_t i = 0; i < front_elements; ++i) {
+ builder.Append(comparator(ptr[i], typed_val));
+ }
+ ptr += front_elements;
+
+ // Fast path: we compare as many groups of 64 elements as we can.
+ // This should be very easy for the compiler to auto-vectorize.
+ uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
+ for (uint32_t i = 0; i < fast_path_elements; i += BitVector::kBitsInWord) {
+ uint64_t word = 0;
+ // This part should be optimised by SIMD and is expected to be fast.
+ for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k) {
+ bool comp_result = comparator(start[i + k], typed_val);
+ word |= static_cast<uint64_t>(comp_result) << k;
+ }
+ builder.AppendWord(word);
+ }
+ ptr += fast_path_elements;
+
+ // Slow path: we compare <64 elements and append to fill the Builder.
+ uint32_t back_elements = builder.BitsUntilFull();
+ for (uint32_t i = 0; i < back_elements; ++i) {
+ builder.Append(comparator(ptr[i], typed_val));
+ }
+}
+
+template <typename T, typename Comparator>
+void TypedIndexSearch(T typed_val,
+ const T* start,
+ uint32_t* indices,
+ Comparator comparator,
+ BitVector::Builder& builder) {
+ // Slow path: we compare <64 elements and append to get us to a word
+ // boundary.
+ const T* ptr = start;
+ uint32_t front_elements = builder.BitsUntilWordBoundaryOrFull();
+ for (uint32_t i = 0; i < front_elements; ++i) {
+ builder.Append(comparator(ptr[indices[i]], typed_val));
+ }
+ ptr += front_elements;
+
+ // Fast path: we compare as many groups of 64 elements as we can.
+ // This should be very easy for the compiler to auto-vectorize.
+ uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
+ for (uint32_t i = 0; i < fast_path_elements; i += BitVector::kBitsInWord) {
+ uint64_t word = 0;
+ // This part should be optimised by SIMD and is expected to be fast.
+ for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k) {
+ bool comp_result = comparator(start[indices[i + k]], typed_val);
+ word |= static_cast<uint64_t>(comp_result) << k;
+ }
+ builder.AppendWord(word);
+ }
+ ptr += fast_path_elements;
+
+ // Slow path: we compare <64 elements and append to fill the Builder.
+ uint32_t back_elements = builder.BitsUntilFull();
+ for (uint32_t i = 0; i < back_elements; ++i) {
+ builder.Append(comparator(ptr[indices[i]], typed_val));
+ }
}
} // namespace
+BitVector NumericStorage::LinearSearch(FilterOp op,
+ SqlValue sql_val,
+ RowMap::Range range) const {
+ std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+ if (op == FilterOp::kIsNotNull)
+ return BitVector(size(), true);
+
+ if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+ return BitVector();
+
+ BitVector::Builder builder(range.end);
+ builder.Skip(range.start);
+ std::visit(
+ [this, range, op, &builder](auto val) {
+ using T = decltype(val);
+ auto* start = static_cast<const T*>(data_) + range.start;
+ std::visit(
+ [start, val, &builder](auto comparator) {
+ TypedLinearSearch(val, start, comparator, builder);
+ },
+ GetFilterOpVariant<T>(op));
+ },
+ *val);
+ return std::move(builder).Build();
+}
+
+BitVector NumericStorage::IndexSearch(FilterOp op,
+ SqlValue sql_val,
+ uint32_t* indices,
+ uint32_t indices_count) const {
+ std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+ if (op == FilterOp::kIsNotNull)
+ return BitVector(size(), true);
+
+ if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+ return BitVector();
+
+ BitVector::Builder builder(indices_count);
+ std::visit(
+ [this, indices, op, &builder](auto val) {
+ using T = decltype(val);
+ auto* start = static_cast<const T*>(data_);
+ std::visit(
+ [start, indices, val, &builder](auto comparator) {
+ TypedIndexSearch(val, start, indices, comparator, builder);
+ },
+ GetFilterOpVariant<T>(op));
+ },
+ *val);
+ return std::move(builder).Build();
+}
+
+RowMap::Range NumericStorage::BinarySearchIntrinsic(
+ FilterOp op,
+ SqlValue sql_val,
+ RowMap::Range search_range) const {
+ std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+ if (op == FilterOp::kIsNotNull)
+ return RowMap::Range(0, size());
+
+ if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+ return RowMap::Range();
+
+ switch (op) {
+ case FilterOp::kEq:
+ return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+ UpperBoundIntrinsic(data_, *val, search_range));
+ case FilterOp::kLe:
+ return RowMap::Range(0, UpperBoundIntrinsic(data_, *val, search_range));
+ case FilterOp::kLt:
+ return RowMap::Range(0, LowerBoundIntrinsic(data_, *val, search_range));
+ case FilterOp::kGe:
+ return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+ size_);
+ case FilterOp::kGt:
+ return RowMap::Range(UpperBoundIntrinsic(data_, *val, search_range),
+ size_);
+ case FilterOp::kNe:
+ case FilterOp::kIsNull:
+ case FilterOp::kIsNotNull:
+ case FilterOp::kGlob:
+ return RowMap::Range();
+ }
+ return RowMap::Range();
+}
+
+RowMap::Range NumericStorage::BinarySearchExtrinsic(
+ FilterOp op,
+ SqlValue sql_val,
+ uint32_t* indices,
+ uint32_t indices_count) const {
+ std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+
+ if (op == FilterOp::kIsNotNull)
+ return RowMap::Range(0, size());
+
+ if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+ return RowMap::Range();
+
+ switch (op) {
+ case FilterOp::kEq:
+ return RowMap::Range(
+ LowerBoundExtrinsic(data_, *val, indices, indices_count),
+ UpperBoundExtrinsic(data_, *val, indices, indices_count));
+ case FilterOp::kLe:
+ return RowMap::Range(
+ 0, UpperBoundExtrinsic(data_, *val, indices, indices_count));
+ case FilterOp::kLt:
+ return RowMap::Range(
+ 0, LowerBoundExtrinsic(data_, *val, indices, indices_count));
+ case FilterOp::kGe:
+ return RowMap::Range(
+ LowerBoundExtrinsic(data_, *val, indices, indices_count), size_);
+ case FilterOp::kGt:
+ return RowMap::Range(
+ UpperBoundExtrinsic(data_, *val, indices, indices_count), size_);
+ case FilterOp::kNe:
+ case FilterOp::kIsNull:
+ case FilterOp::kIsNotNull:
+ case FilterOp::kGlob:
+ return RowMap::Range();
+ }
+ return RowMap::Range();
+}
+
void NumericStorage::StableSort(uint32_t* rows, uint32_t rows_size) const {
NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
std::visit(
@@ -93,184 +375,6 @@
void NumericStorage::Sort(uint32_t*, uint32_t) const {}
-// Responsible for invoking templated version of FastPathComparison.
-void NumericStorage::LinearSearchAligned(FilterOp op,
- SqlValue sql_val,
- uint32_t offset,
- uint32_t num_elements,
- BitVector::Builder& builder) const {
- PERFETTO_DCHECK(num_elements % BitVector::kBitsInWord == 0);
- std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-
- // If the value is invalid we should just ignore those elements.
- if (!val.has_value() || op == FilterOp::kIsNotNull ||
- op == FilterOp::kIsNull || op == FilterOp::kGlob) {
- builder.Skip(num_elements);
- return;
- }
- std::visit(
- [this, op, offset, num_elements, &builder](auto num_val) {
- using T = decltype(num_val);
- auto* typed_start = static_cast<const T*>(data_) + offset;
- TypedFastPathComparison(num_val, op, typed_start, num_elements,
- builder);
- },
- *val);
-}
-
-// Responsible for invoking templated version of SlowPathComparison.
-void NumericStorage::LinearSearchUnaligned(FilterOp op,
- SqlValue sql_val,
- uint32_t offset,
- uint32_t num_elements,
- BitVector::Builder& builder) const {
- std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-
- // If the value is invalid we should just ignore those elements.
- if (!val.has_value() || op == FilterOp::kIsNotNull ||
- op == FilterOp::kIsNull || op == FilterOp::kGlob) {
- builder.Skip(num_elements);
- return;
- }
-
- std::visit(
- [this, op, offset, num_elements, &builder](auto val) {
- using T = decltype(val);
- auto* typed_start = static_cast<const T*>(data_) + offset;
- TypedSlowPathComparison(val, op, typed_start, num_elements, builder);
- },
- *val);
-}
-
-uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
- Range search_range) const {
- return std::visit(
- [this, search_range](auto val_data) {
- using T = decltype(val_data);
- const T* typed_start = static_cast<const T*>(data_);
- auto upper = std::upper_bound(typed_start + search_range.start,
- typed_start + search_range.end, val_data);
- return static_cast<uint32_t>(std::distance(typed_start, upper));
- },
- val);
-}
-
-// As we don't template those functions, we need to use std::visitor to type
-// `start`, hence this wrapping.
-uint32_t NumericStorage::LowerBoundIndex(NumericValue val,
- Range search_range) const {
- return std::visit(
- [this, search_range](auto val_data) {
- using T = decltype(val_data);
- const T* typed_start = static_cast<const T*>(data_);
- auto lower = std::lower_bound(typed_start + search_range.start,
- typed_start + search_range.end, val_data);
- return static_cast<uint32_t>(std::distance(typed_start, lower));
- },
- val);
-}
-
-std::optional<Range> NumericStorage::BinarySearch(FilterOp op,
- SqlValue sql_val,
- Range search_range) const {
- std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
- if (op == FilterOp::kIsNotNull)
- return Range(0, size());
-
- if (!val)
- return std::nullopt;
-
- switch (op) {
- case FilterOp::kEq:
- return Range(LowerBoundIndex(*val, search_range),
- UpperBoundIndex(*val, search_range));
- case FilterOp::kLe:
- return Range(0, UpperBoundIndex(*val, search_range));
- case FilterOp::kLt:
- return Range(0, LowerBoundIndex(*val, search_range));
- case FilterOp::kGe:
- return Range(LowerBoundIndex(*val, search_range), size_);
- case FilterOp::kGt:
- return Range(UpperBoundIndex(*val, search_range), size_);
- case FilterOp::kNe:
- case FilterOp::kIsNull:
- case FilterOp::kIsNotNull:
- case FilterOp::kGlob:
- return std::nullopt;
- }
- return std::nullopt;
-}
-
-uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
- uint32_t* order,
- Range search_range) const {
- return std::visit(
- [this, order, search_range](auto val_data) {
- using T = decltype(val_data);
- const T* typed_start = static_cast<const T*>(data_);
- auto upper = std::upper_bound(order + search_range.start,
- order + search_range.end, val_data,
- [typed_start](T val, uint32_t index) {
- return val < *(typed_start + index);
- });
- return static_cast<uint32_t>(std::distance(order, upper));
- },
- val);
-}
-
-// As we don't template those functions, we need to use std::visitor to type
-// `start`, hence this wrapping.
-uint32_t NumericStorage::LowerBoundIndex(NumericValue val,
- uint32_t* order,
- Range search_range) const {
- return std::visit(
- [this, order, search_range](auto val_data) {
- using T = decltype(val_data);
- const T* typed_start = static_cast<const T*>(data_);
- auto lower = std::lower_bound(order + search_range.start,
- order + search_range.end, val_data,
- [typed_start](uint32_t index, T val) {
- return *(typed_start + index) < val;
- });
- return static_cast<uint32_t>(std::distance(order, lower));
- },
- val);
-}
-
-std::optional<Range> NumericStorage::BinarySearchWithIndex(
- FilterOp op,
- SqlValue sql_val,
- uint32_t* order,
- Range search_range) const {
- std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-
- if (op == FilterOp::kIsNotNull)
- return Range(0, size());
-
- if (!val.has_value())
- return std::nullopt;
-
- switch (op) {
- case FilterOp::kEq:
- return Range(LowerBoundIndex(*val, order, search_range),
- UpperBoundIndex(*val, order, search_range));
- case FilterOp::kLe:
- return Range(0, UpperBoundIndex(*val, order, search_range));
- case FilterOp::kLt:
- return Range(0, LowerBoundIndex(*val, order, search_range));
- case FilterOp::kGe:
- return Range(LowerBoundIndex(*val, order, search_range), size_);
- case FilterOp::kGt:
- return Range(UpperBoundIndex(*val, order, search_range), size_);
- case FilterOp::kNe:
- case FilterOp::kIsNull:
- case FilterOp::kIsNotNull:
- case FilterOp::kGlob:
- return std::nullopt;
- }
- return std::nullopt;
-}
-
} // namespace storage
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/db/storage/numeric_storage.h b/src/trace_processor/db/storage/numeric_storage.h
index a926447..ab802de 100644
--- a/src/trace_processor/db/storage/numeric_storage.h
+++ b/src/trace_processor/db/storage/numeric_storage.h
@@ -19,12 +19,13 @@
#include <variant>
#include "src/trace_processor/db/storage/storage.h"
-#include "src/trace_processor/db/storage/storage_variants.h"
+#include "src/trace_processor/db/storage/types.h"
namespace perfetto {
namespace trace_processor {
namespace storage {
+// Storage for all numeric type data (i.e. doubles, int32, int64, uint32).
class NumericStorage : public Storage {
public:
NumericStorage(void* data, uint32_t size, ColumnType type)
@@ -34,53 +35,31 @@
void Sort(uint32_t* rows, uint32_t rows_size) const override;
- void LinearSearchAligned(FilterOp op,
- SqlValue val,
- uint32_t offset,
- uint32_t num_elements,
- BitVector::Builder& builder) const override;
+ BitVector LinearSearch(FilterOp op,
+ SqlValue val,
+ RowMap::Range) const override;
- void LinearSearchUnaligned(FilterOp op,
- SqlValue val,
- uint32_t offset,
- uint32_t num_elements,
- BitVector::Builder& builder) const override;
+ BitVector IndexSearch(FilterOp op,
+ SqlValue value,
+ uint32_t* indices,
+ uint32_t indices_count) const override;
- std::optional<Range> BinarySearch(FilterOp op,
- SqlValue val,
- Range search_range) const override;
+ RowMap::Range BinarySearchIntrinsic(
+ FilterOp op,
+ SqlValue val,
+ RowMap::Range search_range) const override;
- std::optional<Range> BinarySearchWithIndex(FilterOp op,
- SqlValue val,
- uint32_t* order,
- Range search_range) const override;
+ RowMap::Range BinarySearchExtrinsic(FilterOp op,
+ SqlValue val,
+ uint32_t* indices,
+ uint32_t indices_count) const override;
uint32_t size() const override { return size_; }
private:
- // As we don't template those functions, we need to use std::visitor to type
- // `start`, hence this wrapping.
- uint32_t UpperBoundIndex(NumericValue val, Range search_range) const;
-
- // As we don't template those functions, we need to use std::visitor to type
- // `start`, hence this wrapping.
- uint32_t LowerBoundIndex(NumericValue val, Range search_range) const;
-
- // As we don't template those functions, we need to use std::visitor to type
- // `start`, hence this wrapping.
- uint32_t UpperBoundIndex(NumericValue val,
- uint32_t* order,
- Range search_range) const;
-
- // As we don't template those functions, we need to use std::visitor to type
- // `start`, hence this wrapping.
- uint32_t LowerBoundIndex(NumericValue val,
- uint32_t* order,
- Range search_range) const;
-
- const ColumnType type_;
- const void* data_;
- const uint32_t size_;
+ const ColumnType type_ = ColumnType::kDummy;
+ const void* data_ = nullptr;
+ const uint32_t size_ = 0;
};
} // namespace storage
diff --git a/src/trace_processor/db/storage/storage.h b/src/trace_processor/db/storage/storage.h
index 23aa84d..1021723 100644
--- a/src/trace_processor/db/storage/storage.h
+++ b/src/trace_processor/db/storage/storage.h
@@ -17,6 +17,7 @@
#define SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/db/storage/types.h"
@@ -24,55 +25,64 @@
namespace trace_processor {
namespace storage {
-using Range = RowMap::Range;
-
-// Most base column interpreting layer - responsible for implementing searches
-// and sorting.
+// Backing storage for columnar tables.
class Storage {
public:
virtual ~Storage();
- // Changes the vector of indices to represent the sorted (stable sort) state
- // of the column.
- virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
+ // Searches for elements which match |op| and |value| between |range.start|
+ // and |range.end|.
+ //
+ // Returns a BitVector of size |range.end| with the position of the 1s
+ // representing the positions which matched and 0s otherwise. The first
+ // |range.start| number of elements will be zero.
+ virtual BitVector LinearSearch(FilterOp op,
+ SqlValue value,
+ RowMap::Range range) const = 0;
- // Changes the vector of indices to represent the sorted (not stable) state of
- // the column.
+ // Searches for elements which match |op| and |value| at the positions given
+ // by |indices| array.
+ //
+ // Returns a BitVector of size |indices_count| with the position of the 1s
+ // representing the positions which matched and 0s otherwise.
+ virtual BitVector IndexSearch(FilterOp op,
+ SqlValue value,
+ uint32_t* indices,
+ uint32_t indices_count) const = 0;
+
+ // Binary searches for elements which match |op| and |value| between
+ // |range.start_index| and |range.end_index|.
+ //
+ // Returns a range, indexing the storage, where all elements in that range
+ // match the constraint.
+ //
+ // Note: the caller *must* know that the elements in this storage are sorted;
+ // it is an error to call this method otherwise.
+ virtual RowMap::Range BinarySearchIntrinsic(FilterOp op,
+ SqlValue value,
+ RowMap::Range range) const = 0;
+
+ // Binary searches for elements which match |op| and |value| only considering
+ // the elements in |indices|.
+ //
+ // Returns a sub-Range of Range[0, indices_count) which indicates the
+ // positions of elements in |indices| which match.
+ //
+ // Note: the caller *must* known that the elements in storage will be sorted
+ // by the elements in |indices|; it is undefined behaviour to call this method
+ // otherwise.
+ virtual RowMap::Range BinarySearchExtrinsic(FilterOp op,
+ SqlValue value,
+ uint32_t* indices,
+ uint32_t indices_count) const = 0;
+
+ // Sorts |rows| in ascending order with the comparator:
+ // data[rows[a]] < data[rows[b]].
virtual void Sort(uint32_t* rows, uint32_t rows_size) const = 0;
- // Efficiently compares series of |num_elements| of data from |data_start| to
- // comparator value and appends results to BitVector::Builder. Should be used
- // where possible
- virtual void LinearSearchAligned(FilterOp op,
- SqlValue value,
- uint32_t offset,
- uint32_t compare_elements_count,
- BitVector::Builder&) const = 0;
-
- // Inefficiently compares series of |num_elements| of data from |data_start|
- // to comparator value and appends results to BitVector::Builder. Should be
- // avoided if possible, with `LinearSearchAligned` used instead.
- virtual void LinearSearchUnaligned(FilterOp op,
- SqlValue value,
- uint32_t offset,
- uint32_t compare_elements_count,
- BitVector::Builder&) const = 0;
-
- // Compares sorted (asc) series data in |range| with comparator value. Should
- // be used where possible. Returns the Range of indices which match the
- // constraint.
- virtual std::optional<Range> BinarySearch(FilterOp op,
- SqlValue value,
- Range search_range) const = 0;
-
- // Compares sorted (asc) with |order| vector series in |range| with comparator
- // value. Should be used where possible. Returns the Range of indices
- // inside |order| vector which match the constraint.
- virtual std::optional<Range> BinarySearchWithIndex(
- FilterOp op,
- SqlValue value,
- uint32_t* order,
- Range search_range) const = 0;
+ // Stable sorts |rows| in ascending order with the comparator:
+ // data[rows[a]] < data[rows[b]].
+ virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
// Number of elements in stored data.
virtual uint32_t size() const = 0;
diff --git a/src/trace_processor/db/storage/storage_unittest.cc b/src/trace_processor/db/storage/storage_unittest.cc
index 0f7904c..6d737b9 100644
--- a/src/trace_processor/db/storage/storage_unittest.cc
+++ b/src/trace_processor/db/storage/storage_unittest.cc
@@ -17,14 +17,16 @@
#include <numeric>
#include "src/trace_processor/db/storage/numeric_storage.h"
+
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace trace_processor {
namespace storage {
-
namespace {
+using Range = RowMap::Range;
+
TEST(NumericStorageUnittest, StableSortTrivial) {
std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
std::vector<uint32_t> out = {0, 1, 2, 3, 4, 5, 6, 7, 8};
@@ -49,42 +51,12 @@
ASSERT_EQ(out, stable_out);
}
-TEST(NumericStorageUnittest, CompareSlow) {
- uint32_t size = 10;
- std::vector<uint32_t> data_vec(size);
- std::iota(data_vec.begin(), data_vec.end(), 0);
- NumericStorage storage(data_vec.data(), size, ColumnType::kUint32);
- BitVector::Builder builder(size);
- storage.LinearSearchUnaligned(FilterOp::kGe, SqlValue::Long(5), 0, size,
- builder);
- BitVector bv = std::move(builder).Build();
-
- ASSERT_EQ(bv.CountSetBits(), 5u);
- ASSERT_EQ(bv.IndexOfNthSet(0), 5u);
-}
-
-TEST(NumericStorageUnittest, CompareSlowLarge) {
- uint32_t size = 1025;
- std::vector<uint32_t> data_vec(size);
- std::iota(data_vec.begin(), data_vec.end(), 0);
- NumericStorage storage(data_vec.data(), size, ColumnType::kUint32);
- BitVector::Builder builder(size);
- storage.LinearSearchUnaligned(FilterOp::kGe, SqlValue::Long(5), 0, size,
- builder);
- BitVector bv = std::move(builder).Build();
-
- ASSERT_EQ(bv.CountSetBits(), 1020u);
- ASSERT_EQ(bv.IndexOfNthSet(0), 5u);
-}
-
TEST(NumericStorageUnittest, CompareFast) {
std::vector<uint32_t> data_vec(128);
std::iota(data_vec.begin(), data_vec.end(), 0);
NumericStorage storage(data_vec.data(), 128, ColumnType::kUint32);
- BitVector::Builder builder(128);
- storage.LinearSearchAligned(FilterOp::kGe, SqlValue::Long(100), 0, 128,
- builder);
- BitVector bv = std::move(builder).Build();
+ BitVector bv =
+ storage.LinearSearch(FilterOp::kGe, SqlValue::Long(100), Range(0, 128));
ASSERT_EQ(bv.CountSetBits(), 28u);
ASSERT_EQ(bv.IndexOfNthSet(0), 100u);
@@ -94,12 +66,12 @@
std::vector<uint32_t> data_vec(128);
std::iota(data_vec.begin(), data_vec.end(), 0);
NumericStorage storage(data_vec.data(), 128, ColumnType::kUint32);
- std::optional<Range> range =
- storage.BinarySearch(FilterOp::kGe, SqlValue::Long(100), Range(0, 128));
+ Range range = storage.BinarySearchIntrinsic(
+ FilterOp::kGe, SqlValue::Long(100), Range(0, 128));
- ASSERT_EQ(range->size(), 28u);
- ASSERT_EQ(range->start, 100u);
- ASSERT_EQ(range->end, 128u);
+ ASSERT_EQ(range.size(), 28u);
+ ASSERT_EQ(range.start, 100u);
+ ASSERT_EQ(range.end, 128u);
}
TEST(NumericStorageUnittest, CompareSortedIndexesGreaterEqual) {
@@ -108,8 +80,8 @@
NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
- std::optional<Range> range = storage.BinarySearchWithIndex(
- FilterOp::kGe, SqlValue::Long(60), sorted_order.data(), Range(0, 10));
+ std::optional<Range> range = storage.BinarySearchExtrinsic(
+ FilterOp::kGe, SqlValue::Long(60), sorted_order.data(), 10);
ASSERT_EQ(range->size(), 4u);
ASSERT_EQ(range->start, 6u);
@@ -122,8 +94,8 @@
NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
- std::optional<Range> range = storage.BinarySearchWithIndex(
- FilterOp::kLt, SqlValue::Long(60), sorted_order.data(), Range(0, 10));
+ std::optional<Range> range = storage.BinarySearchExtrinsic(
+ FilterOp::kLt, SqlValue::Long(60), sorted_order.data(), 10);
ASSERT_EQ(range->size(), 6u);
ASSERT_EQ(range->start, 0u);
@@ -136,8 +108,8 @@
NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
- std::optional<Range> range = storage.BinarySearchWithIndex(
- FilterOp::kEq, SqlValue::Long(60), sorted_order.data(), Range(0, 10));
+ std::optional<Range> range = storage.BinarySearchExtrinsic(
+ FilterOp::kEq, SqlValue::Long(60), sorted_order.data(), 10);
ASSERT_EQ(range->size(), 1u);
ASSERT_EQ(range->start, 6u);
diff --git a/src/trace_processor/db/storage/storage_variants.h b/src/trace_processor/db/storage/storage_variants.h
deleted file mode 100644
index 799301c..0000000
--- a/src/trace_processor/db/storage/storage_variants.h
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2023 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_DB_STORAGE_STORAGE_VARIANTS_H_
-#define SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_VARIANTS_H_
-
-#include <variant>
-
-#include "src/trace_processor/db/storage/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace storage {
-
-// All viable numeric values for ColumnTypes.
-using NumericValue = std::variant<uint32_t, int32_t, int64_t, double_t>;
-
-// Using the fact that binary operators in std are operators() of classes, we
-// can wrap those classes in variants and use them for std::visit in
-// SerialComparators. This helps prevent excess templating and switches.
-template <typename T>
-using FilterOpVariant = std::variant<std::greater<T>,
- std::greater_equal<T>,
- std::less<T>,
- std::less_equal<T>,
- std::equal_to<T>,
- std::not_equal_to<T>>;
-
-// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
-// std::nullopt if SqlValue can't be cast and should be considered invalid for
-// comparison.
-inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type,
- SqlValue val) {
- if (val.is_null())
- return std::nullopt;
-
- switch (type) {
- case ColumnType::kDouble:
- return val.AsDouble();
- case ColumnType::kInt64:
- return val.AsLong();
- case ColumnType::kInt32:
- if (val.AsLong() > std::numeric_limits<int32_t>::max() ||
- val.AsLong() < std::numeric_limits<int32_t>::min())
- return std::nullopt;
- return static_cast<int32_t>(val.AsLong());
- case ColumnType::kUint32:
- if (val.AsLong() > std::numeric_limits<uint32_t>::max() ||
- val.AsLong() < std::numeric_limits<uint32_t>::min())
- return std::nullopt;
- return static_cast<uint32_t>(val.AsLong());
- case ColumnType::kString:
- case ColumnType::kDummy:
- case ColumnType::kId:
- return std::nullopt;
- }
- PERFETTO_FATAL("For GCC");
-}
-
-// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
-// std::nullopt if SqlValue can't be cast and should be considered invalid for
-// comparison.
-inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type) {
- return GetNumericTypeVariant(type, SqlValue::Long(0));
-}
-
-// Fetch std binary comparator class based on FilterOp. Can be used in
-// std::visit for comparison.
-template <typename T>
-inline FilterOpVariant<T> GetFilterOpVariant(FilterOp op) {
- switch (op) {
- case FilterOp::kEq:
- return FilterOpVariant<T>(std::equal_to<T>());
- case FilterOp::kNe:
- return FilterOpVariant<T>(std::not_equal_to<T>());
- case FilterOp::kGe:
- return FilterOpVariant<T>(std::greater_equal<T>());
- case FilterOp::kGt:
- return FilterOpVariant<T>(std::greater<T>());
- case FilterOp::kLe:
- return FilterOpVariant<T>(std::less_equal<T>());
- case FilterOp::kLt:
- return FilterOpVariant<T>(std::less<T>());
- case FilterOp::kGlob:
- case FilterOp::kIsNotNull:
- case FilterOp::kIsNull:
- PERFETTO_FATAL("Not a valid operation on numeric type.");
- }
- PERFETTO_FATAL("For GCC");
-}
-
-} // namespace storage
-} // namespace trace_processor
-} // namespace perfetto
-#endif // SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_VARIANTS_H_