如果需要 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:IntrospectionDataLoggerNullLogger,其 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。另一种选择是让用户选择模板实例化,但这可能不太干净,并且缺点是您必须发布代码。