6 ms·
> no actions need to happen on destruction so... you like never free, never fclose, never munmap, never pthread_join, never unlock mutexes, never close network
by jcelerier 8y ago
> no actions need to happen on destruction
so... you like never free, never fclose, never munmap, never pthread_join, never unlock mutexes, never close network connections or what ?
Also, all of your protoypes such as
sg_pipeline sg_alloc_pipeline() {
should really be
sg_pipeline sg_alloc_pipeline(void) {
since the first one can actually be called with any number of arguments (in C, not in C++).
Note also that C doesn't have return-value-optimization, hence all your struct-returning functions can possibly cause a call to memcpy (won't happen when compiled in C++ mode of course) and will generally lead to much more binary bloat than the traditional C way of passing outputs as arguments.
e.g. given this trivial code in foo.c:
typedef struct _foo
{
int x;
float foo, bar;
char z[1024]; // change this to 10024 to get a memcpy!
} foo;
void do_stuff_to_foo(foo*);
foo blah()
{
foo f;
do_stuff_to_foo(&f);
return f;
}
A disassembly after compilation with gcc -std=c99 -O3 gives
blah:
.LFB0:
pushq %rbp
movq %rdi, %rbp
pushq %rbx
subq $1064, %rsp
movq %fs:40, %rax
movq %rax, 1048(%rsp)
xorl %eax, %eax
movq %rsp, %rbx
movq %rbx, %rdi
call do_stuff_to_foo@PLT
movq (%rsp), %rax
leaq 8(%rbp), %rdi
movq %rbp, %rcx
andq $-8, %rdi
movq %rbx, %rsi
subq %rdi, %rcx
movq %rax, 0(%rbp)
movq 1028(%rbx), %rax
subq %rcx, %rsi
addl $1036, %ecx
movq %rax, 1028(%rbp)
shrl $3, %ecx
rep movsq
movq 1048(%rsp), %rdx
xorq %fs:40, %rdx
jne .L5
addq $1064, %rsp
movq %rbp, %rax
popq %rbx
popq %rbp
ret
The same thing built with g++ -O3 gives
_Z4blahv:
pushq %rbx
movq %rdi, %rbx
call _Z15do_stuff_to_fooP4_foo@PLT
movq %rbx, %rax
popq %rbx
ret
While clang (in C mode) goes for a memcpy:
blah: # @blah
pushq %r14
pushq %rbx
subq $1048, %rsp # imm = 0x418
movq %rdi, %rbx
movq %fs:40, %rax
movq %rax, 1040(%rsp)
movq %rsp, %r14
movq %r14, %rdi
callq do_stuff_to_foo@PLT
movl $1036, %edx # imm = 0x40C
movq %rbx, %rdi
movq %r14, %rsi
callq memcpy@PLT
movq %fs:40, %rax
cmpq 1040(%rsp), %rax
jne .LBB0_2
movq %rbx, %rax
addq $1048, %rsp # imm = 0x418
popq %rbx
popq %r14
retq
- Hello71 8y agoif you find yourself automagically pthread_joining, something is probably terribly terribly wrong. similarly, if you commonly need to release a mutex far away from where you acquired it, something is probably also terribly wrong.
- frankzinger 8y ago> automagically pthread_joining What do you mean by this, exactly? You mean leaving it to RAII instead of doing it explicitly?
- crawshaw 8y agoYou can call those functions without object destructors. That is the point being made in that paragraph: there are other ways to program besides having data owning resources.
- obl 8y ago> Note also that C doesn't have return-value-optimization, hence all your struct-returning functions will cause a call to memcpy (won't happen when compiled in C++ mode of course). What ? RVO is precisely needed because a copy in C++ can run arbitrary code and so is not as easy to ellide as a memcpy. RVO is basically a promess you make to have your copy constructor & destructor be semantically harmless compared to a memcpy. There is no need for RVO in C.
- jcelerier 8y ago> There is no need for RVO in C. I updated my comment with some assembly.
- obl 8y agoThanks. Unless something is escaping me, that's an optimizer bug. I'm pretty sure the ABI allows you to do whatever you want with the sret pointer, including passing it to another function to chain return for free. I guess you could say that RVO is more robust since it's implemented in the frontend and does not rely on finding the optimization after a fair amount of lowering. Edit: I don't have time to debug this but at least for llvm, I think this optimization should trigger in the optimizer's memcpy elimination pass ( https://github.com/llvm-mirror/llvm/blob/0818e789cb58fbf6b5e225a3f1c722294881c445/lib/Transforms/Scalar/MemCpyOptimizer.cpp#L803 https://github.com/llvm-mirror/llvm/blob/0818e789cb58fbf6b5e... ). However I don't see why clang could not simply apply the RVO logic to C code as well.
- tomsmeding 8y agoI'd wager the optimisations you're talking about happen just fine if blah is a static function, where the compiler can assume nothing from the outside will call this function, so calling conventions can be broken at will. Seeing as blah isn't a static function, I think the calling convention for C that g++ uses somehow dictates that a memcpy is to be used in this case. Note that I haven't acually tried this, so no guarantees.
- 8y ago
- jstimpfle 8y agoIf you have a need for more than a few pthread_join(), fclose(), etc, something needs to be fixed. In my experience, RAII isn't a good idea. With RAII control flow is implicit and all over the place. With a little data structure organization work the C way is so much better. I'd much prefer 5 explicit fopen/fclose pairs in my program, and 5 lock/unlock, 5 malloc/free, etc., to having to code out-of-band and out-of-context ctor/dtor pairs for each type of "resource" (plus the paralysis when from having to decide what the resources are, what should happen automatically and what explicitly, etc). Regarding the assembly example. If the copy matters performance-wise (note: it doesn't!) then DON'T MAKE A COPY. Have a pointer argument and put the result there.
- jcelerier 8y ago> If the copy matters performance-wise (note: it doesn't!) then DON'T MAKE A COPY. Have a pointer argument and put the result there. it sure does, especially if you're going to make a high performance graphics library. Some old benchmarks I found were on an average of 10-15% performance loss if calling non-RVO'ed code in some tight loop.
- ufo 8y agoIs there a performance difference between C++ with RVO and C where the functions operate on pointers to structs instead of returning the struct directly? Or is it just a matter of coding style? This is something I could never understand very well.
- jstimpfle 8y ago> (note: it doesn't!) That was tongue-in-cheek. Of course you can always find a slow instance for any approach. The more explicit message is: You're optimizing the wrong thing. "Tight loop" and "RVO", these things don't go together. Don't write slow and complicated code and expect the compiler to magically make it fast. Write straightforward code. It's easy. It will be effortlessly fast. There are very few cases where you need to work hard to get fast code (SIMD etc). Typically, if you need to work hard, the code will be slow.
- zik 8y ago
- somebodynew 8y agoI find it bizarre that the compiler isn't more aggressive about this in C (versus C++) as it doesn't need to worry about special member functions. If nothing volatile is involved there's no reason to bother copying anything.
- badsectoracula 8y agoClang (trunk) in godbolt.org seems to produce the same code for both C and C++ (with -O3). Maybe this is some sort of optimization chance that the C-specific parts of both GCC and Clang didn't catch until now?
- jcelerier 8y agoGCC in godbolt is always C++, even if you pass -std=c89/c99 : https://godbolt.org/g/SFMB1G https://godbolt.org/g/SFMB1G ; Clang doesn't even compile with -std=c89
- badsectoracula 8y agoDo not pass -std, just select the language from the combobox at the top right side of the source window. Your code doesn't compile (__cplusplus is undefined) if you select C. (btw i was talking about Clang, not GCC above)
- jcelerier 8y agodamn! I use godbolt everyday and didn't ever notice that you could change language like this. thanks!
- flohofwoe 8y ago> Also, all of your protoypes such as > sg_pipeline sg_alloc_pipeline() { > should really be > sg_pipeline sg_alloc_pipeline(void) { Ah thanks, I fixed that :) In the case of sokol-gfx, missing return value optimization won't be a big deal, but I'll keep that in mind.
- fpoling 8y agoCalling free is unnecessary in many kind of utility code. Writing such utilities in C++ may even slow things down due to useless free calls duplicating the job of kernel. As for threads and mutexes in C using multiple forked processes and communicating using pipes is still a valid option that does not require much if any RAII. Note that am not arguing that RAII is unecesary, it is just I completely agree with the author that RAII is much less needed in C then in C++ due to different code style and structure. Plus there are static code checkers and/or compiler options that allows to enforce various RAII patterns in C.
- BeeOnRope 8y agoOf course in C++ as in C if there is some case where you want to intentionally leak your allocations or otherwise have the memory be cleaned up on process exit rather than via explicit free() or delete you can do that: it's not as if raw pointers are unavailable to C++, after all.
- mistrial9 8y agouhh "binary bloat" ? with that level of detail? not the right thing to cuttingly show as the deep weakness of C the memcpy feature, on the other hand, might be unexpected enough to escalate to something worth fretting about..