microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
10.2k stars 1.51k forks source link

<functional>: std::function: avoid one indirection for SFO #969

Open AlexGuteniev opened 4 years ago

AlexGuteniev commented 4 years ago

@MikeGitb mentioned in https://github.com/microsoft/STL/issues/964#issuecomment-652925503

Note that the self-referencing is inherent to the Small Functor Optimization which is critically important for performance.

Are you sure? I thought Sean Parent had shown in his lightning talk "Polymorphic Task Template in Ten" (Meeting C++ 2017) how this works without the extra pointer: https://www.youtube.com/watch?v=2KGkcGtGVM4. I don't remember if there were any problems with his implementation that would prevent the same implementation strategy in std::function.


Will try to explain very briefly:

Currently std::function uses pointer to small implementation in its buffer and large implementation on heap.

It could instead both place small and large implementation in its buffer. In this case, large implementation data has to be on heap, but vptr of large implementation is still in the buffer. But both implementations are on a known location, so no pointer to it is needed.

This would result in avoiding extra indirection (both pointer size and run-time pointer chasing).

This will also make std::function not self-referencing ( #964 will be presumably fixed by compiler, but it might come handy if uses decides to move std::function objects with memcpy ).

AlexGuteniev commented 4 years ago

I'd add from myself here: While implementing this, you may simulate vtable with static struct that contains pointer. This may make the technique easier to understand, as vptr re-assignment will happen as plain vptr re-assignment, not as placement new. This may also reduce RTTI overhead.

MikeGitb commented 4 years ago

This will also make std::function not self-referencing ( #964 will be presumably fixed by compiler, but it might come handy if uses decides to move std::function objects with memcpy ).

It will make it non-self-referencing, but I don't think the transformation allows to use memcpy, as it will still have to go through a virtual copy/move operation unless you encode this information (trivially copyable/trivially movable/trivially relocatable) separately.

AlexGuteniev commented 4 years ago

A lot of non-trivial objects can be memcpy'd if you forget to destroy the original. Sure it is UB for nontrivial objects, but it still happens to work.

Some MFC and ATL containers use memcpy this way for optimization.

MikeGitb commented 4 years ago

A lot of non-trivial objects can be memcpy'd if you forget to destroy the original.

a.k.a trivially relocatable (hope that gest into c++23).

Sure, if you are happy with UB and you know that all the callables in your std::functions are trivially relocatable then that change would help (I'd probably not use a std::function int he first place then though).

AlexGuteniev commented 4 years ago

a.k.a trivially relocatable (hope that gest into c++23).

Oh, so this has chance to become standard conforming. Then there is even more sense to make std::finction such, to prepare for trivial relocation future.

frederick-vs-ja commented 3 years ago

Then there is even more sense to make std::finction such, to prepare for trivial relocation future.

If we decide to make function and move_only_function trivially relocatable, we also need to reject non-trivially-relocatable target types for small object optimization. It seems unimplementable without the proposed is_trivially_relocatable though.

AlexGuteniev commented 3 years ago

If we decide to make function and move_only_function trivially relocatable, we also need to reject non-trivially-relocatable target types for small object optimization

Hm, right. Not sure if function worth making trivially-relocatable then.

Also maybe copying small part of the SFO buffer is better thhan trivially relocating always entire SFO buffer