[Development] Question about QCoreApplicationData::*_libpaths

Kevin Kofler kevin.kofler at chello.at
Sat Jan 16 06:07:39 CET 2016


Bubke Marco wrote:
> Actually this convince is hurting you if you need performance. I would
> prefer the convenience on top of lower level api.
[and at the end:]
> It really depends what you want to do. I would prefer it we had a CoW
> wrapper around std vector. Best of both worlds.

The question is whether such a wrapper would be able to support all 
functionality of the current QVector or whether it would run into some 
limitations. I suspect we would lose at least some optimizations from 
Q_DECLARE_TYPEINFO. There might also be some API that cannot be implemented, 
or at least not efficiently. (This could be even worse for other containers 
than QVector.) So, it sounds like a great idea in theory, but I wonder how 
practical it is in practice. I guess I would have to see a practical 
implementation to know for sure.

What I know for sure is that my priority queue abstraction on top of 
std::priority_queue can support only a very limited API, and it would be the 
same if QQueue were to be implemented on top of std::queue: The current 
QQueue publicly inherits the underlying QList. The STL API, instead, wraps 
the underlying vector and only offers the queue primitives, a completely 
different philosophy that leads to a much more limited API. Of course, this 
could be worked around by wrapping std::vector directly (or maybe 
std::dequeue), or by simply keeping QQueue implemented on top of QList and 
only changing QList. But there might be other such API limitations in the 
STL.

And QList might not be implementable at all on top of the STL, at least not 
the flexible way it is now (array of pointers, except when the type can be 
put in the place of the pointer).

> I prefer algorithms which are based on iterators to handwritten loops.
> They are easier to parallise in the future too.

Well, then I have good news for you:
some_algorithm(myQVector.begin(), myQVector.end());
is perfectly safe. If myQVector was shared, the begin() or end() call, 
whichever is executed first, will detach, and then some_algorithm will 
operate on a new deep copy of myQVector that nothing else can possibly have 
seen yet. If myQVector was not shared to begin with, then it is of course 
safe too and there is no detach in that case.

The iterators are only problematic when you keep an iterator across an 
assignment, such as:
QVector<int>::iterator evil = myQVector.begin();
QVector<int> broken = myQVector;
*evil = 666;
where "evil" will corrupt "broken".

IMHO, this is simply a user error and a case of "don't do that then". This 
issue is explicitly warned about in the Qt documentation. If you really need 
a copy of myQVector at this place, then you can use this:
QVector<int>::iterator evil = myQVector.begin();
QVector<int> unbroken = myQVector;
unbroken.data(); // force detach
*evil = 666;
and everything will magically work again.

> Atomics on the other side can produce strange performance bugs with false
> sharing. I don't believe they are the future in a many core environment
> where you share cache lines very often.

Well, we could in principle do CoW without atomics (Qt 3 did them that way), 
but the drawback then is that we would lose thread-safety (which is of 
course why Qt 4 introduced atomic reference counts).

> Hmm most other languages I know provide  more convenience than Qt but are
> slower. I think you pick C++ in the context of speed. So we should provide
> a wrapper around std vector with cow.

Well, most other languages I know provide *less* convenience than Qt but are 
slower. ;-) See also my rant about Java elsewhere in this thread. Its 
"everything is a reference" explicit sharing (which I have also seen in many 
other languages, even and especially ones marketed to beginners: Visual 
Basic, Python and many more) is a poor substitute for CoW. That sharing 
issue you can run into when using iterators on Qt containers (see above) is 
how those languages *always* work: If you do not explicitly clone the 
object, writing to any "copy" will actually affect them *all*. And even 
Java's "final" will not protect you, because it is only the equivalent of a
Foo * const, not of a const Foo * or const Foo &. (That's why immutable 
objects are so popular in those programming languages, but those are of 
course a major performance issue if you need to make any modifications, 
because you are deep-copying all the time.)

> QList is really a trap.
> 
> struct Entry {
>   QString text;
>   bool isHtml;
> } ;
> 
> QList<Entry> list;
> 
> Do you see the problem?

I see that this is going to create a vector of Entry * and so require a lot 
of allocations of size sizeof(void *) + 1 + padding. Congratulations, you 
found the worst case of QList. Now imagine a future version of your code 
changes your Entry to look like this:
struct Entry {
  QString text;
  bool isHtml;
  int data[10000];
} ;
and suddenly, the QList strategy will not be that bad an idea anymore, 
especially if you find yourself doing random inserts or removals. 
(Incidentally, they plan to change QString in Qt 6 with an alleged 
"optimization" that will actually make your struct look not all that 
different from that: They want to make QString big so that they can store 
small strings without an allocation. I call that the "small string 
pessimization". I strongly doubt the saved allocation is worth passing 
around huge blobs for a type as common as QString.) Good luck waiting for 
the memory move of your QVector or std::vector if you delete element 5000 
out of a 10000-element vector of this new Entry type.

        Kevin Kofler




More information about the Development mailing list