QTcpSocket write() causing GUI stuttering/delay
-
Hi all,
I have one application which receives TCP data, performs some processing, then sends it to another application via TCP. This end-to-end process works, but I'm experiencing some strange issues when transmitting the processed TCP data to the external application.
When transmission to the remote application is disabled (i.e. commented out as shown below), the GUI operates as expected. However, when the TCP transmission is enabled, the GUI exhibits severe stuttering/delay. It's important to note that the classes involved in the processing and transmission of the data via TCP are in separate threads to the GUI/main thread, so I'm not sure why this stuttering is occuring.
The below is an excerpt from the processing class:
//The processed data is packaged into a QJsonObject for transmission m_jsonData["SomeValue"] = "Some text string"; //I create a QByteArray and QDataStream for reading and transmitting the size of the packet QByteArray packetSize; QDataStream dataStreamPacketSize(&packetSize, QIODevice::ReadWrite); //Set the QDataStream version dataStreamPacketSize.setVersion(QDataStream::Qt_5_14); //Instantiate a QJsonDocument for converting the QJsonObject into a binary data QByteArray QJsonDocument jsonDocument(m_jsonData); QByteArray byteArrayPacket = jsonDocument.toBinaryData(); //Read the size of the packet into the packetSize QByteArray dataStreamPacketSize << byteArrayPacket.size(); //Transmit the packet size header via the tcpConnection class tcpConnectionInstance->tcpClient->write(packetSize); //Transmit the packet tcpConnectionInstance->tcpClient->write(byteArrayPacket); //Flush the socket interface tcpConnectionInstance->tcpClient->flush();
In the above code, the tcpConnection class is instantiated within the processing class, and moved into its thread (this has been verified by checking each class' QThread::currentThreadId()). The processing class defines tcpConnectionInstance as a tcpConnection pointer in its header file.
void processingClass::initialiseClass() { tcpConnectionInstance = new tcpConnection(); tcpConnectionInstance->moveToThread(this->thread()); //Set up signals/slots etc. }
As mentioned above, if I disable the TCP component of the above code, the application runs flawlessly. I.e.:
//Transmit the packet size header via the tcpConnection class //tcpConnectionInstance->tcpClient->write(packetSize); //Transmit the packet //tcpConnectionInstance->tcpClient->write(byteArrayPacket); //Flush the socket interface //tcpConnectionInstance->tcpClient->flush();
This suggests that the packaging of the data into the QJsonObject and converting it into a binary data QByteArray is not an issue.
In terms of data rate, the QJsonObject contains 216 key/value pairs, which results in a byteArrayPacket size of around 8,600 bytes. The rate at which these packets are processing and transmitted is locked to 50Hz.
I will note that if I instead use a dummy QJsonObject as the source of the transmission, with only a handful (<10) of key/value pairs, the GUI remains responsive. This suggests that it's the size of the data being sent that is the issue, but I still don't understand how that would cause stuttering/performance degradation in the main thread, when the processing and TCP transmission is decoupled from the GUI/main thread?
Any input would be greatly appreciated!
-
I think I've found the underlying issue. As somewhat expected, the GUI stuttering isn't directly related to the above code, but the issue is being caused by TCP traffic (from what I can tell).
The processingClass class receives data from an external application via TCP (via another class on a separate thread). As shown above, the processed data is then transmitted via TCP. I've just done some profiling, where I've measured the elapsed time between incoming TCP packets. I.e. how long it takes (in milliseconds) for each TCP packet to be emitted (via signal/slot) from the receiving class to the processingClass class.
I've plotted the results below:
During the profiling, I toggled the TCP transmissions on/off (i.e. the processed data TCP transmission as shown above). As previously stated, the incoming TCP data frequency is locked at 50Hz, which can be seen in the (relatively) stable areas, where the elapsed time is hovering at around 20ms as expected. As soon as I turn on the outbound TCP transmission, the incoming TCP frequency goes haywire. As soon as I turn the outbound TCP transmission offer, the inbound TCP frequency is again relatively stable at 20ms.
These spikes in inbound TCP data reception are definitely the cause of the observed GUI stuttering, as the GUI is only updated once the various functions in the processingClass class have finished.
In the process of writing the above, I've just done some further testing and have actually resolved this issue! The device in question was connected to the network and internet via WiFi, and it looks like the 2.4GHz channel it was connected to had really poor bandwidth, so it looks like it was taking excessive time for the inbound and outbound packets to be received and sent. I've just switched to the 5GHz channel and the issue goes away completely.
-
just as an FYI, this:
void processingClass::initialiseClass()
{
tcpConnectionInstance = new tcpConnection();
tcpConnectionInstance->moveToThread(this->thread());//Set up signals/slots etc.
}
doesn't do any parallelisation.
You're moving the tcpConnection to the thread it was already living in, (presumably) the main gui thread. -
Thanks for that @J-Hilk much appreciated.
You're right; I must have misremembered how I actually had that piece of code setup. The processingClass class is instantiated in the GUI thread, but is moved to its own thread before the initialiseClass() function is called. As such, when the tcpConnection class is instantiated, it's already living within processingClass' thread.
I thought I had solved this issue, but I'm still experiencing significant jitter (if you can even call it that) in the inbound TCP packets, which is causing havoc for the GUI and output packet transmission:
I don't currently have the means to run iperf3 or similar on the device to check what the actual WiFi signal bandwidth is, so it may just be a really poor connection?!