Delete awful template PerformanceAdaptro and add simple ImplementationSelector instead

This commit is contained in:
Dmitrii Kovalkov 2020-05-18 22:07:24 +02:00
parent ea1285328b
commit c524642d24
4 changed files with 134 additions and 131 deletions

View File

@ -143,26 +143,39 @@ private:
) // DECLARE_MULTITARGET_CODE ) // DECLARE_MULTITARGET_CODE
template <typename Name> template <typename Name>
class FunctionStartsEndsWith class FunctionStartsEndsWith : public TargetSpecific::Default::FunctionStartsEndsWith<Name>
: public FunctionPerformanceAdaptor<TargetSpecific::Default::FunctionStartsEndsWith<Name>>
{ {
public: public:
FunctionStartsEndsWith(const Context & context_) FunctionStartsEndsWith(const Context & context) : selector(context)
: FunctionPerformanceAdaptor<TargetSpecific::Default::FunctionStartsEndsWith<Name>>(context_)
{ {
selector.registerImplementation<TargetArch::Default,
TargetSpecific::Default::FunctionStartsEndsWith<Name>>();
if constexpr (UseMultitargetCode) if constexpr (UseMultitargetCode)
{ {
this->template registerImplementation<TargetSpecific::SSE42::FunctionStartsEndsWith<Name>> (TargetArch::SSE42); selector.registerImplementation<TargetArch::SSE42,
this->template registerImplementation<TargetSpecific::AVX::FunctionStartsEndsWith<Name>> (TargetArch::AVX); TargetSpecific::SSE42::FunctionStartsEndsWith<Name>>();
this->template registerImplementation<TargetSpecific::AVX2::FunctionStartsEndsWith<Name>> (TargetArch::AVX2); selector.registerImplementation<TargetArch::AVX,
this->template registerImplementation<TargetSpecific::AVX512F::FunctionStartsEndsWith<Name>>(TargetArch::AVX512F); TargetSpecific::AVX::FunctionStartsEndsWith<Name>>();
selector.registerImplementation<TargetArch::AVX2,
TargetSpecific::AVX2::FunctionStartsEndsWith<Name>>();
selector.registerImplementation<TargetArch::AVX512F,
TargetSpecific::AVX512F::FunctionStartsEndsWith<Name>>();
} }
} }
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) static FunctionPtr create(const Context & context)
{ {
return std::make_shared<FunctionStartsEndsWith<Name>>(context); return std::make_shared<FunctionStartsEndsWith<Name>>(context);
} }
private:
ImplementationSelector<IFunction> selector;
}; };
} }

View File

@ -99,28 +99,44 @@ public:
}; };
template <typename ToType, typename Name> template <typename ToType, typename Name>
class FunctionRandom : public FunctionPerformanceAdaptor<FunctionRandomImpl<TargetSpecific::Default::RandImpl, ToType, Name>> class FunctionRandom : public FunctionRandomImpl<TargetSpecific::Default::RandImpl, ToType, Name>
{ {
public: public:
FunctionRandom(const Context & context_) FunctionRandom(const Context & context) : selector(context)
: FunctionPerformanceAdaptor<FunctionRandomImpl<TargetSpecific::Default::RandImpl, ToType, Name>>(context_)
{ {
selector.registerImplementation<TargetArch::Default,
FunctionRandomImpl<TargetSpecific::Default::RandImpl, ToType, Name>>();
selector.registerImplementation<TargetArch::Default,
FunctionRandomImpl<TargetSpecific::Default::RandImpl2, ToType, Name>>();
if constexpr (UseMultitargetCode) if constexpr (UseMultitargetCode)
{ {
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::SSE42::RandImpl, ToType, Name>>(TargetArch::SSE42); selector.registerImplementation<TargetArch::SSE42,
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX::RandImpl, ToType, Name>>(TargetArch::AVX); FunctionRandomImpl<TargetSpecific::SSE42::RandImpl, ToType, Name>>();
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX2::RandImpl, ToType, Name>>(TargetArch::AVX2); selector.registerImplementation<TargetArch::AVX,
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX512F::RandImpl, ToType, Name>>(TargetArch::AVX512F); FunctionRandomImpl<TargetSpecific::AVX::RandImpl, ToType, Name>>();
selector.registerImplementation<TargetArch::AVX2,
FunctionRandomImpl<TargetSpecific::AVX2::RandImpl, ToType, Name>>();
selector.registerImplementation<TargetArch::AVX512F,
FunctionRandomImpl<TargetSpecific::AVX512F::RandImpl, ToType, Name>>();
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::Default::RandImpl2, ToType, Name>>(TargetArch::Default); selector.registerImplementation<TargetArch::AVX2,
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX2::RandImpl2, ToType, Name>>(TargetArch::AVX2); FunctionRandomImpl<TargetSpecific::AVX2::RandImpl2, ToType, Name>>();
} }
} }
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) static FunctionPtr create(const Context & context)
{ {
return std::make_shared<FunctionRandom<ToType, Name>>(context); return std::make_shared<FunctionRandom<ToType, Name>>(context);
} }
private:
ImplementationSelector<IFunction> selector;
}; };
} }

View File

@ -117,123 +117,67 @@ struct PerformanceStatistics
PerformanceStatistics(ssize_t choose_method_) : choose_method(choose_method_) {} PerformanceStatistics(ssize_t choose_method_) : choose_method(choose_method_) {}
}; };
struct PerformanceAdaptorOptions /* 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.
std::optional<std::vector<String>> implementations; *
}; * 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).
/// Redirects IExecutableFunctionImpl::execute() and IFunction:executeImpl() to executeFunctionImpl(); *
template <typename DefaultFunction, typename Dummy = void> * Example of usage:
class FunctionExecutor; *
* class MyDefaulImpl : public IFunction {...};
template <typename DefaultFunction> * class MySecondImpl : public IFunction {...};
class FunctionExecutor<DefaultFunction, std::enable_if_t<std::is_base_of_v<IExecutableFunctionImpl, DefaultFunction>>> * class MyAVX2Impl : public IFunction {...};
: public DefaultFunction *
* /// 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<TargetArch::Default, MyDefaultImpl>();
* selector.registerImplementation<TargetArch::Default, MySecondImpl>();
* selector.registreImplementation<TargetArch::AVX2, MyAVX2Impl>();
* }
*
* void executeImpl(...) override {
* selector.selectAndExecute(...);
* }
*
* static FunctionPtr create(const Context & context) {
* return std::make_shared<MyFunction>(context);
* }
* private:
* ImplementationSelector<IFunction> selector;
* };
*/
template <typename FunctionInterface>
class ImplementationSelector
{ {
public: public:
using BaseFunctionPtr = ExecutableFunctionImplPtr; using ImplementationPtr = std::shared_ptr<FunctionInterface>;
template <typename ...Args> ImplementationSelector(const Context & context_) : context(context_) {}
FunctionExecutor(Args&&... args) : DefaultFunction(std::forward<Args>(args)...) {}
virtual void executeFunctionImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) = 0; /* Select the best implementation based on previous runs.
* If FunctionInterface is IFunction, then "executeImpl" method of the implementation will be called
virtual void execute(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override * 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); if (implementations.empty())
} throw Exception("There are no available implementations for function " "TODO(dakovalkov): add name",
};
template <typename DefaultFunction>
class FunctionExecutor<DefaultFunction, std::enable_if_t<std::is_base_of_v<IFunction, DefaultFunction>>>
: public DefaultFunction
{
public:
using BaseFunctionPtr = FunctionPtr;
template <typename ...Args>
FunctionExecutor(Args&&... args) : DefaultFunction(std::forward<Args>(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 <typename DefaultFunction>
class FunctionPerformanceAdaptor : public FunctionExecutor<DefaultFunction>
{
public:
using BaseFunctionPtr = typename FunctionExecutor<DefaultFunction>::BaseFunctionPtr;
template <typename ...Params>
FunctionPerformanceAdaptor(const Context & context_, Params&&... params)
: FunctionExecutor<DefaultFunction>(std::forward<Params>(params)...)
, context(context_)
{
if (isImplementationEnabled(DefaultFunction::getImplementationTag()))
statistics.emplace_back();
}
/// Register alternative implementation.
template<typename Function, typename ...Params>
void registerImplementation(TargetArch arch, Params&&... params)
{
if (IsArchSupported(arch) && isImplementationEnabled(Function::getImplementationTag()))
{
impls.emplace_back(std::make_shared<Function>(std::forward<Params>(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",
ErrorCodes::NO_SUITABLE_FUNCTION_IMPLEMENTATION); ErrorCodes::NO_SUITABLE_FUNCTION_IMPLEMENTATION);
auto id = statistics.select(); auto id = statistics.select();
Stopwatch watch; Stopwatch watch;
if (id == impls.size()) if constexpr (std::is_same_v<FunctionInterface, IFunction>)
{ implementations[id]->executeImpl(block, arguments, result, input_rows_count);
if constexpr (std::is_base_of_v<IFunction, FunctionPerformanceAdaptor>)
DefaultFunction::executeImpl(block, arguments, result, input_rows_count);
else else
DefaultFunction::execute(block, arguments, result, input_rows_count); implementations[id]->execute(block, arguments, result, input_rows_count);
}
else
{
if constexpr (std::is_base_of_v<IFunction, FunctionPerformanceAdaptor>)
impls[id]->executeImpl(block, arguments, result, input_rows_count);
else
impls[id]->execute(block, arguments, result, input_rows_count);
}
watch.stop(); watch.stop();
// TODO(dakovalkov): Calculate something more informative. // 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 <TargetArch Arch, typename FunctionImpl, typename ...Args>
void registerImplementation(Args&&... args)
{
if (IsArchSupported(Arch))
{
implementations.emplace_back(std::make_shared<FunctionImpl>(std::forward<Args>(args)...));
statistics.emplace_back();
}
}
private: private:
std::vector<BaseFunctionPtr> impls; // Alternative implementations.
PerformanceStatistics statistics;
const Context & context; const Context & context;
std::vector<ImplementationPtr> implementations;
PerformanceStatistics statistics;
}; };
} }

View File

@ -29,24 +29,35 @@ struct RandXorshiftImpl2
) // DECLARE_MULTITARGET_CODE ) // DECLARE_MULTITARGET_CODE
template <typename ToType, typename Name> template <typename ToType, typename Name>
class FunctionRandomXorshift class FunctionRandomXorshift : public FunctionRandomImpl<TargetSpecific::Default::RandXorshiftImpl, ToType, Name>
: public FunctionPerformanceAdaptor<FunctionRandomImpl<TargetSpecific::Default::RandXorshiftImpl, ToType, Name>>
{ {
public: public:
FunctionRandomXorshift(const Context & context_) FunctionRandomXorshift(const Context & context) : selector(context)
: FunctionPerformanceAdaptor<FunctionRandomImpl<TargetSpecific::Default::RandXorshiftImpl, ToType, Name>>(context_)
{ {
selector.registerImplementation<TargetArch::Default,
FunctionRandomImpl<TargetSpecific::Default::RandXorshiftImpl, ToType, Name>>();
if constexpr (UseMultitargetCode) if constexpr (UseMultitargetCode)
{ {
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX2::RandXorshiftImpl, ToType, Name>>(TargetArch::AVX2); selector.registerImplementation<TargetArch::AVX2,
this->template registerImplementation<FunctionRandomImpl<TargetSpecific::AVX2::RandXorshiftImpl2, ToType, Name>>(TargetArch::AVX2); FunctionRandomImpl<TargetSpecific::AVX2::RandXorshiftImpl, ToType, Name>>();
selector.registerImplementation<TargetArch::AVX2,
FunctionRandomImpl<TargetSpecific::AVX2::RandXorshiftImpl2, ToType, Name>>();
} }
} }
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) static FunctionPtr create(const Context & context)
{ {
return std::make_shared<FunctionRandomXorshift<ToType, Name>>(context); return std::make_shared<FunctionRandomXorshift<ToType, Name>>(context);
} }
private:
ImplementationSelector<IFunction> selector;
}; };
} }