1

I have a task of processing UDP data with ~10kHz read rate. I'm working with Qt 5.13.1 (MinGW32), so I tried to use a QUdpSocket.
I made a simple test program, but the results are a bit frustrating. The readyRead() signal is just too slow. For some reason I'm getting delays over 1 or 2 ms every 2-4 signals.
I made a simple packet counter and compare it with what I see in wireshark. Sure enough there is a packet loss.

What can I do to enhance perfomance? Or maybe it's just the limit of the Qt event loop?

I run it with Qt Creator 4.10.0. On Windows 7.

Update: With your advices: I tried:

  1. Moving socket in different thread. This gives a bit more perfomance .. a very bit

  2. LowDelayOption = 1 - I did not notice any changes

  3. ReceiveBufferSizeSocketOption - I did not notice any changes

  4. No usage of QDebug while reading - I did not check it, just use for collecting statistics

udpproc.h

#ifndef UDPPROC_H
#define UDPPROC_H

#include "QObject"
#include "QUdpSocket"
#include "QHostAddress"
#include "QThread"

#include "QDebug"
#include "networker.h"

class UDPProc : public QObject
{
    Q_OBJECT
public:
    UDPProc();
    ~UDPProc();
private:
    QUdpSocket dataServerSocket;
    NetWorker* netWorker;
    QThread netThread;


};

#endif // UDPPROC_H

udpproc.cpp

UDPProc::UDPProc() {

netWorker = new NetWorker(&dataServerSocket);
netWorker->moveToThread(&netThread);
netWorker->getInnerLoop()->moveToThread(&netThread);

connect(&netThread, SIGNAL(started()), netWorker, SLOT(serverSocketProccessing()));
connect(&this->dataServerSocket, SIGNAL(readyRead()), netWorker->getInnerLoop(), SLOT(quit()));

QString address = "127.0.0.3:16402";
QHostAddress ip(address.split(":").at(0));
quint16 port = address.split(":").at(1).toUShort();
dataServerSocket.bind(ip, port);

//dataServerSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
dataServerSocket.moveToThread(&netThread);

netThread.start();

//dataServerSocket.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 128000);
//qDebug()<<dataServerSocket.socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toInt();

}

networker.h

#ifndef NETWORKER_H
#define NETWORKER_H

#include <QObject>
#include "QElapsedTimer"
#include "QEventLoop"
#include "QUdpSocket"
#include "QVector"

class NetWorker : public QObject
{
    Q_OBJECT
private:

    QElapsedTimer timer;
    QVector<long long> times;

    QEventLoop loop;
    QUdpSocket *dataServerSocket;

    char buffer[16286];
    int cnt = 0;
public:
    NetWorker(QUdpSocket *dataServerSocket);
    ~NetWorker();
    QEventLoop * getInnerLoop();

public slots:
    void serverSocketProccessing();

};

#endif // NETWORKER_H

networker.cpp

#include "networker.h"

NetWorker::NetWorker(QUdpSocket *dataServerSocket)
{
    this->dataServerSocket = dataServerSocket;
}

NetWorker::~NetWorker()
{
    delete dataServerSocket;
}

QEventLoop *NetWorker::getInnerLoop()
{
    return &loop;
}

void NetWorker::serverSocketProccessing()
{
    while(true){
        timer.start();
        loop.exec();
        times<<timer.nsecsElapsed();
        while(dataServerSocket->hasPendingDatagrams()){
            dataServerSocket->readDatagram(buffer, dataServerSocket->pendingDatagramSize());
        }
        if (times.size() >= 10000){
            long long sum = 0;
            for (int x : times){
                //qDebug()<<x;
                sum += x;
            }
            qDebug() << "mean: "<<sum/times.size();
            break;
        }

    }
}
Letargon
  • 23
  • 5
  • 1
    First thing I would do is move your UDP reading into it's own separate/dedicated QThread (if you haven't done so already); if it's running in the main Qt/GUI thread then it will have to wait for unrelated GUI operations to complete from time to time, before it can execute. Also it can help to call `setSocketOption(ReceiveBufferSizeSocketOption, someLargeNumber);` to make the socket's receive buffer larger, and/or set the UDP-receive thread to a higher execution priority. – Jeremy Friesner Oct 28 '19 at 14:35
  • @Jeremy I run it in console application without gui so i'm not sure if there are unrelated signals. And i tried setting different values for ReceiveBufferSizeSocketOption (checking myself with socketOption(ReceiveBufferSizeSocketOption), but with no result. – Letargon Oct 28 '19 at 15:16
  • At that rate you're better off dedicating a separate thread to a blocking read. – Botje Oct 28 '19 at 15:18
  • If you run it in a separate thread, then you can be sure that there are no unrelated signals. – Jeremy Friesner Oct 28 '19 at 15:40
  • @Jeremy, Oh, it's too hard to evaluate, every time there are new numbers. Just run it in separate thread using an eventLoop to wait for a signal. I evaluated it with average waiting time for 10000 packets, and got 6 ms. The same (+-1ms) is for old version. This time no qdebug while reading. I guess i just want too much. I was sure that >1ms is veery long for just optimization issue. – Letargon Oct 29 '19 at 12:06

2 Answers2

2

You cannot receive socket packets on Windows with so high rate ever. It is limit of the operating system. Even using QAbstractSocket::LowDelayOption and if move your receiving code into an infinite loop such this:

socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
...

for (;;)
{
    if(socket->waitForReadyRead(1)) // waits for some events anyway
    {
        // read here
    }
}

Alternatively, you can embed some time code field into you data packet structure and send several packets together instead or use some connection where are no packet lost. As example, use TCP connection + transactions because the next situations are possible for a socket:

  • Full packet received
  • Received only part of the packet
  • Received several packets together

Also, do not try to change readBufferSize:

If the buffer size is limited to a certain size, QAbstractSocket won't buffer more than this size of data. Exceptionally, a buffer size of 0 means that the read buffer is unlimited and all incoming data is buffered. This is the default.

This option is useful if you only read the data at certain points in time (e.g., in a real-time streaming application) or if you want to protect your socket against receiving too much data, which may eventually cause your application to run out of memory.

Only QTcpSocket uses QAbstractSocket's internal buffer; QUdpSocket does not use any buffering at all, but rather relies on the implicit buffering provided by the operating system. Because of this, calling this function on QUdpSocket has no effect.

Vladimir Bershov
  • 2,701
  • 2
  • 21
  • 51
  • So the speed of 1ms-15ms to catch one signal is ok? I can accept some packet loss, but waiting for signal is most time-consuming now. Originally i just wanted to write these packets data to a file, but it takes me more time to catch a signal, than actually process data. – Letargon Oct 29 '19 at 12:21
  • @Letargon Yes, the delays are ok for everything dependent on event system in Windows. You cannot avoid the delay even using a loop. – Vladimir Bershov Oct 29 '19 at 12:53
1

When measuring timecritical sections of your code I recommend to avoid using qDebug (or any other slow-ish print/debug functionality). It might have too big of an effect on your actual measurements.

What I suggest is that you store the timing values received from QElapsedTimer to a separate container (QVector for example, or just a single qint64 that you average over time) and only show the debug messages once in a while (every second or only at the end). That way the overhead caused by the measurement is has less effect. The averaging over a longer period of time will also help with the variance of your measurement results.

I also recommend you to use QElapsedTimer::nsecsElapsed to avoid rounding issues in high frequency situations because QElapsedTiemr::elapsed will always round to the closest millisecond (and you are already measuring thing within 1ms region).

You can always convert the nanoseconds to milliseconds later on when actually showing the results.

What is the size of the data you are receiving at 10kHz rate?

parti82
  • 165
  • 5
  • I'm currently not sure about perfomance requirements. I use local program as data source and i guess it just generates packets with max available frequency. Data rate is about 2.5 MB. I just want to build a kind of efficient system. Updated code with QVectors. – Letargon Oct 29 '19 at 12:11