我正在研究非常大的c ++项目,它具有许多实时关键功能以及许多慢速后台功能。不应从时间关键函数中调用这些后台函数。那么有没有办法检测从关键函数调用的这些后台函数?编译时间会很好,但无论如何我想在这些后台函数之前检测。 更多信息,慢速和关键功能都是同一类的一部分,并共享相同的标题。
更多信息,关键函数在非常快的线程(> 10KHz)下运行,在较慢的线程(< = 1KHz)下运行较慢。使用慢函数中的关键部分来保护类成员变量,因为它们都使用相同的类成员变量。这就是在关键功能中调用缓慢功能的原因会降低整体系统性能。这就是为什么我喜欢自动找到所有这些功能而不是手动检查。
...谢谢
答案 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
}