QUdpSocket Benchmark for Package Lost Ratio
-
When we planned to ship our app from linux to windows, we decided to use Qt5 because of its cross-platform benifit. QUdpSocket is wildly used in M2M scenario, however it seems that QUdpSocket is slower than platform local API in windows, that can cause many package lost.
Test result
We built a test program, with 100 packages/20ms, find these result in 2 PC (one is a notebook, the other is a workstation).
lost ratio(lost:total) QUdpSocket with simple Loops QUdpSocket with signal-slots Local WinAPI notebook PC 277:10277 2482:12482 3:10003 workstation PC 28:10028 247:10246 0:10000 It seems that:
- Reliability is directly related to CPU performance;
- The packet loss of signal and slot is the most serious;
- Even with a simple loop, it is significantly slower than the native API.
Attachment - Test code
- built it in msys2 mingw32 qt5.14 static
project
QT -= gui QT += network CONFIG += c++11 console CONFIG -= app_bundle QMAKE_LIBS += -lws2_32 DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp HEADERS += \ allinone.h
main.cpp
#include "allinone.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); //Start Sending sendingThread * tsend = new sendingThread; tsend->start(QThread::HighestPriority); QThread::msleep(3000); printf("Start...\n"); //test UDP with a simple loop (while...) int total_loop = test_Loop(); printf("QUdpSocket LOOP:\n\tSend %d, Recv %d, Lost %d.\n", total_loop,tests,total_loop-tests ); QThread::msleep(3000); printf("Start...\n"); //test Udp with a QObject Class , using Signals and Slots. QThread * trecv = new QThread; TestObj * sobj = new TestObj; sobj->moveToThread(trecv); trecv->start(); sobj->start(); trecv->wait(); sobj->deleteLater(); trecv->deleteLater(); const int totaltest_ss = sobj->endID()-sobj->startID()+1; printf("QUdpSocket Signal and Slots:\n\tSend %d, Recv %d, Lost %d.\n", totaltest_ss, tests, totaltest_ss-tests ); QThread::msleep(3000); printf("Start...\n"); //Test Local windows API const int local_total = local_test(); printf("Local Socket :\n\tSend %d, Recv %d, Lost %d.\n", local_total, tests, local_total-tests ); //end tsend->stop(); tsend->wait(); tsend->deleteLater(); QCoreApplication::processEvents(); getchar(); return 0; }
allinone.h
#ifndef ALLINONE_H #define ALLINONE_H #include <QCoreApplication> #include <QThread> #include <QUdpSocket> #include <QHostAddress> #include <QTextStream> #include <QNetworkDatagram> #include <windows.h> static const int tests = 10000; static const int port = 23456; static const int period = 20; static const int batch = 100; static const int packsize = 512;//pack size in 8 bytes /*! * \brief test_Loop Test QUDPSocket using simple loop. * \return total send packages. */ inline int test_Loop() { qint64 startID = -1, endID = -1; QUdpSocket * p = new QUdpSocket; if (p->bind(QHostAddress::LocalHost,port)) { for (int i=0;i<tests;++i) { while (!(p->hasPendingDatagrams())) p->waitForReadyRead(20); QNetworkDatagram g = p->receiveDatagram(); if (g.data().size()<8) continue; const qint64 * d = reinterpret_cast<const qint64 *>(g.data().constData()); if (d) { if (startID==-1) startID = *d; endID = *d; } } p->close(); } p->deleteLater(); return endID - startID + 1; } /*! * \brief The TestObj class test QUDPSocket using signal-slots. */ class TestObj:public QObject { Q_OBJECT public: explicit TestObj(QObject * pa = nullptr):QObject(pa){ connect (this,&TestObj::_cmd_start,this,&TestObj::slot_start,Qt::QueuedConnection); } bool isFinished() const {return m_finished;} int startID() const {return m_startID;} int endID() const {return m_endID;} protected slots: void readPdDt(){ while (m_sock->hasPendingDatagrams()) { QNetworkDatagram g = m_sock->receiveDatagram(); if (g.data().size()<8) continue; if (++m_total>tests) { m_sock->close(); m_finished = true; QThread::currentThread()->quit(); break; } const qint64 * d = reinterpret_cast<const qint64 *>(g.data().constData()); if (d) { if (m_startID==-1) m_startID = *d; m_endID = *d; } } } public: void start(){ emit _cmd_start(); } private slots: void slot_start(){ m_sock = new QUdpSocket(this); connect (m_sock,&QUdpSocket::readyRead,this,&TestObj::readPdDt); if (!m_sock->bind(QHostAddress::LocalHost,port)) { m_sock->deleteLater(); return; } } signals: void _cmd_start(); private: int m_startID = -1; int m_endID = -1; int m_total = 0; bool m_finished = false; QUdpSocket * m_sock = nullptr; }; /*! * \brief local_test test udp recv using local socket API * \return total send */ inline int local_test() { //init wsa const WORD sockVision = MAKEWORD(2,2); WSADATA wsadata; //sockets SOCKET sock; if( WSAStartup(sockVision,&wsadata) != 0 ) { printf("WSA initial failed.\n"); return 0; } //Create sock sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(sock == INVALID_SOCKET) { printf("socket creating failed.\n"); return 0; } struct sockaddr_in sin; //Bind to Localhost:port sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//local host if( bind(sock,(LPSOCKADDR)&sin,sizeof(sin)) == SOCKET_ERROR ) { printf("Can not bind to Address.\n"); return 0; } struct sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); int startID = -1, endID = -1; int total = 0; char revdata[packsize*8]; const long long * pL = reinterpret_cast<const long long *>(revdata); while (total<tests) { int rev = recvfrom(sock,revdata,packsize*8,0,(SOCKADDR*)&remoteAddr,&nAddrlen); if(rev >=8) { if (startID==-1) startID = *pL; endID = *pL; ++total; } } closesocket(sock); WSACleanup(); return endID - startID + 1; } /*! * \brief The sendingThread class send testing packages. */ class sendingThread: public QThread{ Q_OBJECT public: explicit sendingThread(QObject * pa = nullptr):QThread(pa){} void stop(){term = true;} protected: void run() override{ qint64 SendBuf[packsize] = {0}; const char * dtp = reinterpret_cast<const char *>(SendBuf); int iResult; WSADATA wsaData; SOCKET SendSocket = INVALID_SOCKET; sockaddr_in RecvAddr; //---------------------- // Initialize Winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) { wprintf(L"WSAStartup failed with error: %d\n", iResult); return; } //--------------------------------------------- // Create a socket for sending data SendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (SendSocket == INVALID_SOCKET) { wprintf(L"socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return; } //--------------------------------------------- RecvAddr.sin_family = AF_INET; RecvAddr.sin_port = htons(port); RecvAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //--------------------------------------------- // Send a datagram to the receiver while(!term) { for (int i=0;i<batch;++i) { const int BufLen = sizeof(SendBuf) - rand()%(sizeof(SendBuf)-100); ++SendBuf[0]; iResult = sendto(SendSocket, dtp, BufLen, 0, (SOCKADDR *) & RecvAddr, sizeof (RecvAddr)); if (iResult == SOCKET_ERROR) { wprintf(L"sendto failed with error: %d\n", WSAGetLastError()); closesocket(SendSocket); WSACleanup(); return ; } } if (period>0) msleep(period); } //--------------------------------------------- // When the application is finished sending, close the socket. wprintf(L"Finished sending. Closing socket.\n"); iResult = closesocket(SendSocket); if (iResult == SOCKET_ERROR) { wprintf(L"closesocket failed with error: %d\n", WSAGetLastError()); WSACleanup(); return; } //--------------------------------------------- // Clean up and quit. wprintf(L"Exiting.\n"); WSACleanup(); } private: bool term = false; }; #endif // ALLINONE_H
-
Sorry but I don't understand what you're trying to achieve - an udp datagram is sent directly to the underlying OS, see https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_win.cpp.html#1290 and https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_unix.cpp.html#_ZN26QNativeSocketEnginePrivate18nativeSendDatagramEPKcxRK15QIpPacketHeader
And when you try to receive a datagram you're doing a blocked wait for 20ms so ...
-
@Christian-Ehrlicher said in QUdpSocket Benchmark and Advice against Package Lost Ratio:
Sorry but I don't understand what you're trying to achieve - an udp datagram is sent directly to the underlying OS, see https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_win.cpp.html#1290 and https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_unix.cpp.html#_ZN26QNativeSocketEnginePrivate18nativeSendDatagramEPKcxRK15QIpPacketHeader
And when you try to receive a datagram you're doing a blocked wait for 20ms so ...
Ok, When I comment these 20ms code, It's ok, Thank you!
Start... QUdpSocket LOOP: Send 10013, Recv 10000, Lost 13. Start... QUdpSocket Signal and Slots: Send 10245, Recv 10000, Lost 245. Start... Local Socket : Send 10000, Recv 10000, Lost 0. Finished sending. Closing socket. Exiting.
Much better! The only problem is CPU usage is very high when there is no data recieved.
-
I tested again, avoiding memory re-allocate and extra-info (Source Address) can improve performance, It's OK!
inline int test_Loop() { qint64 startID = -1, endID = -1; QUdpSocket * p = new QUdpSocket; char revdata[packsize*8]; const long long * d = reinterpret_cast<const long long *>(revdata); if (p->bind(QHostAddress::LocalHost,port)) { for (int i=0;i<tests;++i) { while (!(p->hasPendingDatagrams())); p->waitForReadyRead(); int sz = p->readDatagram(revdata,packsize*8); if (sz<8) continue; if (d) { if (startID==-1) startID = *d; endID = *d; } } p->close(); } p->deleteLater(); return endID - startID + 1; } /*! * \brief The TestObj class test QUDPSocket using signal-slots. */ class TestObj:public QObject { Q_OBJECT public: explicit TestObj(QObject * pa = nullptr):QObject(pa){ connect (this,&TestObj::_cmd_start,this,&TestObj::slot_start,Qt::QueuedConnection); } bool isFinished() const {return m_finished;} int startID() const {return m_startID;} int endID() const {return m_endID;} protected slots: void readPdDt(){ static char revdata[packsize*8]; static const long long * d = reinterpret_cast<const long long *>(revdata); while (m_sock->hasPendingDatagrams()) { int sz = m_sock->readDatagram(revdata,packsize*8); if (sz<8) continue; if (++m_total>tests) { m_sock->close(); m_finished = true; QThread::currentThread()->quit(); break; } if (d) { if (m_startID==-1) m_startID = *d; m_endID = *d; } } } public: void start(){ emit _cmd_start(); } private slots: void slot_start(){ m_sock = new QUdpSocket(this); connect (m_sock,&QUdpSocket::readyRead,this,&TestObj::readPdDt); if (!m_sock->bind(QHostAddress::LocalHost,port)) { m_sock->deleteLater(); return; } } signals: void _cmd_start(); private: int m_startID = -1; int m_endID = -1; int m_total = 0; bool m_finished = false; QUdpSocket * m_sock = nullptr; };
Output:
Start... QUdpSocket LOOP: Send 10000, Recv 10000, Lost 0. Start... QUdpSocket Signal and Slots: Send 10000, Recv 10000, Lost 0. Start... Local Socket : Send 10000, Recv 10000, Lost 0. Finished sending. Closing socket. Exiting.
-
There is no buffering of a udp datagram in Qt code - the only thing you can do is to increase the os udp buffer. But I don't see this as drawback of Qt - it's simply the nature of UDP.