6 ms·
In modern gcc, std::variant doesn't look like a competent replacement for old-fashioned tagged unions. On x86_64 Linux, it looks like a function with signature
by tmyklebu 10y ago
In modern gcc, std::variant doesn't look like a competent replacement for old-fashioned tagged unions.
On x86_64 Linux, it looks like a function with signature `void f(std::variant<int, char>)` expects its (8-byte) argument to be passed by reference, whereas `void f2(tagged_union_of_int_and_char)` passes its argument in rdi.
gcc (-O3) also generates miserable code for calling f(42):
4004f0: 48 83 ec 18 sub $0x18,%rsp
4004f4: 48 89 e7 mov %rsp,%rdi
4004f7: c7 04 24 2a 00 00 00 movl $0x2a,(%rsp)
4004fe: c6 44 24 04 00 movb $0x0,0x4(%rsp)
400503: e8 c8 ff ff ff callq 4004d0 <f>
400508: 48 83 c4 18 add $0x18,%rsp
40050c: c3 retq
as compared with calling f2(42):
400510: 48 bf 00 00 00 00 2a movabs $0x2a00000000,%rdi
400517: 00 00 00
40051a: eb c4 jmp 4004e0 <f2>
std::optional seems to have the same disease; the ABI is different from a plain struct containing a bool and the value and you get worse initialisation code. There's also a build time penalty to using std::optional; on my machine, including the code that uses optionals makes my trivial test program take 320ms to compile and link rather than 50ms.
These look like yet more new C++ features that are worse than what they're trying to replace.
- Sharlin 10y agoEh, I get that one of the points of C++ is to minimize abstraction penalty even when you do use abstractions, but these things are compromises. In almost all real-world software development the advantages of abstraction far outweigh micro-optimizations that shave off individual cycles...
- tmyklebu 10y agoWriting ad-hoc tagged unions and two-element 'optional' structs is a completely straightforward mechanical task---exactly the sort of thing that a programming language ought to automate. Why does there need to be a "compromise" between boilerplate and performance here? Why should I have to pay performance to use standard tagged unions and optionals, or conversely, pay by writing boilerplate in order to get performance?
- wolf550e 10y agoIf the individual cycles don't matter, you can use a safer language than C++ which compiles faster and provides more static and dynamic guarantees. If you choose C++ for a new project in 2017 it's because you need all the cycles and would have chosen assembly if you thought you could write the asm fast and cheaply enough.
- nightski 10y agoFirst of all, the world isn't that black and white. Second, the advantage of C++ is that you are not forced to use these abstractions. You can pick and choose when to use them and pay the performance penalty.
- xwvvvvwx 10y agoI think the point is not that the abstractions have some cost, but that the cost is higher than it needs to be. I would also agree that pure performance and deterministic memory management are the two main reasons to use c++ for a new project nowadays.
- wolf550e 10y agoFor the parts of the codebase where you can use abstractions that have runtime costs, you can also choose a safer language that imposes similar runtime costs. I bet it would be easier to write maintainable code in the safer language than in the version+subset of C++ on which your team agrees. For the parts of the codebase where you count cycles, you often want to carefully lay out the data and then use hand coded SIMD. Possibly use a specialized code generator that writes the SIMD asm for you, with less chance of error. If you can't do SIMD, I would just use C, but use a safety-critical code standard for the C part. Existing codebases and existing investment into learning C++ arcana complicates these decisions, but I truly believe C++ is not worth it for new projects.
- fsloth 10y ago"I truly believe C++ is not worth it for new projects." So what would you use if the constraints for the project are of the usual C++ kind? I.e. language has industrial support, proven toolchain, loads of potential candidates who can write the language, the code will compile 20 years in the future, can tightly integrate with a gui ... etc. Honestly, if there is a better language where I percieve C++ to be the best language I would dearly like to know. Half of the reasons I use C++ have nothing to do with the language itself but of the modern computing ecosystem.
- typon 10y agoDoes anyone know what kind of assembly is generated when using std::option in Rust when make a similar function call?
- steveklabnik 10y agovoid f(std::variant<int, char>) would be f(x: IntOrChar) roughly in Rust, I'm too lazy to make the types _exactly_ the same, but https://godbolt.org/g/pZde2A https://godbolt.org/g/pZde2A is roughly it. Actually, https://godbolt.org/g/1vcMeG https://godbolt.org/g/1vcMeG might be even better to compare; I'm not an expert, but looks like the only difference is in the size. last one, adding inline(never) so you can see the calls in main: https://godbolt.org/g/cVDjQH https://godbolt.org/g/cVDjQH movabs rdi, 21474836480 call example::f1@PLT vs mov edi, 5 pop rbp jmp example::f2@PLT so, yup.
- typon 10y agoThank you! This is really cool.
- cfallin 10y agoAnother cool feature, on top of steveklabnik's comment: rustc is actually smart enough to collapse `Option<T>` where `T` is a borrowed reference (pointer) to a single word. Basically, the compiler knows that the pointer can never be null (borrows are always valid when in scope), so it can use the non-null values for `Some(...)` and the null value for `None`. In other words, the compiler can turn an Option into a null-pointer convention by reasoning from first principles. (I think this optimization works for enums in general, and is somehow related to the `nonzero::NonZero` type, but I could be wrong about that.) Example (look at the calls to f1 and f2 in example::main): https://godbolt.org/g/SSs6X2 https://godbolt.org/g/SSs6X2
- steveklabnik 10y agoYes. It's when the type is NonZero.
- chubot 10y agoIs this just a gcc implementation detail or is it required by the ABI? (I know nothing about std::variant yet.)
- yongjik 10y agostd::optional is nice if you have a class with invariants that you want to always maintain (i.e., you don't want to deal with the pesky "uninitialized" state). You can build a class that always requires proper parameter in its constructor, and then you apply optional<> only when you defer initialization. In most cases, I imagine the performance penalty would be dwarfed by the cost of actually using the class.
- ambrop7 10y agoI suspect the issue is that variant<int,char> is not trivially copy constructible (at least when I tested on godbolt), so the ABI believes it is not allowed to pass the object by value. (this is a guess only, correct me if you know better!) Now why it is not trivially copy constructible is another question. But the latest standard draft does not require it [variant.ctor] though it does require the destructor to be trivial if all type destructors are trivial [variant.dtor]. The GCC implementation also seems to ensure only trivial destructor not copy constructor (the code is here [1] if anyone likes to see). EDIT: I did a small experiment that agrees with this - adding a copy constructor causes passing by pointer. https://godbolt.org/g/UG0YXJ https://godbolt.org/g/UG0YXJ EDIT2: I filed a bug report for gcc, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80187 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80187 [1] https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/variant https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-...
- gpderetta 10y agoUnfortunately the abi requires non-trivially copyable/destructible types to be passed by (hidden) reference. It should be possible to implement std::variant in such a way that it is trivially constructible and destructible as long as all elements are, but apparently stdlibc++ doesn't. This is GCC choice and other compilers apparently do differently. Unless GCC changes it before finalising their c++17 implementation, the choice will be unfortunately set in stone for them as they guarantee abi stability of the library. P0602R0 is a proposal to require trivial copy/move (but strangely not destruct), don't know whether it has been accepted though.
- ambrop7 10y agoRight, this is basically what I said in my comment :) Thanks for the proposal reference.