6

Why it is not recommended to emit a large amount of data over signals in Qt?

Why then we have a option to send our own types through them?

  • 4
    Where did you read that? – Tarod Apr 11 '16 at 11:54
  • what do you mean by "a large amount"? – UmNyobe Apr 11 '16 at 12:14
  • @Tarod I get that response on qt.io/forum. I think on QImage which is for instance large. –  Apr 11 '16 at 12:18
  • 1
    @Lazar - when you pass QImage by value, only a shallow copy occurs. So it is not all that heavy. The actual image data will not be copied. – dtech Apr 11 '16 at 12:24
  • https://forum.qt.io/topic/62678/passing-data-between-signals-and-slots/11 look at a bottom @ddriver –  Apr 11 '16 at 12:25
  • 4
    This doesn't sound like it's specific to signals/slots, or even Qt5. It boils down to the simple fact: Copying data doesn't come for free. Well, d'uh, what a surprise. – IInspectable Apr 11 '16 at 12:31
  • 1
    @Lazar - read about implicit sharing in Qt http://doc.qt.io/qt-5/implicit-sharing.html The image data IS NOT copied when you pass the image by value. – dtech Apr 11 '16 at 12:35

3 Answers3

9

Why it is not recommended to emit a large amount of data over signals in Qt?

There's no such recommendation. See this question for discussion.

Data vs. Object

First of all, when we pass data through signal arguments, we pass object instances - by value or by reference.

Yet one must discriminate between the data and the object. A QString might contain a lot of data, but it doesn't imply that it will copy the data when you copy the string object.

// one million worth of 'a's, about 2 megabytes worth of data
const QString large1(1000*1000, QLatin1Char('a'));

large1 is an object of a QString type. It happens that the QString implementation uses implicit data sharing, and copying the object does not copy the data. So, copying a string is cheap, although it's still more expensive than copying a pointer value.

Now let's consider another string type:

// one million worth of 'a's
const std::string large2(1000*1000, 'a');

large2 is an object of a std::string type. Most implementations don't use implicit data sharing, and copying the object will copy the data.

When do Copies Happen?

There are three cases in the signal-slot system that will force an object to be copied:

  1. At the time of signal emission, when the signal's parameter type is a value type. E.g.:

    Q_SIGNAL void mySignal(std::string); // copies or moves the object and data
    Q_SIGNAL void mySignal(const std::string &); // no copies here
    Q_SIGNAL void mySignal(QString); // copies or moves the object, but not the data
    Q_SIGNAL void mySignal(const QString &); // no copies here
    
  2. At the time of invoking the slot, when the slot's parameter type is a value type. Examples are same as above.

  3. Each time a queued connection is used to call the slot.

    This happens when you select a queued connection type, or when the connection is automatic (by default), and the receiver object lives in another thread at the time the signal is emitted.

Examples

Suppose we have the following class:

class C : public QObject {
public:
  Q_SIGNAL void signal1(const QString &); // correct
  Q_SIGNAL void signal2(QString); // don't do that
  Q_SIGNAL void signal3(const std::string &); // correct
  Q_SIGNAL void signal4(std::string); // really don't do that

  Q_SLOT void slot1(const QString &); // correct
  Q_SLOT void slot2(QString); // only do that if you need a value to modify
  Q_SLOT void slot3(const std::string &); // correct
  Q_SLOT void slot4(std::string); // only do that if you need a value to modify
};

We can now try multiple combinations of signals and slots. Assume that in each case the signal is emitted N times, and it's connected to M slots:

Number of Copies of Object, Direct Connection

      signal1 signal2 signal3 signal4
slot1    0       N       -       -
slot2   N*M    N*(M+1)   -       -
slot3    -      -        0       N
slot4    -      -       N*M   N*(M+1)

Number of Copies of Data, Direct Connection

      signal1 signal2 signal3 signal4
slot1    0      0        -       -
slot2    0      0        -       -
slot3    -      -        0       N
slot4    -      -       N*M   N*(M+1)

Number of Copies of Object, Queued Connection

      signal1 signal2 signal3 signal4
slot1    N*M  N*(M+1)    -       -
slot2   2*N*M 2*N*(M+1)  -       -
slot3    -      -       N*M    N*(M+1)
slot4    -      -      2*N*M  2*N*(M+1)

Number of Copies of Data, Queued Connection

      signal1 signal2 signal3 signal4
slot1    0      0        -       -
slot2    0      0        -       -
slot3    -      -       N*M    N*(M+1)
slot4    -      -      2*N*M  2*N*(M+1)

See this answer for a test case.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
3

Why it is not recommended to emit a large amount of data over signals in Qt?

Because it usually involves copying the data, although you could pass by reference as well. And copying of data takes time. If you chose to pass by reference you must keep object lifetime in mind, otherwise you end up with a dangling reference and a crash. So passing by value is safer in this regard.

Why then we have a option to send our own types through them?

Because otherwise how will you send data? If there wasn't an option to do anything computationally expensive, you wouldn't be able to do much with a computer, would you?

Qt uses implicit sharing for most of its container classes, QImage included. This means when you pass by value, the actual image data is not copied, only a shallow copy is made, which is much cheaper.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • The "usually involves copying the data" is false. It's not "usually", it's either "always" or "never", and we aren't talking of copying the "data", but of copying the object instance: whether such a copy will copy the underlying "data" will depend on the implementation! Given these shortcomings, this answer is essentially useless. – Kuba hasn't forgotten Monica Apr 11 '16 at 17:42
  • @KubaOber - you are getting it incorrectly. "usually involves copying the data" means that "usually you pass by value" when you emit data, but you can emit by reference just as well. Got it now? – dtech Apr 11 '16 at 17:46
  • Why would you "usually pass by value" when emitting? It's a pointless premature pessimization for any type other than numerical types and pointers. Even passing a `QString` by value is not as cheap as passing it by reference, since the copy will force an atomic reference count increment. If you inspect Qt itself, there are zero (0) signal signatures that pass large objects by value. It'd be entirely pointless to code that way. – Kuba hasn't forgotten Monica Apr 11 '16 at 17:59
  • Because you are using Qt and it is safer because Qt uses CoW and for CoW classes it is cheap enough. The "essentially useless" answer explicitly warns about the danger of dangling references. DO'H You do realize that atomic means "happens in one clock cycle" right? – dtech Apr 11 '16 at 18:05
  • 1
    Atomic does not mean "in one clock cycle", far from it. A **whole lot of stuff** can happen around an atomic operation, up to shuttling cache lines between cores. An atomic operation merely means that **from the perspective of the machine code**, the operation has given atomic semantics. Clock cycles don't figure anywhere in atomicity, and atomic operations are almost always more expensive than non-atomic ones, even in the case where cache coherency gives it a go without having to move data around. – Kuba hasn't forgotten Monica Apr 11 '16 at 18:45
  • "it is safer because Qt uses CoW and for CoW classes it is cheap enough" I repeat: in **no case** does Qt code itself pass CoW signal arguments by value. Neither should you. It is a premature pessimization, and it's pointless. It's not safer at all. Const references in signal-slot code can't dangle: the compiler and Qt guarantee that. In direct connection, all the slots have been called by the time the signal returns. In queued connection, all the copies have been made by the time signal returns. What "dangling" do you worry about?! – Kuba hasn't forgotten Monica Apr 11 '16 at 18:48
2

Qt has some examples where you can see a QImage sent via signal/slot mechanism. Check this. There is no problem with that.

And some classes, like QCameraImageCapture, provide signals with QImage as parameter. I.e:

void QCameraImageCapture::imageCaptured(int id, const QImage & preview)

Reading the answer in the link you provided, they say that sending large objects via signals by value might decrease the performance of your program. And it's true. But the problem is not the signal: in C++ you should pass large objects by reference, not by value.

Tarod
  • 6,732
  • 5
  • 44
  • 50