Qt Signals Slots VS QEvents - Qt跨线程异步操作性能测试与选取建议

news2024/12/27 12:46:21

相关代码参考:https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/qt_event_signal

1.问题的由来

在对 taskBus 进行低延迟改造时,避免滥用信号与槽起到了较好的作用。笔者在前一篇文章中,叙述了通过避免广播式地播发信号,以及频繁的 new 与 delete 来提高软件无线电(SDR)平台的吞吐。近期,考虑到跨线程异步操作其实事件(QEvent)可能更加适合点对点的调用,遂把taskBus的主数据流转使用 Events 进行了改造,收到了大概5-10ms的提升。

这个提升还是没有达到我的期望,因为印象里,信号与槽是非常慢的。那么,问题来了,跨线程 Signal&Slots 与 Events 到底谁要快,为什么快,以及快多少呢?

2. 回顾“信号槽很慢”的印象的源头

在经典的Qt文档里,有一段对信号与槽性能选择的性能描述:

Compared to callbacks, signals and slots are slightly slower because of the 
increased flexibility they provide, although the difference for real applications 
is insignificant. In general, emitting a signal that is connected to some slots,
 is approximately ten times slower than calling the receivers directly, with 
 non-virtual function calls.

“与回调相比,信号和插槽的速度稍慢,因为它们提供了更大的灵活性,尽管实际应用程序的差异并不显著。通常,发射连接到某些插槽的信号比直接调用接收器(使用非虚拟函数调用)慢大约十倍。”

这就是印象的源头了。不过,带着上述问题,仔细阅读,发现还有更多的解释:

 This is the overhead required to locate the connection 
 object, to safely iterate over all connections (i.e. checking that subsequent 
 receivers have not been destroyed during the emission), and to marshall any 
 parameters in a generic fashion. While ten non-virtual function calls may sound 
 like a lot, it's much less overhead than any new or delete operation, for example.
  As soon as you perform a string, vector or list operation that behind the scene 
  requires new or delete, the signals and slots overhead is only responsible for a 
  very small proportion of the complete function call costs. The same is true 
  whenever you do a system call in a slot; or indirectly call more than ten 
  functions. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.

“这是定位连接对象、安全地迭代所有连接(即检查后续接收器在发射过程中是否未被破坏)以及以通用方式整理任何参数所需的开销。虽然十个非虚拟函数调用听起来可能很多,但它的开销比任何new操作或delete操作都要小得多。一旦执行了一个字符串、向量或列表操作,而该操作在后台需要新建或删除,则信号和插槽开销只占整个函数调用成本的一小部分。每当您在插槽中进行系统调用时,情况也是如此;或者间接调用十多个函数。信号和插槽机制的简单性和灵活性非常值得开销,而您的用户甚至不会注意到这一点。”

会不会,上次的优化起到关键作用的不是信号与槽改成了直接函数调用,而是把频繁new delete换成静态内存导致的提升?我立刻进行了测试,发现的确如此。

  • new 和 delete的开销远远大于 signal&slots的开销

3. 编程进行专门测试

我们使用Qt专门对信号与槽、事件这两种跨线程传递消息的方法来进行测试。设计测试由两个对象之间互相以最快速度乒乓消息为场景,如下图所示:

时戳消息
时戳消息
Object1
Object2

含有时戳的消息,可以用来计算和统计平均延迟(消息生成和被处理的时差),以及最终的处理能力。

测试分为信号-槽测试,事件测试,以及单线程对比调用测试。测试进行10000次调用,并模拟实际程序对传递的消息进行一些处理,如产生一定长度的字符串。

通过观察不同长度下的开销,即可直挂感受消息传递开销与处理开销的占比。

3.1 用于测试的消息体

消息体既用于信号与槽测试,也用于Event测试以及直接调用。这样参数中都直接new消息,对大家是公平的。

testevent.h---------------

#include <QEvent>
#include <time.h>
#include <QString>
class TestMsg : public QEvent
{
private:
	static QEvent::Type m_testEvt;
	static QEvent::Type m_startEvt;
	static QEvent::Type m_startSig;
	static QEvent::Type m_quit;
	clock_t m_clk = 0;
	QString m_dummyLongStr;
public:
	TestMsg(clock_t clk);
	~TestMsg();
	inline clock_t clock() {return m_clk;}
	inline void fillStr(int len)
	{
		for (int i=0;i<len;++i)
		{
			m_dummyLongStr.push_back((char)(i*37%64+32));
		}
	}
public:
	static inline QEvent::Type type() {return m_testEvt;}
	static inline QEvent::Type startEvt() {return m_startEvt;}
	static inline QEvent::Type startSig() {return m_startSig;}
	static inline QEvent::Type quitEvt() {return m_quit;}
};

//testevent.cpp---------------
#include "testevent.h"
#include <QDebug>
//Regisit a event
QEvent::Type TestMsg::m_testEvt = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_startEvt = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_startSig = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_quit = (QEvent::Type)QEvent::registerEventType();

TestMsg::TestMsg(clock_t clk)
	:QEvent(m_testEvt)
	,m_clk(clk)
{

}
TestMsg::~TestMsg()
{
	
}

消息体含有一个clock时戳,用于计算传递耗时。同时,有一个fillStr的耗时操作,用于模拟真实的有效数据处理。

3.2 测试对象

测试对象直接派生自 QObject,将在线程中执行测试。为了模拟信号与槽的 meta 开销,设置了26组信号与槽,进行交叉连接,并用第16组进行测试。

#ifndef TESTOBJ_H
#define TESTOBJ_H

#include <QObject>
#include <QEvent>
#include "testevent.h"
class TestObj : public QObject
{
	Q_OBJECT
public:
	explicit TestObj(QObject *parent = nullptr);
	void setBuddy(TestObj * buddy) {
		m_buddy = buddy;
		buddy->m_buddy = this;
	}
	void runDirectCall();
public slots:
	void test_slot1(QEvent * evt){}
	//...
	void test_slot16(QEvent * evt);
	//...
	void test_slot26(QEvent * evt){}
signals:
	void test_sig1(QEvent * evt);
	//...
	void test_sig26(QEvent * evt);

	void evt_finished();
	void sig_finished();
protected:
	void customEvent(QEvent *) override;
private:
	clock_t m_nFirstClkSig = -1;
	quint32 m_nCount_Sigs = 0;

	clock_t m_nFirstClkEvt = -1;
	quint32 m_nCount_Evts = 0;

	clock_t m_nFirstClkDir = -1;
	quint32 m_nCount_Dir = 0;
private:
	TestObj * m_buddy = nullptr;
private:
	void run_signal();
	void run_event();
private:
	void direct_call(QEvent * evt);
};

#endif // TESTOBJ_H

无论以何种接口获得 TestMsg,都执行相同的操作:

//以信号槽为例
void TestObj::test_slot16(QEvent * evt)
{

	clock_t curr_clk = clock();
	if (m_nFirstClkSig==-1)
		m_nFirstClkSig = curr_clk;

	TestMsg * e = dynamic_cast<TestMsg *>(evt);
	if (e)
	{
		++m_nCount_Sigs;
		e->fillStr(fillStrLen);
		if (m_nCount_Sigs==testCounts)
		{
			QTextStream strm(stdout);
			strm << objectName()<< QString().asprintf(" (%llX) run %d Signals, total costs %.2lf ms, AVG cost %.2lf us / test.\n"
									   ,(unsigned long long)this
									   ,(int)(m_nCount_Sigs)
									   ,1e3 * (curr_clk - m_nFirstClkSig) / CLOCKS_PER_SEC
									   ,1e6 * (curr_clk - m_nFirstClkSig) / CLOCKS_PER_SEC / testCounts
									   );
			strm.flush();
			emit sig_finished();
		}
		delete e;

	}
}

void TestObj::run_signal()
{
	for (int i=0;i<testCounts;++i)
		emit test_sig16(new TestMsg(clock()));
}

3.3 测试方法

在 main函数中进行测试:

#include <QCoreApplication>
#include <QThread>
#include "testevent.h"
#include "testobj.h"
int main(int argc, char *argv[])
{
	QCoreApplication a(argc, argv);

	clock_t clk_start = clock();
	printf("StartClk = %d\n",clk_start);
	QThread::msleep(100);

	QThread * thread1 = new QThread;
	QThread * thread2 = new QThread;

	TestObj * obj1 = new TestObj;
	TestObj * obj2 = new TestObj;

	obj1->setObjectName("OBJ1");
	obj2->setObjectName("OBJ2");

	obj1->setBuddy(obj2);

	obj1->connect(obj1,&TestObj::test_sig1,obj2,&TestObj::test_slot16,Qt::QueuedConnection);
	//...
	obj1->connect(obj1,&TestObj::test_sig26,obj2,&TestObj::test_slot16,Qt::QueuedConnection);

	obj1->connect(obj2,&TestObj::test_sig16,obj1,&TestObj::test_slot1,Qt::QueuedConnection);
	//...
	obj1->connect(obj2,&TestObj::test_sig16,obj1,&TestObj::test_slot26,Qt::QueuedConnection);

	obj1->moveToThread(thread1);
	obj2->moveToThread(thread2);

	thread1->start();
	thread2->start();

	printf("Test signals & slots, Events...\n");
	QCoreApplication::processEvents();

	a.connect (obj1,&TestObj::sig_finished,[=]()->void{
		QThread::msleep(1000);
		QCoreApplication::postEvent(obj1,new QEvent(TestMsg::startEvt()));
	});
	a.connect (obj2,&TestObj::sig_finished,[=]()->void{
		QThread::msleep(1000);
		QCoreApplication::postEvent(obj2,new QEvent(TestMsg::startEvt()));
	});

	a.connect (obj1,&TestObj::evt_finished,[=]()->void{
		QThread::msleep(1000);
		QCoreApplication::postEvent(obj1,new QEvent(TestMsg::quitEvt()));
	});
	a.connect (obj2,&TestObj::evt_finished,[=]()->void{
		QThread::msleep(1000);
		QCoreApplication::postEvent(obj2,new QEvent(TestMsg::quitEvt()));
	});

	QThread::msleep(1000);
	QCoreApplication::processEvents();

	QCoreApplication::postEvent(obj1,new QEvent(TestMsg::startSig()));
	QCoreApplication::postEvent(obj2,new QEvent(TestMsg::startSig()));

	a.processEvents();

	thread1->wait();
	thread2->wait();

	QThread::msleep(2000);

	obj1->runDirectCall();

	printf("Finished.\n");

	thread1->deleteLater();
	thread2->deleteLater();
	obj1->deleteLater();
	obj2->deleteLater();
	QThread::msleep(1000);
	QCoreApplication::processEvents();

	return 0;
}

4. 测试结果

测试环境:i7 10代 win11 Mingw64 Qt6.6

4.1 Debug, Strlen=0,20000次

StartClk = 2
Test signals & slots, Events...
OBJ1 (1AD52AD73E0) run 20000 Signals, total costs 2089.00 ms, AVG cost 104.45 us / test.
OBJ2 (1AD52AD74C0) run 20000 Signals, total costs 32.00 ms, AVG cost 1.60 us / test.
OBJ2 (1AD52AD74C0) run 20000 Events, total costs 14.00 ms, AVG cost 0.70 us /test.
OBJ1 (1AD52AD73E0) run 20000 Events, total costs 27.00 ms, AVG cost 1.35 us /test.
Test Direct Call...
OBJ1 (1AD52AD73E0) run 20000 Direct Calls, total costs 6.00 ms, AVG cost 0.30 us / test.
Finished.

4.2 Debug, Strlen=1000,20000次

StartClk = 2
Test signals & slots, Events...
OBJ1 (21AFB83BBA0) run 20000 Signals, total costs 2541.00 ms, AVG cost 127.05 us / test.
OBJ2 (21AFB83B820) run 20000 Signals, total costs 1630.00 ms, AVG cost 81.50 us / test.
OBJ2 (21AFB83B820) run 20000 Events, total costs 1373.00 ms, AVG cost 68.65 us /test.
OBJ1 (21AFB83BBA0) run 20000 Events, total costs 1403.00 ms, AVG cost 70.15 us /test.
Test Direct Call...
OBJ1 (21AFB83BBA0) run 20000 Direct Calls, total costs 1377.00 ms, AVG cost 68.85 us / test.
Finished.

4.3 Release, Strlen=0,100000次

StartClk = 5
Test signals & slots, Events...
OBJ1 (294D24BB5F0) run 100000 Signals, total costs 2236.00 ms, AVG cost 22.36 us / test.
OBJ2 (294D24BB930) run 100000 Signals, total costs 25.00 ms, AVG cost 0.25 us / test.
OBJ2 (294D24BB930) run 100000 Events, total costs 12.00 ms, AVG cost 0.12 us /test.
OBJ1 (294D24BB5F0) run 100000 Events, total costs 38.00 ms, AVG cost 0.38 us /test.
Test Direct Call...
OBJ1 (294D24BB5F0) run 100000 Direct Calls, total costs 14.00 ms, AVG cost 0.14 us / test.
Finished.

4.4 Release, Strlen=1000,100000次

StartClk = 4
Test signals & slots, Events...
OBJ1 (154799851A0) run 100000 Signals, total costs 1358.00 ms, AVG cost 13.58 us / test.
OBJ2 (154799854E0) run 100000 Signals, total costs 620.00 ms, AVG cost 6.20 us / test.
OBJ2 (154799854E0) run 100000 Events, total costs 543.00 ms, AVG cost 5.43 us /test.
OBJ1 (154799851A0) run 100000 Events, total costs 547.00 ms, AVG cost 5.47 us /test.
Test Direct Call...
OBJ1 (154799851A0) run 100000 Direct Calls, total costs 527.00 ms, AVG cost 5.27 us / test.
Finished.

5. 结果分析

可以看见,

  • Release下,Event 和 直接调用的性能几乎完全一样。Debug下略慢。
  • Release下,如果发射的信号只对应一个槽,则比直接调用的性能稍微差一些,但远没有10倍的差距,比如Obj2发射的某个信号,只连接到Obj1 的1个槽,其实区别不大。
  • Release下,如果发射的信号对应多个槽,则性能显著下降,正如obj1发射的一个信号,连接到多个槽,结果就很耗时。发射的信号会给每个槽都走一遭。
  • 如果存在大量的new\delete,则无论何方式,区别都不大。new/delete非常耗时。

6. 开发建议

对于密集的点对点异步调用,显然是Event比较好。如果是广播性质的多对多,Event需要循环,则使用信号与槽会利于开发。此外信号与槽会自动维护双方的可用性,在目的析构后不再调用槽。Events因为要给入指针,则必须自己确保指针的有效性。

提高速度的关键还是使用静态内存,避免频繁new、delete。

7. 改进效果

taskBus的异步调用最后一步队列操作,使用Event而不是信号,来通知线程干活,只获得了大概10ms的延迟优化,是因为大部分延迟实际是在缓存、滤波器上,并不在信号-槽中。使用了QEvent后,构造的全通无线网络,在 128kbps的带宽下,还是要稍微顺畅一些。

Gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1125988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS鸿蒙原生应用开发设计- HarmonyOS Sans 字体

HarmonyOS设计文档中&#xff0c;为大家提供了独特的字体&#xff0c;开发者可以根据需要直接引用。 开发者直接使用官方提供的字体内容&#xff0c;既可以符合HarmonyOS原生应用的开发上架运营规范&#xff0c;又可以防止使用别人的字体侵权意外情况等&#xff0c;减少自主创…

大模型:机器学习的崭新时代

原创 | 文 BFT机器人 在机器学习领域&#xff0c;随着计算能力和数据规模的不断增长&#xff0c;大模型成为一种引人注目的技术。这些具有大规模参数和参数量的机器学习模型正在改变着我们对于人工智能的认识&#xff0c;大模型的出现使得机器学习模型能够处理更复杂的任务&am…

看完这篇 教你玩转渗透测试靶机Vulnhub——Mr-Robot :1

Vulnhub靶机Mr-Robot :1渗透测试详解 Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;暴力破解&#xff1a;③&#xff1a;登入后台GetShell&#xff1a…

docker入门加实战—项目部署之DockerCompose

docker入门加实战—项目部署之DockerCompose 我们部署一个简单的java项目&#xff0c;可能就包含3个容器&#xff1a; MySQLNginxJava项目 而稍微复杂的项目&#xff0c;其中还会有各种各样的其它中间件&#xff0c;需要部署的东西远不止3个。如果手动的逐一部署&#xff0c…

RTSP/Onvif安防平台EasyNVR接入EasyNVS,出现报错“Login error, i/o deadline reached”的解决方法

EasyNVS管理平台具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;可以将接入的视频资源实现视频能力统一输出&#xff0c;并能进行远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户反馈&#xff0c;RTSP/Onvif协议…

C语言用筛选法求 100 之内的素数(挖去 1,被除数平方根)

完整代码&#xff1a; // 用筛选法求 100 之内的素数&#xff08;挖去 1&#xff0c;被除数平方根&#xff09; //筛选法又称筛法&#xff0c;具体做法是&#xff1a;先把N个自然数按次序排列起来。1不是质数&#xff0c;也不是合数&#xff0c;要划去。第二个数2是质数留下来…

三刷操作系统之一文带你搞懂FreeRTOS——互斥信号量和递归互斥信号量

1.互斥信号量 互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙…

apple MFI工厂认证,干货,为防止MFI工作人员查看,已设置VIP阅读

一开始以为审核特别严格,准备了好久,经历过了之后会发现很简单,1个小时完成了所有审核事项。 好好招待审计员,比如能接送就接送,到点吃饭就尽量约时间吃饭后再审计,找个正式的会议室,该摆盘水果就摆上,让审计员感觉到公司是很重视这次的MFI审核,但是不能贿赂发红包那…

第一章 | 计算机网络原理 谢希仁(第八版)_ 习题答案

文章目录 计算机网络原理 谢希仁&#xff08;第八版&#xff09;第一章习题答案1-011-021-031-041-051-061-071-081-091-101-111-121-131-141-151-161-171-181-191-201-211-221-231-241-251-261-271-281-291-301-311-321-331-341-351-36 计算机网络原理 谢希仁&#xff08;第八…

Arm 警告其 GPU 驱动漏洞正被活跃利用

导读Arm 周一警告其 Mali 系列 GPU 驱动漏洞 CVE-2023-4211 正被活跃利用。 Mali GPU 被广泛用于 Google Pixels 等 Android 手机&#xff0c;Chromebook 等 Linux 设备。 本地非特权用户可利用该漏洞访问已释放的内存。访问不再使用的系统内存是将恶意代码加载到攻击者可执行…

MFC+OSG(Open Secene Graph)场景实现中文HUD(head up display)效果,防止中文乱码

MFCOSG&#xff08;Open Secene Graph&#xff09;场景实现中文HUD(head up display)效果&#xff0c;防止中文乱码 背景&#xff1a;为什么同时使用MFC和OSG 我建立了一个MFC工程&#xff0c;以OSG作为视图显示的基础&#xff0c;通过点击MFC菜单启动某些功能。因此&#xf…

基于springboot实现地方废物回收机构平台管理系统【项目源码+论文说明】

基于springboot实现地方废物回收机构管理系统演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把地方废物回收机构管理与现在网络相结合&#xff0c;利用java技术建设地方废物回收机构管理系统&#xff0c;实现地方废物回收机构的信息化。则对于进一步提高地方废物回收机…

【Javascript】运算符(赋值,算术,自增,自减)

目录 赋值 算术 单个变量&#xff1a; 多个变量&#xff1a; 在字符串&#xff0c;数组中充当连接符 自符串与字符串 数组与数组 数组与字符串 自增与自减 前置 自增 自减 后置 自增 自减 赋值 var a 1;算术 单个变量&#xff1a; var a 1;a 1;console.l…

最小二乘法,可视化UI界面

import tkinter as tk import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from pylab import mplmpl.rcParams[font.sans-serif] [FangSong] # 指定默认字体 mpl.rcParams[axes.unicode_minus] False …

【matplotlib】matplotlib的颜色表

【matplotlib】matplotlib的颜色表 文章目录 【matplotlib】matplotlib的颜色表1. 颜色表Reference 1. 颜色表 在使用matplotlib库进行绘图的时候&#xff0c;只需要指定关键字coloryour_color就能修改绘制的颜色了&#xff0c;具体的颜色表如下。 Reference https://finthon…

文献阅读(207)FPGA HBM

题目&#xff1a;HBM Connect: High-Performance HLS Interconnect for FPGA HBM时间&#xff1a;2021会议&#xff1a;FPGA研究机构&#xff1a;UCLA Jason Cong 题目&#xff1a;Demystifying the Memory System of Modern Datacenter FPGAs for Software Programmers throug…

五金经营小程序商城的作用体现在哪

对消费者而言&#xff0c;如今线上购买五金是很多人的选择&#xff0c;传统线下购买&#xff0c;不仅需要跑路&#xff0c;而且店内未必有所需品&#xff0c;但线上平台则一目了然购买所需品&#xff0c;本地/外地均可以触达到&#xff0c;同时还可对用户/会员进行高效管理&…

vscode代码快捷输入

Vscode代码片段快捷输入 常用的代码片段为了避免重复输入,可以使用Vsco的中用户代码片段进行设置,这样就可以实现快捷输入. 操作流程 如下 打开vscode的设置 2. 找到用户代码片段 3. 选择模板 4. 然后写入代码片段即可 上面的代码片段可以设置多个,看自己 重点关注的是 prefi…

GoLong的学习之路(四)语法之循环语句

书接上回&#xff0c;上回说到运算符&#xff0c;这次我们说一个编程语言中最重要的一点&#xff1a;流程控制&#xff0c;及循环语句 文章目录 循环语句if else(分支结构)if条件判断特殊写法 for(循环结构)for range(键值循环) switch casegoto(跳转到指定标签)break(跳出循环…

2022年京东双11母婴品类数据回顾

母婴产品作为部分家庭的刚需&#xff0c;双11期间的行业热度也节节攀升&#xff0c;2022年双11期间&#xff0c;行业中不少品类赛道势头猛进。下面&#xff0c;鲸参谋带大家一起来回顾2022年双11期间母婴行业大盘及母婴重点细分赛道的销售表现。 母婴行业大盘 2022年双11期间&a…