blob: 3b66e2207db40a2f1d466d8dc3e0dff39bf5d037 [file] [log] [blame]
/*
* 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.
*/
#include <variant>
#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/db/column.h"
#include "src/trace_processor/db/numeric_storage.h"
namespace perfetto {
namespace trace_processor {
namespace column {
namespace {
// Templated part of FastPathComparison.
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;
}
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));
}
// Templated part of SlowPathComparison.
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;
}
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));
}
},
GetFilterOpVariant<T>(op));
}
} // namespace
void NumericStorage::StableSort(uint32_t* rows, uint32_t rows_size) const {
NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
std::visit(
[this, &rows, rows_size](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
std::stable_sort(rows, rows + rows_size,
[typed_start](uint32_t a_idx, uint32_t b_idx) {
T first_val = typed_start[a_idx];
T second_val = typed_start[b_idx];
return first_val < second_val;
});
},
val);
}
// Responsible for invoking templated version of FastPathComparison.
void NumericStorage::CompareFast(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::CompareSlow(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) const {
return std::visit(
[this](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto upper =
std::upper_bound(typed_start, typed_start + size_, 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) const {
return std::visit(
[this](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto lower =
std::lower_bound(typed_start, typed_start + size_, val_data);
return static_cast<uint32_t>(std::distance(typed_start, lower));
},
val);
}
void NumericStorage::CompareSorted(FilterOp op,
SqlValue sql_val,
RowMap& rm) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
if (!val.has_value() || op == FilterOp::kIsNotNull ||
op == FilterOp::kIsNull || op == FilterOp::kGlob) {
rm.Clear();
return;
}
switch (op) {
case FilterOp::kEq: {
uint32_t beg = LowerBoundIndex(*val);
uint32_t end = UpperBoundIndex(*val);
RowMap sec(beg, end);
rm.Intersect(sec);
return;
}
case FilterOp::kLe: {
uint32_t end = UpperBoundIndex(*val);
RowMap sec(0, end);
rm.Intersect(sec);
return;
}
case FilterOp::kLt: {
uint32_t end = LowerBoundIndex(*val);
RowMap sec(0, end);
rm.Intersect(sec);
return;
}
case FilterOp::kGe: {
uint32_t beg = LowerBoundIndex(*val);
RowMap sec(beg, size_);
rm.Intersect(sec);
return;
}
case FilterOp::kGt: {
uint32_t beg = UpperBoundIndex(*val);
RowMap sec(beg, size_);
rm.Intersect(sec);
return;
}
case FilterOp::kNe:
case FilterOp::kIsNull:
case FilterOp::kIsNotNull:
case FilterOp::kGlob:
rm.Clear();
}
return;
}
uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
uint32_t* order) const {
return std::visit(
[this, order](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto upper = std::upper_bound(order, order + size_, 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) const {
return std::visit(
[this, order](auto val_data) {
using T = decltype(val_data);
const T* typed_start = static_cast<const T*>(data_);
auto lower = std::lower_bound(order, order + size_, val_data,
[typed_start](uint32_t index, T val) {
return *(typed_start + index) < val;
});
return static_cast<uint32_t>(std::distance(order, lower));
},
val);
}
void NumericStorage::CompareSortedIndexes(FilterOp op,
SqlValue sql_val,
uint32_t* order,
RowMap& rm) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
if (!val.has_value() || op == FilterOp::kIsNotNull ||
op == FilterOp::kIsNull || op == FilterOp::kGlob) {
rm.Clear();
return;
}
switch (op) {
case FilterOp::kEq: {
uint32_t beg = LowerBoundIndex(*val, order);
uint32_t end = UpperBoundIndex(*val, order);
std::vector<uint32_t> index(order + beg, order + end);
rm.Intersect(RowMap(std::move(index)));
return;
}
case FilterOp::kLe: {
uint32_t end = UpperBoundIndex(*val, order);
std::vector<uint32_t> index(order, order + end);
rm.Intersect(RowMap(std::move(index)));
return;
}
case FilterOp::kLt: {
uint32_t end = LowerBoundIndex(*val, order);
std::vector<uint32_t> index(order, order + end);
rm.Intersect(RowMap(std::move(index)));
return;
}
case FilterOp::kGe: {
uint32_t beg = LowerBoundIndex(*val, order);
std::vector<uint32_t> index(order + beg, order + size_);
rm.Intersect(RowMap(std::move(index)));
return;
}
case FilterOp::kGt: {
uint32_t beg = UpperBoundIndex(*val, order);
std::vector<uint32_t> index(order + beg, order + size_);
rm.Intersect(RowMap(std::move(index)));
return;
}
case FilterOp::kNe:
case FilterOp::kIsNull:
case FilterOp::kIsNotNull:
case FilterOp::kGlob:
rm.Clear();
}
return;
}
} // namespace column
} // namespace trace_processor
} // namespace perfetto