如何检测关键函数中调用的后台函数

时间:2013-05-22 10:28:01

标签: c++

我正在研究非常大的c ++项目,它具有许多实时关键功能以及许多慢速后台功能。不应从时间关键函数中调用这些后台函数。那么有没有办法检测从关键函数调用的这些后台函数?编译时间会很好,但无论如何我想在这些后台函数之前检测。 更多信息,慢速和关键功能都是同一类的一部分,并共享相同的标题。

更多信息,关键函数在非常快的线程(> 10KHz)下运行,在较慢的线程(< = 1KHz)下运行较慢。使用慢函数中的关键部分来保护类成员变量,因为它们都使用相同的类成员变量。这就是在关键功能中调用缓慢功能的原因会降低整体系统性能。这就是为什么我喜欢自动找到所有这些功能而不是手动检查。

...谢谢

4 个答案:

答案 0 :(得分:4)

您需要利用链接器。将“实时”和慢速函数分成两个模块,并按正确顺序链接它们。

例如,将文件拆分为两个目录。从每个目录创建一个lib(将目标文件一起运行),然后使用以下命令链接最终的应用程序:

c++ -o myapp main.o lib1/slowfns.a lib2/realtime.a

如果你试图在realtime.a中从slowfns.a调用任何东西,取决于编译器,它将无法链接(某些编译器可能需要选项来强制执行此操作)。

此外,这使您可以轻松管理编译时声明:在编译“实时”函数库以确保添加保护时,确保来自slowfns库的标头不在包含路径上。

答案 1 :(得分:3)

在编译时不知道如何做到这一点,但对于运行时,可能使用互斥?

static Mutex critical_mutex;
#define CALL_SLOW( f ) if( critical_mutex.try_lock() == FAIL) \
   printf("SLOW FUNCTION " #f" called while in CRITICAL\n");\
   f

#define ENTER_CRITICAL() critical_mutex.lock()
#define EXIT_CRITICAL() critical_mutex.unlock()

每当你在关键部分使用慢速功能时,trylock都会失败。

void slow_func(){
}

ENTER_CRITICAL();
CALL_SLOW( slow_func() );
EXIT_CRITICAL();

将打印:

SLOW FUNCTION slow_func() called while in CRITICAL

如果您需要速度,可以在Windows上使用interlockedincrement或在Linux上使用__sync*函数实现轻量级互斥锁。
Preshing有一系列关于这个HERE的博客文章。

答案 2 :(得分:3)

获得Nicholas Wilson提出的编译时检测将是非常困难的,如果不是不可能的话,但假设“背景”真的是指函数,而不是多线程(我没有看到线程中的线程)问题,所以我认为这只是一个奇怪的措辞)你可以平凡地使用全局标志和一个更衣室对象,并assert或抛出异常。或者,输出调试消息。当然,这将仅限运行时 - 但您应该能够非常快速地隔离违规者。调试版本的开销也很低(几乎可以保证从L1缓存运行),而版本构建也没有。

使用CaptureStackBackTrace,应该能够捕获有问题的函数的地址,addr2line之类的工具(或等效的MS)可以直接转换为代码中的一行。甚至可能有一个toolhelp函数可以直接进行这种翻译(虽然我不知道)。

所以,这样的事情(未经测试!)可能会成功:

namespace global { int slow_flag = 0; }
struct slow_func_locker
{
    slow_func_locker() { ++global::slow_flag; }
    ~slow_func_locker(){ --global::slow_flag; }
};
#indef NDEBUG
  #define REALTIME  if(global::slow_flag) \
  { \
    void* backtrace; \
    CaptureStackBackTrace(0, 1, &backtrace, 0); \
    printf("RT function %s called from %08x\n", __FUNCTION__, backtrace); \
  }
  #define SLOW_FUNC slow_func_locker slow_func_locker_;
#else
  #define REALTIME
  #define SLOW_FUNC
#endif

foo_class::some_realtime_function(...)
{
    REALTIME;
    //...
};

foo_class::some_slow_function(...)
{
    SLOW_FUNC;
    //...
    some_realtime_function(blah); // this will trigger
};

唯一真正的缺点(除了不是编译时)你必须使用任一标记来标记每个慢速和实时功能,但由于编译器无法神奇地知道哪个是什么,所以无论如何都没有多少选择

请注意,全局“标志”实际上是计数器,而不是标志。原因是慢速函数可以立即调用另一个返回并清除标志的慢速函数 - 错误地假设现在有一个快速函数(在这种情况下,xgbi建议的关键部分的方法可能会死锁!)。计数器可以防止这种情况发生。在存在线程的情况下,也可以用int替换std::atomic_int

修改
现在很清楚,确实有 2个线程正在运行,并且只关注其中一个(“快速”线程)不会调用“慢”函数,还有一个简单的,工作解决方案(例如使用Win32 API,但可以用POSIX完成):

当“快速”线程启动时(“慢”线程不需要这样做),将线程ID存储在某处,作为全局变量,或作为包含所有快/慢函数的对象的成员 - 任何可以访问的地方:

global::fast_thread_id = GetCurrentThreadId();

拯救“不受欢迎”的函数调用的宏可能看起来像:

#define CHECK_FAST_THREAD assert(GetCurrentThreadID() != global::fast_thread_id)

然后将此宏添加到任何“慢”函数中,该函数永远不应从“快速”线程调用。如果快速线程调用一个它不能调用的函数,则断言触发,并且知道调用哪个函数。

答案 3 :(得分:1)

如果您可以随意修改代码,那么可以使用类型系统级解决方案来添加一些样板。

基本上,您创建了一个新类SlowFunctionToken。程序中的每个慢速函数都会引用SlowFunctionToken。接下来,将SlowFunctionToken的默认和复制构造函数设为私有。

现在只有已经有SlowFunctionToken的函数才能调用慢函数。你如何获得SlowFunctionToken?将朋友声明添加到SlowFunctionToken;具体来说,朋友允许使用慢速函数的线程的线程入口函数。然后,在那里创建本地SlowFunctionToken对象并将其传递下去。

class SlowFunctionToken;

class Stuff {
public:
  void FastThread();
  void SlowThread();

  void ASlowFunction(SlowFunctionToken& sft);
  void AnotherSlowFunction(SlowFunctionToken& sft);

  void AFastFunction();
};

class SlowFunctionToken {
  SlowFunctionToken() {}
  SlowFunctionToken(const SlowFunctionToken&) {}

  friend void Stuff::SlowThread();
};

void Stuff::FastThread() {
  AFastFunction();
  //SlowFunctionToken sft; doesn't compile
  //ASlowFunction(???); doesn't compile
}

void Stuff::SlowThread() {
  SlowFunctionToken sft;
  ASlowFunction(sft);
}

void Stuff::ASlowFunction(SlowFunctionToken& sft) {
  AnotherSlowFunction(sft);
  AFastFunction(); // works, but that function can't call slow functions
}