这个Bug是在为公司一个项目编写idle延迟处理机制时发现的,在QT里面定时器类QTimer存在一个CPU高占用的问题,Bug的详细描述和解决方案已经在官方社区有提到:https://bugreports.qt-project.org/browse/QTBUG-7618。
社员Daniel Kristjansson给出一个临时解决该问题的补丁(qt4,qt5),当然他也提到了最佳的解决方案:But probably the correct approach is to use nanosleep() on POSIX 1999 or later systems and simply sleep a full millisecond on systems 或者 since select is being used to also monitor other things.. pselect() could be used instead to get nanosecond accuracy。
Daniel Kristjansson提供了示例程序来说明该Bug,我自己也写出QT和GTK+两个版本来对比,使用的CPU是Intel® Core™ i5-2400 CPU @ 3.10GHz × 4,使用的进程CPU信息查看工具是htop,操作系统用Ubuntu 12.04 LTS。
QT: 使用Version 4.8.1
#include <QtGui/QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTimer t;
t.start(1);
return app.exec();
}
GTK+:使用Version 2.0
#include<gtk/gtk.h>
#include<time.h>
void TimePorcFunction ()
{
//printf("ThisTimePorcFunction () \n");
return;
}
int main(int argc,char * argv[])
{
gtk_init(&argc,&argv);
gtk_timeout_add(1, (GtkFunction)TimePorcFunction, NULL);
gtk_main();
return 1;
}
对比:QTimer实现的计时器程序qt_timer在timeout间隔为1ms时,几乎100%占满CPU;而GTK+实现的gtk_timer的CPU占用率在2%-3%之间。
在Bug报告中,Daniel Kristjansson说到“QTimer spends up to the last millisecond of the timer interval busy-waiting, in its attempt to be very accurate”。即是说QTimer内部实现时,计时器最后1ms是使用busy-waiting的方式处理,目的也就是想提高精确度。因此这个Bug的临时解决方案就是在计时器计算最后1ms时,特意加上一个数,利用除法四舍五入(rounding up),使计时器提前结束(timeout),不留下最后1ms。代码示例:
*timeout = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
vs
*timeout = (tv.tv_sec * 1000) + ((tv.tv_usec + 999) / 1000);
虽然临时解决方案不完美,提前结束计时器通常意味着会提前触发某一事件,但是由于QTimer本身并不是高精度计时器,所以即使提前结束计时器的最后1ms也不会造成多大问题。
最后看下应用Daniel Kristjansson的补丁后,上文提到的QT示例程序表现如何:
可以明显地看到此时QTimer实现的计时器qt_timer占用率降低,在3%-4%之间。
总结:
1、之所以发现这个Bug是小组老大在跟我一起分析我写的代码时,用valgrind+callgrind以及htop这个小工具分析到程序空闲时仍有计时器消耗较高的CPU,这个是分析程序的好方法;
2、QT的Bug得先去其官方的bugreports.qt-project.org先找,像这个Bug就有Daniel Kristjansson给出这么好的解析和解决方案;
3、有时QT的实现可以用其他库做同样的实现来对比,利于比较和发现问题;
4、QT有社区维护,因此有问题首先到社区寻找帮助。
最后我将此Bug有关的示例程序和patch放到我个人的github上,地址是:https://github.com/chenjiexin/qtbug7618