7

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

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

谢谢....

4

4 回答 4

4

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

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

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

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

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

于 2013-05-22T10:49:48.263 回答
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 有一组很棒的关于这里的博客文章。

于 2013-05-22T14:50:28.847 回答
3

如果不是不可能的话,获得除 Nicholas Wilson 提出的之外的编译时检测将非常困难,但假设“背景”确实指的是函数,而不是多线程(我在问题中没有提到线程,所以我认为这只是一个奇怪的措辞)您可以轻松地使用全局标志和储物柜对象,assert或者抛出异常。或者,输出调试消息。当然,这将是仅运行时的——但您应该能够非常快速地隔离违规者。调试构建的开销也非常低(几乎可以保证从 L1 缓存运行),而对于发布构建则没有。

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

所以,像这样的东西(未经测试!)可能会奏效:

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 建议的关键部分方法可能会死锁!)。计数器可以防止这种情况发生。在存在线程的情况下,也可以替换intstd::atomic_int

编辑:
现在很清楚确实有2 个线程在运行,并且其中一个(“快速”线程)从未调用“慢”函数很重要,还有另一个简单的工作解决方案(例如使用Win32 API,但无论哪种方式都可以使用 POSIX 完成):

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

global::fast_thread_id = GetCurrentThreadId();

用于解决“不受欢迎”函数调用的宏可能如下所示:

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

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

于 2013-05-22T15:52:43.550 回答
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
}
于 2013-05-23T11:52:43.683 回答