[Development] What's Q_PRIMITIVE_TYPE for?

Lars Knoll lars.knoll at qt.io
Fri Nov 13 08:45:35 CET 2020

> On 13 Nov 2020, at 07:48, Thiago Macieira <thiago.macieira at intel.com> wrote:
> On Thursday, 12 November 2020 07:07:53 PST Lars Knoll wrote:
>> So except for the memset(0) for default construction, Primitive and Trivial
>> have the same conditions in your list here. My proposal would be to drop
>> the memset(0) optimisation and with that unify those two groups.
> Not exactly. The distinctions come up at these points (see 2 and 3):
> 1) extending the container with new elements (resize() with sufficient 
> capacity already allocated or constructor with element count): memsettable 
> types can benefit from memset(0), as can most trivial types, except those 
> containing pointer-to-member-object.
> 2) reallocating the container: relocatable types (which include all trivially-
> copyable types) can be relocated by memcpy, which in turn means they can be 
> relocated by realloc().
> 2b) relocating elements without reallocating (insertion): same as #2
> 3) detaching: trivially copyable types can be copied with memcpy(), but 
> relocatable ones cannot
> Q_PRIMITIVE_TYPE is currently something more restrictive than trivial type 
> because of #1.

Correct, that’s exactly what I said. So if we remove the memset() optimisation (that is pretty much only used for resize()), all trivial types can be treated as primitive. resize() is not really a very common operation, so we wouldn’t  loose a lot even if the compiler doesn’t manage to fully optimise it. 
>> (Note: I'm completely ignoring the lifetime issues at just blessing a block
>> of memory and saying "there are N objects here". Thiago is right, we
>> shouldn't ignore that. But I'm not sure how, except by biting the bullet
>> and calling constructors/destructors, then relying on the compiler to do
>> the right thing.)
>> Those lifetime issues need to be tackled differently. One option could be to
>> abandon all our optimizations in a checked build, and only enable them in
>> release mode.
> We have to assume that any build with optimisations turned on means a checked 
> build. Therefore, your proposal is self-contradictory.

Huh? In a release build assertions also expand to nothing. So it does remove many checks. Opposed to that, a debug build or one with address sanitiser enabled, could check those kind of things more thoroughly.
> I propose we ignore the lifetime issues *for* *now*. We need a proper language 
> solution to declare objects' lifetime has started when we do either of the 
> three operations above, without loss of performance afforded by the memcpy / 
> memset. So either the language gives us a std::launder-like interface to bless 
> the fait accompli or the compilers give us equivalent performance without such 
> an interface. The relocation of non-trivially copyable types will likely have 
> a language update because the current direction seems to include adding a 
> destructive move operation.
> But we will not be able to ignore the problem forever and I think the bill 
> will come due before the end of the Qt 6 lifetime. For that reason, I am 
> asking we decide which of the above is worth. I think:
> 1) not worth memsetting and very likely the compilers can be fixed to 
> optimise. The benefit of memset compared to the whole operation (including 
> malloc()) may not be worth the hassle
> 2) worth it because the language seems to be going in the direction we want 
> and have showed the way for the last 15 years (relocatable types, destructive 
> move)
> 3) uncertain; if the compilers do #1, it's likely they'll do #3 too.

I agree that (1) is probably not be worth it, especially if it means that we have to largely cut down on the amount of classes that can be marked as primitive by default.

(2) and (3) are clearly worth it. And for types with trivial copy/move operations, the standard even says that memcpy’ing them is ok.


More information about the Development mailing list