14 ms·
Comparing C and C++ usage and performance with a real world project
- gens 9y ago> .. which is written in plain C using GLib .. That's about as "C" as C++ is. Why not Gneural or libpng or even GNU make ? http://git.savannah.gnu.org/cgit/gneuralnetwork.git http://git.savannah.gnu.org/cgit/gneuralnetwork.git https://github.com/glennrp/libpng https://github.com/glennrp/libpng http://git.savannah.gnu.org/cgit/make.git/tree/ http://git.savannah.gnu.org/cgit/make.git/tree/
- deleted 9y ago[deleted]
- fpgaminer 9y agoThe minor discussion on the C version's memory leaks reminded me of a neat trick. If you're developing a short lived application, like pkg-config, you can opt to never deallocate. i.e. leak everything. In lightweight, short lived applications there's usually not a lot of incentive to deallocate; your application will never use much memory anyway and the deallocations waste time. You can think of it like treating C as a garbage collected language, except the garbage collection cycle occurs only once at the end of the program :P It really can be an effective trick. Deallocation isn't free, and under certain loads can be quite expensive. The 1000+ leaks in the C version might actually be what's giving it the slight run-time advantage.
- andreasgonewild 9y agoOr take that even further and allocate a slab that's big enough to last the entire runtime and send malloc on vacation. It's worth repeating, with todays focus on web-frameworks, cloud-providers and dogmatics these ideas are slipping into obscurity; which is a shame given how beneficial they can be if your program fits the use case.
- sebcat 9y agoAnd, instead of using pointers into said memory, use indices so that the slab can be reallocated, or just mmap more pages following the slab if you own the process address space. Also, align properly. And having guard pages is always nice. Freeing on exit too, one single deallocation is pretty cheap. I've seen programs building ASTs with hundreds of millions of nodes, where all the nodes were allocated by a separate malloc call, and ref-counted... More than one-third of the startup time (which was counted in minutes) was calls to malloc and free. Some optimizations were made, but in the end we ended up reducing the size of the AST instead of fixing the allocations.
- pjc50 9y agoOuch. Last time I did any serious parser work I used a pool allocator so I could free all the nodes at once, so allocation was just a compare + increment operation. Although that was forced on me by the difficulties of error recovery in yacc.
- humanrebar 9y agoC++ actually supports this approach in the standard library and doing this is a really standard technique in some domains. That's what the allocator template parameter in vector and other containers is for.
- striking 9y agoIt's stop-the-world garbage collection, where collection only happens when the world ends. Side note wrt deallocations being somewhat slow: could something like Boehm conservative GC speed that up, by grouping all the deallocations together, or by doing them on a separate thread?
- agumonkey 9y agoOS delegated one pass GC.
- LukeShu 9y agoI remember one of those sysadmin stories, where a multi-terabyte `cp` command had seemingly completed all of its work, but was sticking around for days; slowly and pointlessly free()ing the 17GB hash table of hardlinks that it had built up; when it could have just exited and let the OS reclaim the memory. https://lists.gnu.org/archive/html/coreutils/2014-08/msg00012.html https://lists.gnu.org/archive/html/coreutils/2014-08/msg0001...
- userbinator 9y agoI'm going to be the first to point out the one major flaw in this comparison: "plain C using GLib" is not comparable to "C++ standard library only" --- what should be compared is "C++ standard library only" and "C standard library only". Reimplementing pkg-config in pure C without GLib would be necessary for that. As for the "memory leaks" --- I haven't looked at the source, but something whose runtime is very short-lived, like pkg-config, may be very well justified in allocating and never freeing, letting the process exit itself be the "ultimate free". I've seen and done this many times myself. I've seen projects that turned from simple and straightforward to buggy (and harder to debug), slow, and bloated because someone decided they wanted to "use C++" and would try to make use of as many "modern C++" features as they could. Converting an existing C program into C++ can yield programs that are as fast, have fewer dependencies and consume less memory. The downsides include a slightly bigger executable and slower compilation times. My experience has been the complete opposite.
- jgh 9y agoReminds me of this anecdote I came across on the internets one time: https://groups.google.com/forum/message/raw?msg=comp.lang.ada/E9bNCvDQ12k/1tezW24ZxdAJ https://groups.google.com/forum/message/raw?msg=comp.lang.ad...
- jamiek88 9y agoThe ultimate in garbage collection indeed! Highly recommend reading that short tale! This lore is slowly being forgotten, thanks for that link.
- humanrebar 9y agoInteresting project! The C++ version uses many memory allocations. Using allocators some in the C++ program would certainly cut down on the number of allocations. It would also be interesting to see if doing so also improved performance. Similarly, it would be interesting to see if using the C++17 string_view (or the gsl version if C++17 isn't available to you) instead of `const string &` parameters affected performance. Finally. I see that in most (all?) cases, objects are returned by value, not returned through reference parameters or pointers. It's interesting to see that that choice didn't compare poorly to a C implementation.
- rjzzleep 9y agoaccording to dan saks, who apparently to some people is famous c++ is faster than c (well in the test setup he describes below) https://accu.org/content/conf2015/DanSaks-Embedded%20Programming%20Death%20Match.pdf https://accu.org/content/conf2015/DanSaks-Embedded%20Program... Language Design Implementation Relative Performance either any inline 1 (fastest) C++ polystate non-inline 1.56 x fastest C++ bundled non-inline 1.65 x fastest C polystate non-inline 1.70 x fastest C bundled non-inline 1.79 x fastest C++ unbundled non-inline 1.82 x fastest C unbundled non-inline 1.95 x fastest He furthermore argued that the biggest mistakes C++ developers did to kill the adoption of C++ for C programmers was to diverge from the previous line of "C++ is a better C" to "if you're using C++ as a better C you're doing it wrong" https://www.youtube.com/watch?v=D7Sd8A6_fYUI https://www.youtube.com/watch?v=D7Sd8A6_fYUI (I have no skin in the game, I was just curious to see if it's worth looking at rust for embedded when I came across that talk)
- WalterBright 9y agoOver in D-land we've embraced the concept of using D as a "better C" :-) https://dlang.org/blog/2017/08/23/d-as-a-better-c/ https://dlang.org/blog/2017/08/23/d-as-a-better-c/ This is not in the sense of tossing away C coded programs wholesale and rewriting it in D, but incrementally using D here and there for parts of a C program. That way, you've always got a working, usable program.
- harry8 9y agoif (existsCoffee) writeln("Drink coffee"); http://ddili.org/ders/d.en/if.html http://ddili.org/ders/d.en/if.html Sad that you adopted one of C's worst features. Why? Can you get rid of it and mandate the bracing every block?
- WalterBright 9y agoMy take on C's biggest mistake: https://digitalmars.com/articles/b44.html https://digitalmars.com/articles/b44.html
- khitchdee 9y agoIf you ignore the protection mechanisms and the class heirarchy built into C++, then a C++ class is like a C struct that can contain function pointers. For many programs this is all that's needed, and the amount of overhead involved in using such an approach to creating objects is obviously lower. So there's no question C will always be faster. It's only when you need protection and class heirarchies that C++ benefits you. That benefit is mainly one of better code organisation.
- humanrebar 9y ago> It's only when you need protection and class heirarchies that C++ benefits you. Well, need is a strong word. You might not need better correctness while still coming out way ahead by using C++.
- khitchdee 9y agoProtection implies a much more complex structure to represent an object and class heirarchies and inheritance imply the need for a runtime. Both these overheads come at a cost. It's the nature of the program you are wriitng that determines whether you will come out ahead. If you were writing a codec, say, you would not use C++.
- humanrebar 9y agoNot necessarily. A trivial scope guard initialized with a lambda has basically no overhead versus the equivalent be-really-careful approach in C.
- khitchdee 9y agoThat would be cruelty to the cat dancing on the hot tin roof.
- Joky 9y agoCan you clarify what runtime needs would a class hierarchy have in C++ that a correctly structured C program wouldn't have?
- milansuk 9y ago> Every manual resource deallocation call is a potential bug. This is confirmed by the number of memory leaks as reported by Valgrind. There are more than 1000 of them, several dozen of which are marked as "definitely lost". You can't compare performance If one program doesn't free memory, which obviously "saves" time. Valgrind can tell you where non-freed heap blocks have been allocated and a fix should not be complicated.
- mcguire 9y ago"Valgrind can tell you where non-freed heap blocks have been allocated and a fix should not be complicated." In theory, theory and practice are the same. In practice, they aren't.
- dingo_bat 9y agoI knew C++ compilation was slow but 30x slower? I'm sure the compile-time memory usage will also show a similar trend. It would be interesting if somebody could explain the reason for this disparity.
- dsign 9y agoIt's a well known fact. Just by including some headers from the standard library the compiler has to go over huge chunks of library template code, and it usually needs quite a few complicated phases to slowly morph those templates into executable code.
- TheCoelacanth 9y agoNot exactly a fair comparison when the C program is using 1.5MB of pre-compiled dependencies while the C++ program reimplements all of the functionality from the dependencies in the code being compiled.
- astrodust 9y agoThe problem, by and large, is that C++ is heavily dependent on header files to implement the Standard Library. It's largely templated, which means there's no way to make a pre-compiled version, the code generated varies wildly depending on the types involved. C has relatively simple header files, they usually contain structs, function signatures, and a bunch of macros. They're easy to parse and apply by comparison, plus don't tend to be as deeply nested. If C++ ever adopts the Pascal-style "module" extensions that have been kicking around in various proposals compile times could shrink by several orders of magnitude.
- ausjke 9y agoIn general there is no way a C program(apple to apple, comparing to its similar c++ version) will be larger than C++, be it static, shared libraries included, or whatever.
- astrodust 9y agoToday, sure, but there's no assurance that this will be true in the future. If more compiler-friendly extensions are added to C++ to help it generate tighter, more nimble machine code because it's given more leeway in optimizations, then the C++ code could be substantially smaller. C doesn't seem as interested in adopting some of the C++ paradigms that could make optimization better, tools like formalized iterators and such. There's been various attempts at pre-compiling the headers over the years, but the results have always been, for various reasons, less than perfect.
- khitchdee 9y agoFWIW, Donald Knuth was a proponent of using C over C++ at the time it first came out. He equated C++ with the use of frameworks in writing programs which he thought were a bad idea for the profession as it would dumb it down. C++ does make code reuse a lot easier.
- overgard 9y ago> C++ does make code reuse a lot easier. Not really, with ABI issues and compiler incompatibility widely used C++ libs are either header-only, or have an "extern C" version of the public API. Id say C++ makes reuse much harder.
- aidenn0 9y ago1) ABI issues and compiler incompatibility hasn't been a problem for 5-10 years (using two compilers for a single binary is relatively rare). 2) Being "header-only" is no impediment to code reuse.
- elderK 9y agoHey there Aidenn0, I'm no expert on C++ and I've been considering using it for several projects. An important thing for my needs is being able to define classes in one shared object and create new subtypes of those classes in another, possibly defining overrides on virtual methods and such. A good friend of mine has said similar things as you - that the ABI issue has not been a major obstacle for some time. And yet, as much as I search, I still find the same-old advice: Don't use STL types in your interfaces or throw exceptions across module boundaries. If all the compilers used for a given platform follow the same ABI, would using a separate and specific STL implementation (say, STLport) instead alleviate that particular issue? Sorry if this question seems a bit rambley but I'd really love to find out how to use C++ in the way I've mentioned.
- khitchdee 9y agoPartly this depends on the platform. C++ is well supported on Microsoft's .NET platform where you can access all the functionality of the .NET libraries through C++. STL, I guess, is more used on Linux. I would advise against trying to use portable libraries and instead using libraries designed for the platform you are targeting. Having said that, a good portable UI library is the open source WxWidgets which is accessible through C++ for OSX, Linux, Windows
- sesutton 9y agoThe aside about Rust is a total straw man. Obviously no one with even a modicum of knowledge of programming languages would think Rust is the only memory safe language. Googling the supposed quote also turns up no results but this post.
- widdershins 9y agoThis is quite a good comparison too, along with lots of advice on code quality. The result was that the speed was almost exactly the same. https://www.youtube.com/watch?v=SIAAvv1O7Gg https://www.youtube.com/watch?v=SIAAvv1O7Gg
- wott 9y agoIt is nonsensical to consider memory leaks as reported by Valgrind on a program that uses Glib. It allocates and builds a whole context system and never frees it regularly, which confuses Valgrind. I am pretty certain almost all 'leaks' come from there. libglib should be put in Valgrind suppression file. It was a very bad choice to choose a program based on Glib for this kind of experiment.
- bjconlan 9y agoI think what you have said here sums up the problem quite concisely. They might as well add core-foundation, qt-core and stllib based executables to the test for 'c' vs 'c++' to give a better cross section.
- hedora 9y agoIt is interesting that the C++ version does twice as many allocations. I suspect this means there is some low hanging fruit for future optimizations.
- 72deluxe 9y ago"The C++ version has no pointers but instead uses value types. This means that all data is stored twice: once in the array and a second time in the hash table." This is interesting. Are they using modern C++ and making use of moves and perfect forwarding? Or are they just throwing std::strings around and doing millions of copies (e.g. remember std::vector must support copyconstructable, so copy constructors & operator=) in the process? That would explain the allocations in C++ being higher perhaps, particularly if they're using the "wrong" containers. Why not sure unique_ptr or shared_ptr? It is worth remembering that move constructors and assignment operators only get used in very specific places and you have to ensure that any constructors you write yourself are explicitly noexcept.