如果需要 none,则无需 运行 时间成本即可从图书馆发布信息
Emit information from a library without run-time cost if none is wanted
我是一个包含许多优化算法的库的作者,我已经在其中投入了相当多的 profiling/tuning。我目前正在为这个库编写前端程序。
目前,库例程本身几乎是黑盒子。考虑一种类似于 bool fit(vector_t const& data, double targetError)
的方法。他们工作并做我想让他们做的事,但对于前端来说,一些 运行 时间信息会很好。例如,如果可以显示 "current error" 或 "number of iterations left" 这样的信息就好了。我不想要一个简单的 if (verbose) cerr << "Info\n";
模式,因为该库在 GUI 环境中应该同样可用。
我特意写成could,因为我想尽可能降低这种信息传播的影响。我如何定义和实现一个接口,
- 每当观察者注册时发出一个带有 运行 时间信息的对象
如果发出信息对象,- 算法的运行时间成本最小,并且
- 如果没有观察员注册,几乎没有 运行 时间成本?
基本上,此可选自省的 运行 时间成本应尽可能低,如果不需要自省,则接近于零。如何实现?为此存在图书馆吗? (我想答案是肯定的,并且可能隐藏在 Boost 项目中,但不知道要寻找什么......)
只需将所有成本转移到编译时间!
最简单的方法是:
template<bool logging>
bool fit(vector_t const& data, double targetError)
{
// ... computations ...
if (logging) {
std::cout << "This is log entry\n";
}
// ... computations ...
}
用法:
fit<true>(data, epsilon); // Will print logs.
fit<false>(data, epsilon); // Will not print logs with zero overhead.
如今几乎所有编译器都会在编译时优化所有此类检查。
更灵活的方法是将记录器作为模板参数传递:
class ConsoleLogger
{
public:
template<typename... Args>
static void log(Args... args) {
// Print args using compile-time recursion.
// Anyway, this prototype is only an example.
// Your may define any logging interface you wish.
}
};
class FileLogger
{
// Some implementation of log() ...
};
class RemoteCloudLogger
{
// Some implementation of log() ...
};
class NullLogger
{
template<typename... Args>
static void log(Args... args) {
// Intentionally left blank. Any calls will be optimized out.
}
};
template<typename Logger>
bool fit(vector_t const& data, double targetError)
{
// ... computations ...
Logger::log("Current error: ", currentError);
Logger::log("Iterations passed: ", i);
// ... computations ...
}
用法:
fit<ConsoleLogger>(data, epsilon); // Will log to console.
fit<RemoteCloudLogger>(data, epsilon); // Will log to a file on remote cloud server.
fit<NullLogger>(data, epsilon); // Any calls to Logger::log will be optimized out, yielding zero overhead.
显然,您可以编写一个记录器 class,它将所有记录的信息收集到具有内省数据的结构中。
例如,您可以将 fit
的接口定义如下:
template<typename Logger>
bool fit(vector_t const& data, double targetError, IntrospectionData& data)
{...}
并仅定义两个日志记录 classes:IntrospectionDataLogger
和 NullLogger
,其 log
方法接受对 IntrospectionData
结构的引用。同样,最后一个 class 包含将被编译器丢弃的空方法。
您可以在堆栈上分配结构并将引用传递给观察者。然后观察者可以对该结构做任何必要的事情,例如复制它、打印它、在 GUI 中显示它等。或者,如果您只想跟踪一个或两个属性,您可能只想将它们作为单独的参数传递。
class Observer
{
...
public:
virtual void observe(MyInfoStruct *info) = 0;
}
...
if(hasObservers()) // ideally inline
{
MyInfoStruct info = {currentError, bla, bla};
for(observer : observers)
{
observer->observe(&info);
}
}
这样,当除了 if
语句之外没有观察者时,应该没有开销。发出信息的开销应该由对观察者的虚拟调用支配。
进一步优化:
您可能希望将可能的冷观察者迭代代码概括为一个单独的函数,以改进热路径的代码局部性。
如果你的代码中有任何频繁的虚拟调用,请考虑添加一个 "Observed" 版本的接口并尝试在那里执行观察工作,并从原始版本中完全省略它。如果被观察的版本可以很容易地在放置观察者时注入一次,您可以省略多余的观察者检查。如果您只想跟踪函数的参数,这很容易,只需将 "Observed" 版本设置为原始版本即可,但是如果您想在算法为 运行 时跟踪信息,这可能不切实际.
根据您要跟踪的内容,可以将其编写为模板:
struct NoObserverDispatch {
bool hasObservers() {return false;}
void observe(MyInfoStruct *) {}
};
struct GenericObserverDispatch {
bool hasObservers() {return !observers.isEmpty();}
void observe(MyInfoStruct *) { for (obs : observers) obs->observe(info); }
private:
vector<unique_ptr<Observer> > observers;
};
template<typename ObserverDispatch>
class Fitter
{
ObserverDispatch obs;
virtual bool fit(vector_t const& data, double targetError)
{
...
if(obs.hasObservers()) { // will be optimized away unless needed
MyInfoStruct info = {currentError, bla, bla};
obs.observe(&info);
}
}
};
显然,这假设您可以在放置观察者时替换 Fitter
。另一种选择是让用户选择模板实例化,但这可能不太干净,并且缺点是您必须发布代码。
我是一个包含许多优化算法的库的作者,我已经在其中投入了相当多的 profiling/tuning。我目前正在为这个库编写前端程序。
目前,库例程本身几乎是黑盒子。考虑一种类似于 bool fit(vector_t const& data, double targetError)
的方法。他们工作并做我想让他们做的事,但对于前端来说,一些 运行 时间信息会很好。例如,如果可以显示 "current error" 或 "number of iterations left" 这样的信息就好了。我不想要一个简单的 if (verbose) cerr << "Info\n";
模式,因为该库在 GUI 环境中应该同样可用。
我特意写成could,因为我想尽可能降低这种信息传播的影响。我如何定义和实现一个接口,
- 每当观察者注册时发出一个带有 运行 时间信息的对象 如果发出信息对象,
- 算法的运行时间成本最小,并且
- 如果没有观察员注册,几乎没有 运行 时间成本?
基本上,此可选自省的 运行 时间成本应尽可能低,如果不需要自省,则接近于零。如何实现?为此存在图书馆吗? (我想答案是肯定的,并且可能隐藏在 Boost 项目中,但不知道要寻找什么......)
只需将所有成本转移到编译时间!
最简单的方法是:
template<bool logging>
bool fit(vector_t const& data, double targetError)
{
// ... computations ...
if (logging) {
std::cout << "This is log entry\n";
}
// ... computations ...
}
用法:
fit<true>(data, epsilon); // Will print logs.
fit<false>(data, epsilon); // Will not print logs with zero overhead.
如今几乎所有编译器都会在编译时优化所有此类检查。
更灵活的方法是将记录器作为模板参数传递:
class ConsoleLogger
{
public:
template<typename... Args>
static void log(Args... args) {
// Print args using compile-time recursion.
// Anyway, this prototype is only an example.
// Your may define any logging interface you wish.
}
};
class FileLogger
{
// Some implementation of log() ...
};
class RemoteCloudLogger
{
// Some implementation of log() ...
};
class NullLogger
{
template<typename... Args>
static void log(Args... args) {
// Intentionally left blank. Any calls will be optimized out.
}
};
template<typename Logger>
bool fit(vector_t const& data, double targetError)
{
// ... computations ...
Logger::log("Current error: ", currentError);
Logger::log("Iterations passed: ", i);
// ... computations ...
}
用法:
fit<ConsoleLogger>(data, epsilon); // Will log to console.
fit<RemoteCloudLogger>(data, epsilon); // Will log to a file on remote cloud server.
fit<NullLogger>(data, epsilon); // Any calls to Logger::log will be optimized out, yielding zero overhead.
显然,您可以编写一个记录器 class,它将所有记录的信息收集到具有内省数据的结构中。
例如,您可以将 fit
的接口定义如下:
template<typename Logger>
bool fit(vector_t const& data, double targetError, IntrospectionData& data)
{...}
并仅定义两个日志记录 classes:IntrospectionDataLogger
和 NullLogger
,其 log
方法接受对 IntrospectionData
结构的引用。同样,最后一个 class 包含将被编译器丢弃的空方法。
您可以在堆栈上分配结构并将引用传递给观察者。然后观察者可以对该结构做任何必要的事情,例如复制它、打印它、在 GUI 中显示它等。或者,如果您只想跟踪一个或两个属性,您可能只想将它们作为单独的参数传递。
class Observer
{
...
public:
virtual void observe(MyInfoStruct *info) = 0;
}
...
if(hasObservers()) // ideally inline
{
MyInfoStruct info = {currentError, bla, bla};
for(observer : observers)
{
observer->observe(&info);
}
}
这样,当除了 if
语句之外没有观察者时,应该没有开销。发出信息的开销应该由对观察者的虚拟调用支配。
进一步优化: 您可能希望将可能的冷观察者迭代代码概括为一个单独的函数,以改进热路径的代码局部性。
如果你的代码中有任何频繁的虚拟调用,请考虑添加一个 "Observed" 版本的接口并尝试在那里执行观察工作,并从原始版本中完全省略它。如果被观察的版本可以很容易地在放置观察者时注入一次,您可以省略多余的观察者检查。如果您只想跟踪函数的参数,这很容易,只需将 "Observed" 版本设置为原始版本即可,但是如果您想在算法为 运行 时跟踪信息,这可能不切实际.
根据您要跟踪的内容,可以将其编写为模板:
struct NoObserverDispatch {
bool hasObservers() {return false;}
void observe(MyInfoStruct *) {}
};
struct GenericObserverDispatch {
bool hasObservers() {return !observers.isEmpty();}
void observe(MyInfoStruct *) { for (obs : observers) obs->observe(info); }
private:
vector<unique_ptr<Observer> > observers;
};
template<typename ObserverDispatch>
class Fitter
{
ObserverDispatch obs;
virtual bool fit(vector_t const& data, double targetError)
{
...
if(obs.hasObservers()) { // will be optimized away unless needed
MyInfoStruct info = {currentError, bla, bla};
obs.observe(&info);
}
}
};
显然,这假设您可以在放置观察者时替换 Fitter
。另一种选择是让用户选择模板实例化,但这可能不太干净,并且缺点是您必须发布代码。