diff --git a/src/Functions/FunctionStartsEndsWith.h b/src/Functions/FunctionStartsEndsWith.h index b0465ecefa6..76aa4530c99 100644 --- a/src/Functions/FunctionStartsEndsWith.h +++ b/src/Functions/FunctionStartsEndsWith.h @@ -143,26 +143,39 @@ private: ) // DECLARE_MULTITARGET_CODE template -class FunctionStartsEndsWith - : public FunctionPerformanceAdaptor> +class FunctionStartsEndsWith : public TargetSpecific::Default::FunctionStartsEndsWith { public: - FunctionStartsEndsWith(const Context & context_) - : FunctionPerformanceAdaptor>(context_) + FunctionStartsEndsWith(const Context & context) : selector(context) { + selector.registerImplementation>(); + if constexpr (UseMultitargetCode) { - this->template registerImplementation> (TargetArch::SSE42); - this->template registerImplementation> (TargetArch::AVX); - this->template registerImplementation> (TargetArch::AVX2); - this->template registerImplementation>(TargetArch::AVX512F); + selector.registerImplementation>(); + selector.registerImplementation>(); + selector.registerImplementation>(); + selector.registerImplementation>(); } } + void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override + { + selector.selectAndExecute(block, arguments, result, input_rows_count); + } + static FunctionPtr create(const Context & context) { return std::make_shared>(context); } + +private: + ImplementationSelector selector; }; } diff --git a/src/Functions/FunctionsRandom.h b/src/Functions/FunctionsRandom.h index eeba5546fc9..995f8ffeb9c 100644 --- a/src/Functions/FunctionsRandom.h +++ b/src/Functions/FunctionsRandom.h @@ -99,28 +99,44 @@ public: }; template -class FunctionRandom : public FunctionPerformanceAdaptor> +class FunctionRandom : public FunctionRandomImpl { public: - FunctionRandom(const Context & context_) - : FunctionPerformanceAdaptor>(context_) + FunctionRandom(const Context & context) : selector(context) { + selector.registerImplementation>(); + selector.registerImplementation>(); + if constexpr (UseMultitargetCode) { - this->template registerImplementation>(TargetArch::SSE42); - this->template registerImplementation>(TargetArch::AVX); - this->template registerImplementation>(TargetArch::AVX2); - this->template registerImplementation>(TargetArch::AVX512F); + selector.registerImplementation>(); + selector.registerImplementation>(); + selector.registerImplementation>(); + selector.registerImplementation>(); - this->template registerImplementation>(TargetArch::Default); - this->template registerImplementation>(TargetArch::AVX2); + selector.registerImplementation>(); } } + void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override + { + selector.selectAndExecute(block, arguments, result, input_rows_count); + } + static FunctionPtr create(const Context & context) { return std::make_shared>(context); } + +private: + ImplementationSelector selector; }; } diff --git a/src/Functions/PerformanceAdaptors.h b/src/Functions/PerformanceAdaptors.h index 0b5e3e10104..b86730952fb 100644 --- a/src/Functions/PerformanceAdaptors.h +++ b/src/Functions/PerformanceAdaptors.h @@ -117,123 +117,67 @@ struct PerformanceStatistics PerformanceStatistics(ssize_t choose_method_) : choose_method(choose_method_) {} }; -struct PerformanceAdaptorOptions -{ - std::optional> implementations; -}; - -/// Redirects IExecutableFunctionImpl::execute() and IFunction:executeImpl() to executeFunctionImpl(); -template -class FunctionExecutor; - -template -class FunctionExecutor>> - : public DefaultFunction +/* Class which is used to store implementations for the function and selecting the best one to run + * based on processor architecture and statistics from previous runs. + * + * FunctionInterface is typically IFunction or IExecutableFunctionImpl, but practically it can be + * any interface that contains "execute" method (IFunction is an exception and is supported as well). + * + * Example of usage: + * + * class MyDefaulImpl : public IFunction {...}; + * class MySecondImpl : public IFunction {...}; + * class MyAVX2Impl : public IFunction {...}; + * + * /// All methods but execute/executeImpl are usually not bottleneck, so just use them from + * /// default implementation. + * class MyFunction : public MyDefaultImpl + * { + * MyFunction(const Context & context) : selector(context) { + * /// Register all implementations in constructor. + * /// There could be as many implementation for every target as you want. + * selector.registerImplementation(); + * selector.registerImplementation(); + * selector.registreImplementation(); + * } + * + * void executeImpl(...) override { + * selector.selectAndExecute(...); + * } + * + * static FunctionPtr create(const Context & context) { + * return std::make_shared(context); + * } + * private: + * ImplementationSelector selector; + * }; + */ +template +class ImplementationSelector { public: - using BaseFunctionPtr = ExecutableFunctionImplPtr; + using ImplementationPtr = std::shared_ptr; - template - FunctionExecutor(Args&&... args) : DefaultFunction(std::forward(args)...) {} + ImplementationSelector(const Context & context_) : context(context_) {} - virtual void executeFunctionImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) = 0; - - virtual void execute(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override + /* Select the best implementation based on previous runs. + * If FunctionInterface is IFunction, then "executeImpl" method of the implementation will be called + * and "execute" otherwise. + */ + void selectAndExecute(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) { - executeFunctionImpl(block, arguments, result, input_rows_count); - } -}; - -template -class FunctionExecutor>> - : public DefaultFunction -{ -public: - using BaseFunctionPtr = FunctionPtr; - - template - FunctionExecutor(Args&&... args) : DefaultFunction(std::forward(args)...) {} - - virtual void executeFunctionImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) = 0; - - virtual void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override - { - executeFunctionImpl(block, arguments, result, input_rows_count); - } -}; - -/// Combine several IExecutableFunctionImpl into one. -/// All the implementations should be equivalent. -/// Implementation to execute will be selected based on performance on previous runs. -/// DefaultFunction should be executable on every supported platform, while alternative implementations -/// could use extended set of instructions (AVX, NEON, etc). -/// It's convenient to inherit your func from this and register all alternative implementations in the constructor. -template -class FunctionPerformanceAdaptor : public FunctionExecutor -{ -public: - using BaseFunctionPtr = typename FunctionExecutor::BaseFunctionPtr; - - template - FunctionPerformanceAdaptor(const Context & context_, Params&&... params) - : FunctionExecutor(std::forward(params)...) - , context(context_) - { - if (isImplementationEnabled(DefaultFunction::getImplementationTag())) - statistics.emplace_back(); - } - - /// Register alternative implementation. - template - void registerImplementation(TargetArch arch, Params&&... params) - { - if (IsArchSupported(arch) && isImplementationEnabled(Function::getImplementationTag())) - { - impls.emplace_back(std::make_shared(std::forward(params)...)); - statistics.emplace_back(); - } - } - - bool isImplementationEnabled(const String & impl_tag) - { - const String & tag = context.getSettingsRef().function_implementation.value; - return tag.empty() || tag == impl_tag; - // if (!options.implementations) - // return true; - - // for (const auto & tag : *options.implementations) - // { - // if (tag == impl_tag) - // return true; - // } - // return false; - } - -protected: - virtual void executeFunctionImpl(Block & block, const ColumnNumbers & arguments, - size_t result, size_t input_rows_count) override - { - if (statistics.empty()) - throw Exception("All available implementations are disabled by user config", + if (implementations.empty()) + throw Exception("There are no available implementations for function " "TODO(dakovalkov): add name", ErrorCodes::NO_SUITABLE_FUNCTION_IMPLEMENTATION); auto id = statistics.select(); Stopwatch watch; - if (id == impls.size()) - { - if constexpr (std::is_base_of_v) - DefaultFunction::executeImpl(block, arguments, result, input_rows_count); - else - DefaultFunction::execute(block, arguments, result, input_rows_count); - } + if constexpr (std::is_same_v) + implementations[id]->executeImpl(block, arguments, result, input_rows_count); else - { - if constexpr (std::is_base_of_v) - impls[id]->executeImpl(block, arguments, result, input_rows_count); - else - impls[id]->execute(block, arguments, result, input_rows_count); - } + implementations[id]->execute(block, arguments, result, input_rows_count); + watch.stop(); // TODO(dakovalkov): Calculate something more informative. @@ -249,10 +193,29 @@ protected: } } + /* Register new implementation for function. + * + * Arch - required instruction set for running the implementation. It's guarantied that no one method would + * be called (even the constructor and static methods) if the processor doesn't support this instruction set. + * + * FunctionImpl - implementation, should be inherited from template argument FunctionInterface. + * + * All function arguments will be forwarded to the implementation constructor. + */ + template + void registerImplementation(Args&&... args) + { + if (IsArchSupported(Arch)) + { + implementations.emplace_back(std::make_shared(std::forward(args)...)); + statistics.emplace_back(); + } + } + private: - std::vector impls; // Alternative implementations. - PerformanceStatistics statistics; const Context & context; + std::vector implementations; + PerformanceStatistics statistics; }; } diff --git a/src/Functions/RandXorshift.h b/src/Functions/RandXorshift.h index 8713d85fdbd..49655d637f2 100644 --- a/src/Functions/RandXorshift.h +++ b/src/Functions/RandXorshift.h @@ -29,24 +29,35 @@ struct RandXorshiftImpl2 ) // DECLARE_MULTITARGET_CODE template -class FunctionRandomXorshift - : public FunctionPerformanceAdaptor> +class FunctionRandomXorshift : public FunctionRandomImpl { public: - FunctionRandomXorshift(const Context & context_) - : FunctionPerformanceAdaptor>(context_) + FunctionRandomXorshift(const Context & context) : selector(context) { + selector.registerImplementation>(); + if constexpr (UseMultitargetCode) { - this->template registerImplementation>(TargetArch::AVX2); - this->template registerImplementation>(TargetArch::AVX2); + selector.registerImplementation>(); + selector.registerImplementation>(); } } + void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override + { + selector.selectAndExecute(block, arguments, result, input_rows_count); + } + static FunctionPtr create(const Context & context) { return std::make_shared>(context); } + +private: + ImplementationSelector selector; }; }