velox/expression/benchmarks/CallNullFreeBenchmark.cpp (134 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 <folly/Benchmark.h> #include <folly/init/Init.h> #include "velox/functions/Macros.h" #include "velox/functions/Registerer.h" #include "velox/functions/lib/benchmarks/FunctionBenchmarkBase.h" #include "velox/functions/prestosql/registration/RegistrationFunctions.h" // These macros should be set, defined in command line. // #define WITH_NULL_ARRAYS false // #define WITH_NULL_VALUES false // Benchmark a function that returns the minimum value in an array. // The function returns NULL if the array or any of its elements are NULL. // Results: // We expect that simpleMinIntegerDefaultContainsNullsBehavior will perform // better than simpleMinInteger when there are no nulls present because we can // do a single check for nulls at the batch level, instead of checking on every // row. In cases where null arrays or null elements are present we expect // simpleMinIntegerDefaultHasNullsBehavior to do worse than simpleMinInteger // due to the distance between where we decode the index to check for nulls and // where we decode the index to access the value, which prevents the compiler // from optimizing this into a single lookup. // We expect simpleMinIntegerNullFreeFastPath to perform better than // simpleMinInteger when there are no nulls present for the same reason. We // expect simpleMinIntegerNullFreeFastPath to do about as well as // simpleMinInteger when null arrays or null elements are present because they // use the same code path after a quick additional check once per batch. namespace facebook::velox::functions { template <typename T> struct ArrayMinSimpleFunction { VELOX_DEFINE_FUNCTION_TYPES(T); template <typename TInput> FOLLY_ALWAYS_INLINE bool call( TInput& out, const arg_type<Array<TInput>>& array) { if (array.size() == 0) { return false; // NULL } auto min = INT32_MAX; if (array.mayHaveNulls()) { for (auto i = 0; i < array.size(); i++) { if (!array[i].has_value()) { return false; // NULL } auto v = array[i].value(); if (v < min) { min = v; } } } else { for (auto i = 0; i < array.size(); i++) { auto v = array[i].value(); if (v < min) { min = v; } } } out = min; return true; } }; template <typename T> struct ArrayMinDefaultContainsNullsBehaviorFunction { VELOX_DEFINE_FUNCTION_TYPES(T); template <typename TInput> FOLLY_ALWAYS_INLINE bool callNullFree( TInput& out, const null_free_arg_type<Array<TInput>>& array) { if (array.size() == 0) { return false; // NULL } auto min = INT32_MAX; for (auto i = 0; i < array.size(); i++) { auto v = array[i]; if (v < min) { min = v; } } out = min; return true; } }; template <typename T> struct ArrayMinNullFreeFastPathFunction { VELOX_DEFINE_FUNCTION_TYPES(T); template <typename TInput> FOLLY_ALWAYS_INLINE bool call( TInput& out, const arg_type<Array<TInput>>& array) { if (array.size() == 0) { return false; // NULL } auto min = INT32_MAX; if (array.mayHaveNulls()) { for (auto i = 0; i < array.size(); i++) { if (!array[i].has_value()) { return false; // NULL } auto v = array[i].value(); if (v < min) { min = v; } } } else { for (auto i = 0; i < array.size(); i++) { auto v = array[i].value(); if (v < min) { min = v; } } } out = min; return true; } template <typename TInput> FOLLY_ALWAYS_INLINE bool callNullFree( TInput& out, const null_free_arg_type<Array<TInput>>& array) { if (array.size() == 0) { return false; // NULL } auto min = INT32_MAX; for (auto i = 0; i < array.size(); i++) { auto v = array[i]; if (v < min) { min = v; } } out = min; return true; } }; void registerVectorFunctionFastPath() { VELOX_REGISTER_VECTOR_FUNCTION(udf_array_min, "vector_fast_path"); } void registerSimpleFunctions() { registerFunction<ArrayMinSimpleFunction, int32_t, Array<int32_t>>( {"array_min_simple"}); registerFunction< ArrayMinDefaultContainsNullsBehaviorFunction, int32_t, Array<int32_t>>({"array_min_default_contains_nulls_behavior"}); registerFunction<ArrayMinNullFreeFastPathFunction, int32_t, Array<int32_t>>( {"array_min_null_free_fast_path"}); } namespace { class CallNullFreeBenchmark : public functions::test::FunctionBenchmarkBase { public: CallNullFreeBenchmark() : FunctionBenchmarkBase() { registerVectorFunctionFastPath(); registerSimpleFunctions(); } RowVectorPtr makeData() { std::function<bool(vector_size_t /*row */)> noNulls = nullptr; const vector_size_t size = 1'000; auto arrayVector = vectorMaker_.arrayVector<int32_t>( size, [](auto row) { return row % 5; }, [](auto idx) { return idx % 23; }, WITH_NULL_ARRAYS ? [](auto row) { return row % 13 == 0; } : noNulls, WITH_NULL_ELEMENTS ? [](auto idx) { return idx % 19 == 0; } : noNulls); return vectorMaker_.rowVector({arrayVector}); } size_t runInteger(const std::string& functionName) { folly::BenchmarkSuspender suspender; auto arrayVector = makeData()->childAt(0); auto rowVector = vectorMaker_.rowVector({arrayVector}); auto exprSet = compileExpression( fmt::format("{}(c0)", functionName), rowVector->type()); suspender.dismiss(); return doRun(exprSet, rowVector); } size_t doRun(exec::ExprSet& exprSet, const RowVectorPtr& rowVector) { int cnt = 0; for (auto i = 0; i < 100; i++) { cnt += evaluate(exprSet, rowVector)->size(); } return cnt; } bool hasSameResults( exec::ExprSet& expr1, exec::ExprSet& expr2, const RowVectorPtr& input) { auto result1 = evaluate(expr1, input); auto result2 = evaluate(expr2, input); if (result1->size() != result2->size()) { return false; } for (auto i = 0; i < result1->size(); i++) { if (!result1->equalValueAt(result2.get(), i, i)) { return false; } } return true; } void test() { auto input = makeData(); auto exprSetRef = compileExpression("vector_fast_path(c0)", input->type()); std::vector<std::string> functions = { "array_min_simple", "array_min_default_contains_nulls_behavior", "array_min_null_free_fast_path", }; for (const auto& name : functions) { auto other = compileExpression(fmt::format("{}(c0)", name), input->type()); if (!hasSameResults(exprSetRef, other, input)) { VELOX_UNREACHABLE(fmt::format("testing failed at function {}", name)); } } } }; BENCHMARK_MULTI(vectorFastPath) { CallNullFreeBenchmark benchmark; return benchmark.runInteger("vector_fast_path"); } BENCHMARK_MULTI(simpleMinInteger) { CallNullFreeBenchmark benchmark; return benchmark.runInteger("array_min_simple"); } BENCHMARK_MULTI(simpleMinIntegerDefaultContainsNullsBehavior) { CallNullFreeBenchmark benchmark; return benchmark.runInteger("array_min_default_contains_nulls_behavior"); } BENCHMARK_MULTI(simpleMinIntegerNullFreeFastPath) { CallNullFreeBenchmark benchmark; return benchmark.runInteger("array_min_null_free_fast_path"); } } // namespace } // namespace facebook::velox::functions int main(int /*argc*/, char** /*argv*/) { facebook::velox::functions::CallNullFreeBenchmark benchmark; benchmark.test(); folly::runBenchmarks(); return 0; }