[Development] Question about QCoreApplicationData::*_libpaths

Bubke Marco Marco.Bubke at theqtcompany.com
Sat Jan 16 13:06:33 CET 2016


On January 16, 2016 06:08:14 Kevin Kofler <kevin.kofler at chello.at> wrote:

> 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.

Is std::is_trivially_copyable amd other type traits are your friends. They make it still easier than the qt workaround. 
>
> 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.

I would not change all types.  The hash implementations of the stl are not that day to my knowledge. I care about interfaces and there you want to use a vector in the most cases. Maybe a flat unordered map on top of vector would be nice. 

I would be discourage the use of QList and promote vector instead. I don't use the extras features of QList  so I think most people do but QList is can be very suboptimal if you use it with entries which are larger than a pointer. 

There are other pitfalls in the qt containers,  e.g.

if (container.contains(key)) 
   auto value = container.value(key);

auto iterator = container.find(key);
if (iterator! = container.den()) 
  auto value = iterator.value();

The first looks nice but leads to two lookups. And I have seen it very often. 


> 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".

I understand the problem. But my problem is more that I have the atomic pointer which can slow your code done unexpectedly. 

> 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.

I buy your argument but documentation is not the solution. You put the burden on the programmer to understand that his value semantic is not quite a value semantic. Do you look in the documentation all the time before you use a function? ;-) Sometimes 'lets document it' is an excuse for a interface with unexpected behavior. :-)

> 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,  isn't sharing of writeable cache lines between cores a bad idea anyway. I write cache lines because your cach lines are the atomic entities, not your data. And we go in the direction of more and more cores. So CoW is maybe not a good idea anymore. 

http://www.gotw.ca/publications/optimizations.htm

>> 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.)

I used that in small talk and python extensively and I like the idea to make copy explicit with a clone function. It is expensive and sold be done only explicitly. 

You have measured that immutable objects are a major performance obstacle? You can optimize them quite well. And with multicore environments they scale much better than CoW. 


>> 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.

Short string optimization is five to ten  times faster than malloc which is no surprise. And many syrings are small. 

And it would be still malloc with short string optimization because the entry is bigger than a pointer. ;-) 

If you provide data structures which are trivially copy able the simply could be moved in memory for a vector which is quite fast. 

I don't say that there is no use case for a pointed vector but I can always do
Vector<Entry*>

You mostly traverse this data structures very often and a pointed vectors are not that optimal because you get much more cach misses which are really expensive. 

Most structures are not that big so you optimize not for the common case and there are many measurements that show that your pointer can breaking the prefetcher. So maybe in theory of its faster but not in practice. 

Kevin,  you come up with performance arguments but please show me your measurements because most I have seen show the opposite. And with my limited understanding of processors and memory it is reasonable understandable why it is so.  ;-)

Download something like google benchmark and do micro benchmarks. Then we have arguments which stands on a stable fundament. We can always look at the code to see what you are measure and find out what is happen.

I think this argumentation should be productive. We should find a better solution but not fight for our 'optimal' solution. So a open discussion should be much more productive than the repetitions of the same arguments again and again. I think for that type of argumentation you have always to try to get in the context of the other. But like we know we are lazy so repetition is much easier.  ;-)

Why not search for a better solution and not spending too much energy in a trench fight for 'my solution'.

>         Kevin Kofler
>
> _______________________________________________
> Development mailing list
> Development at qt-project.org
> http://lists.qt-project.org/mailman/listinfo/development

--
Sent from cellphone, sorry for the typos



More information about the Development mailing list