15 ms·
Is C++ fast?
- kensai 8y ago"C with structs" never heard of this saying. What does it mean exactly?
- swest 8y agoIt's just a snarky way of saying plain C.
- piker 8y agoI read it to imply that C is fine without classes.
- easytiger 8y agoI presume it is a glib humorous attempt to say you have structs in c so don't need c++
- flohofwoe 8y agoI guess it's meant as a reduction of the "C with classes" C++ style: Reduce C++ feature usage even more towards C, but without actually switching to a C compiler.
- jng 8y agoThe first version of C++ ever, designed by Bjarne Stroustrup at AT&T as a thin layer on top of C, was called "C with classes". It was implemented as some kind of source code preprocessor that took ".cc" code and output actual ".c" code, probably not even fully parsing the input code. It is already a somewhat funny first joke saying that the subset of modern C++ that's a nice language is just the C part and the "class" paradigm ("C with classes"). It is another second somewhat funny joke to say that actually the nice part is only an even more restrictive subset, "C with structs". Yes, structs and classes are virtually equivalent in C++, but let's assume they mean structs as in "classes without member functions". Which were already present in C. Which is why it is funny. Jokes are never funny when explained :/ Source: I'm a somewhat experienced stand-up comedy practicioner, apart from a very experienced software developer. I wholeheartedly recommend reading Stroustrup's "Design and evolution of C++", gives you so much valuable and interesting background.
- pjmlp 8y agoIt is even funnier that all major C compilers, and in some cases libc, are nowadays implemented in C++.
- ncmncm 8y ago"C with classes" was never any sort of "source code preprocessor", except in the sense that every compiler is. From day 1 it was a full compiler that happened to generate C at the back end. At the time this was novel, and was easier for people to understand as if it were a preprocessor like RATFOR. Nowadays, generating C is an extremely familiar approach to bootstrapping a new language, and nobody pretends your compiler is "just a preprocessor". (Also, nowadays, you might better generate LLVM. But that option is new.) So, saying "C with Classes was a preprocessor" amounts to simple slander.
- bayesian_horse 8y agoC++ gets you into trouble really fast ;-)
- tqy 8y agoThey are bloating C++ so much that it'll end up as slow as Python or any other high level language with none of the advantages of being a high level language. Just the fact that we're asking ourselves "is C++ fast" should be frightening, for a language that has always rivalled C closely.
- CoolGuySteve 8y agoC++ is relatively fast but MSVC’s STL library’s debug performance is abysmal. It’s been like this since at least MSVC 2008 too. I’m not sure why Microsoft doesn’t think it’s a priority to improve.
- wolfgke 8y ago> C++ is relatively fast but MSVC’s STL library’s debug performance is abysmal. It’s been like this since at least MSVC 2008 too. I’m not sure why Microsoft doesn’t think it’s a priority to improve. If you want fast performance, you use a Release build. The purpose of a Debug build is that you can detect the source of as many bugs in your program as possible. So for Release build, it is a priority to improve performance (and in my opinion Microsoft did a good job). For the Debug build, it is a priority to improve the detectability of bugs in your program. Here also in my opinion Microsoft did a good job.
- svdree 8y agoThat sounds reasonable, until you have to debug an issue that only manifests itself when working with a huge dataset, in which case the whole thing becomes pretty much unusable. As the article says, debug mode is expected to be slower, but the difference between MSVC and the other 2 is just crazy.
- MauranKilom 8y agoIt's crazy because the knob for "how much debugging do I want to do" (aka iterator debug level) has been left untouched. Do you want the most rigorous checks (the default)? Don't do anything. Just want to avoid "variable optimized out" while debugging? Turn iterator debug level to "none".
- puetzk 8y agoIt's because it's trivial to improve. The reason the MSVC STL is so slow in debug mode, is because it's really debug; they pull out all the stops to bounds-check every dereference, keep a generational count to detect iterators that could potentially have been invalidated by an operation that moved elements or reallocates the container (and do so whether or not the potential resize actually happened), etc. This adds a ton of code to the iterator paths, and (due to that code size) loses most inlining opportunities. If you want it to be as fast as other implementations, ask it to do less of this. They provide [_ITERATOR_DEBUG_LEVEL](https://docs.microsoft.com/en-us/cpp/standard-library/iterator-debug-level?view=vs-2017 https://docs.microsoft.com/en-us/cpp/standard-library/iterat...) for exactly that purpose. I think their choice of default (Debug means you want it to catch as many bugs as it can, and Release means it should be fast) is appropriate. If they hadn't provided the knob to adjust this I'd agree they had a problem for some use-cases, but if you want the faster-but-less-thorough mode you just have to ask for it.
- entelechy 8y ago> Let’s try to replace the only STL component we’re still using, std::vector, with a really simple dynamic array - we don’t need resize or push_back in our code, all arrays are initialized with the right size. Given those requirements, a custom allocator would speed things up. A custom allocator could allocate even on the stack (small buffer optimisation) if possible or use a memory pool. Embracing C++ features or reading the docs often gives you more than going back to C or reinventing the wheel. However I think this demonstrates one of the many lurking problems of C++: It does not enforce the right way of doing things nor makes it easy. Instead it makes the right solution awkward or cumbersome to write.
- jcelerier 8y ago> It does not enforce the right way of doing things nor makes it easy. but what the author is doing here, while being "the right thing" for his own project with specific performance requirements, is absolutely not the right thing to do for the average project that has a few std::vector of 15 widgets, a dozen strings and six and a half pixmaps, or does the occasional web request.
- jblow 8y agoWhy not? How do you know?
- gmueckl 8y agoAs already said, it depends on the type of project. Sometimes performance is key, sometimes implementation time is. It is the old tradeoff between engineering effort spent and the value gained.
- jcelerier 8y agoI know by shipping projects where the -O0 -fsanitize=address performance was enough for the client.
- Asooka 8y agoThat average project would be written in JavaScript, not C++.
- lultimouomo 8y agoSo if you get rid of bounds check, reimplement your own feature-limited vector and hash table and change the algorithm moving from a general purpose exact sort to an inexact, domain-specific one, you can improve your run time from 572ms to 320ms. What does this have to do with C++?
- CoolGuySteve 8y agoYa after changing the hash table and sort algorithms, the rest of the improvements are fairly minor (other than MSVC’s debug performance). This is more like “is C++’s STL fast for me”.
- lultimouomo 8y agoFor me the proper title of the article would be "std::unordered_set is slow and MSVC debug runtime is even slower", but I guess that wouldn't generate as many views as pretty much everyone knows it.
- throwawaymath 8y agoI interpret the title to mean, "Is standard usage of C++ language features and the STL competitive with C?". The author is basically showing how you improve performance by selectively tearing out more and more features C++ hands you out of the box. This is characteristic of writing C++ in a more C-like style. In that sense I think the author is correct. However I'd also say this is something already well known by most C and, particularly, C++ developers. Yes, you can get better performance by reimplementing much of what C++ gives you, and that is typical of writing C code instead of C++. But there's a fair bit of productivity trade off in doing so.
- thestoicattack 8y agoThe most surprising part of this to me was the speedup from replacing std::vector. They write, "We don’t need resize or push_back in our code, all arrays are initialized with the right size." But if you're not doing any resizing or anything, and you're reserving the right amount, std::vector basically is a thin wrapper over a dynamically-allocated raw array -- which is what they tout as their replacement. I guess I'm surprised that the overhead from default initialization was so large. With vector, it seems there are always tradeoffs: resize and you have some initialization overhead, but only reserve and you will have a bounds-check-maybe-realloc every time you need to push_back. Of course, since you reserved, the realloc never happens, but you still have to check each time. I wonder if something like generate_n or copy_n into a back_inserter can avoid the bounds checks?
- jhasse 8y ago> std::vector basically is a thin wrapper over a dynamically-allocated raw array Not exactly. A dynamically-allocated array doesn't necessarily save its own size (the system allocator can do optimizations), but std::vector does (you could call size() on it). So std::vector has to save this size somewhere, which takes space and time.
- thestoicattack 8y agoFor me, the overhead of keeping track of size and capacity is still pretty thin. It's not like it's a linked list or something. The overhead is thin but non-zero.
- FartyMcFarter 8y agoThat overhead can be thin or heavy, depending on the operation. Doing "push_back" requires checking if "size < capacity", so this operation has a lot of overhead even for std::vector instances that never reallocate storage.
- MauranKilom 8y ago
- rwmj 8y agoIt would be nice if he used a newer version of GCC and a newer base system in general, rather than the 2+ year old version of Ubuntu. I would also like to see % change as a column in those tables. Also I'd like to see other optimization levels, since at least GCC adds a lot more potentially slow (to compile) optimizations at -O3. Maybe -ffast-math. Static linking too could conceivably make a difference if he calls into external libraries frequently. And where does perf say the time is going? Maybe it's all lost in branches or cache misses so a different algorithm that branches less or uses denser data could help. How about vectorization? I've now looked at the code and it's all vectors, arrays and matrices so surely this is an application for AVX, maybe even a GPU. Anyway I think what's clear is this doesn't have much to do with C vs C++.
- Laakeri 8y agoNot a big surprise that hand implemented data structures with domain specific optimizations are faster than generic standard library implementations.
- WC3w6pXxgGd 8y agoExactly. This is the best comment.
- whathappeneda 8y agoHacker news try all its best to help Mozilla to slander their competitor’s product, policy, and even their development tools. I have to say the world is controlled by Big consortia.
- lbj 8y agoThere are some valid points on optimization here, but its always incredibly tricky to draw conclusions from a series of microbenchmarks.
- adgasf 8y agoThere isn't anything you can make the processor do in C that you cannot also make it do in C++ (if I am not mistaken!) Given that, C will only appear faster if you are not using C++ to its full potential. This might be because you don't know what you're doing or because the C++ language makes it unreasonably awkward. Most articles I have read like this are the former, unfortunately.
- faissaloo 8y agoNo, C will be faster because you have more control, in the same way that Assembly will always be faster than C.
- gmueckl 8y agoC++ allows you to write code that is equally expressive as C. It is no longer true that every valid pice of C is also valid C+++, but the deviations are purely minor syntactical differences. I know no C construct for which a C++ counterpart does not exist. Please enlighten me if you have an example.
- kccqzy 8y agoVLA.
- gmueckl 8y agoOkay, this is true. Using alloca directly is not a good alternative.
- mikulas_florek 8y agohttps://en.cppreference.com/w/c/language/restrict https://en.cppreference.com/w/c/language/restrict
- gmueckl 8y agoReplace it with __restrict and you're fine.
- chrisbennet 8y ago"removing algorithm includes sped up compilation." Why is he optimizing for compile times when his compile times are only 1/2 second the start out with? Interesting article though.
- twtw 8y ago1/2 second for this single file, not for the whole project. The bit you quoted about removing includes was about the changes throughout the project.
- jonstewart 8y agoThe virtues of the STL are that your code is, by default, typesafe and Not Slow. The article shows a few techniques for how to go from Not Slow to fast, mostly by fiddling with the data structures, and these jibe with my experience. In a nontrivial program, you will have your hot code and by creating custom data structures that make different trade offs than the STL (typically eschewing features like initialization, resize, etc), you can get some gain. But that effort isn’t worth it for the rest of your program, and yet the rest of your program is Not Slow. The algorithm discussed also involves matrix math. It’s not object oriented, it’s not data processing. So, there’s just naturally not that much difference between C and C++ in this domain.
- rusabd 8y agoThe headline is a click bait. Slightly better one would be "is idiomatic C++ fast"
- Ragnarork 8y ago> Release performance also slightly increased across the board - this is because for many of our arrays, the default initialization performed by std::vector’s constructor is redundant as we’re going to fill the array anyway. With std::vector, you can either resize a large array and then compute the items (which requires default-initializing every item redundantly), or reserve and push_back repeatedly (which requires a bit more code for adding each item, and this overhead can also add up). With a custom container it’s easy to have an option to skip initialization - in fact, in our replacement that’s the only option since it’s easy to memset the array manually if necessary. I have a hard time to get the rationale here. Is the author implying that using memset is inherently better than initializing an std::vector with the constructor that takes a count and a default value, or resetting it with the same variant of .assign()? Skimming through the code that's linked in the article, it looks like there's no use case where either the former or the latter wouldn't fit.
- kbsletten 8y agoI read it as the author almost never uses the uninitialized data and when they do need a zeroed array it's simple to do so why pay the cost to initialize the elements when you're about to set them anyway.
- cf141q5325 8y agoThere is also the approach to just copy objects existing objects to safe yourself the cost of running the constructor in order to shorten your startup time.
- kllrnohj 8y agoBut you can easily avoid that cost on std::vector, too. std::vector doesn't default-initialize unless you ask it to via resize(). reserve() doesn't initialize, though, it just allocates. Take this toy example: #include <vector> #include <cstdio> struct MyFoo { MyFoo() { printf("default ctor\n"); } MyFoo(int i) { printf("Initialized with %d\n", i); } MyFoo(const MyFoo& foo) {} }; int main() { std::vector<MyFoo> myvec; myvec.reserve(100); printf("resize(2)\n"); myvec.resize(2); printf("emplace_back()\n"); myvec.emplace_back(); printf("emplace_back(1)\n"); myvec.emplace_back(1); printf("push_back(MyFoo{1})\n"); myvec.push_back(MyFoo{2}); return 0; } It will print: resize(2) default ctor default ctor emplace_back() default ctor emplace_back(1) Initialized with 1 push_back(MyFoo{1}) Initialized with 2 Only the resize() call did a default initialization "silently", nothing else did.
- inetknght 8y agoI haven't (yet) read the article, but let me provide an anecdote to answer the question posed by the post title "Is C++ fast?": I write in C++. I had a coworker who wrote in Ruby. He wanted to learn C++ because C++ is "fast". So he wrote two identical programs: one in C++ and one in Ruby. Then, he ran both programs though `time` to determine how long each took. His C++ program took longer. Why was that? He didn't write his C++ program using any efficient algorithms whatsoever while his Ruby program was well-designed. He came to me to try to tell me that C++ wasn't as fast as Ruby. So I rewrote his C++ program. My C++ program then ran almost instantly while his Ruby program hadn't even begun reading or writing; the C++ program finished while the Ruby program was still initializing the Ruby runtime libraries. Is C++ fast? Yes, it can be. But it's only fast if the developer understands how to make it fast. Don't use C++ because it's "fast". Use C++ because you understand what its intended purpose is: to be close to the hardware.
- zymhan 8y agoMaybe you should've saved your breath and read the first paragraph to see that this is in fact about C++ vs C, not some other high level language.
- the_duke 8y agoMaybe you should have spent a bit more time thinking about the above comment. While using Ruby as an example, the point is not related to higher level languages. If you don't understand your tools, you can use them so inefficiently that other, supposedly worse tools are much faster.
- zymhan 8y agoThe last thing HN needs is discussions based around a comment from someone who didn't read the article. I don't care about your contextless philosophy.
- deleted 8y ago[deleted]
- 8y ago
- bluescarni 8y agoWhat a terrible article. It starts with the comparison of a general-purpose hash table with specifically-tailored one. Then it throws in a switch from a comparison-based sort to a radix-based one. Then, the stunning discovery that std::vector value-initialises its elements (fixable with 3 lines of a custom allocator, if you don't want that behaviour). In the middle, it preaches about portability while at the same time advocating the use of __builtin functions vs standard functions, and type-punning floats to int and back (undefined behaviour in C++). And all of this for a 572ms -> 320ms performance improvement?
- gpderetta 8y agoIn the grand tradition of HN i haven't read the article (but in my defense it is blocked by work's firewall). Still, if he claims that unordered_map has issues he is not alone. It is well known that the standard requirements preclude a more efficient implementation. And the issue with vector value initializing it's elements is also well known and programmers have been asking for a solution (more user friendly than a custom allocator) for a long time.
- jcelerier 8y agoI don't understand the problems. Want a better hash map ? Use whatever makes your day here : https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.... ; they are all header-only just like the standard library's. Want a vector that does not initialize its elements ? Ask and thou shalt receive: https://github.com/aguinet/pector https://github.com/aguinet/pector The whole point of C++ is to make it easy for people to write generic libraries beyond de standard base provided by the stdlib. So why won't you use those ?
- gpderetta 8y agoOh, I'm in violent agreement. The single biggest contribution of the STL is its set of concepts more than the actual implementations. If you do not like the specific tradeoffs for a container or algo implementation, you can swap it for a different implementation often without code changes. I.e. you do not need to remove abstraction, you just swap it for a different one. Still it is fair to critique the specific tradeoff done by the standard library.
- deleted 8y ago[deleted]
- hathym 8y agoyes
- heywire 8y agoThe most interesting part of this article to me was introducing me to Jonathan Blow and his Jai language. I've skimmed over some of his videos, and they look quite interesting. The compile time execution of any code supported by the language looks especially interesting.
- mikece 8y agoBetter question: Can C++ be fast? Yes. But depending on the skill -- or lack thereof -- of the developer it can be dreadfully slow. Besides, speed isn't the best metric for judging a language. In the late 90s there was a joke among Windows desktop developers: Which is faster, C++ or VB6? The answer was "VB6, by six months" and there was a ton of truth to that statement (and by the time the C++ version would have been done the parts of the VB desktop app that had performance issues could have been identified and the logic replaced with C++ COM Objects). The language is just a tool; USE the tool, don't be a tool.
- maccard 8y agoI wonder if his MSVC debug performance wors would have been solved by linking against the release CRT
- Const-me 8y agoI don’t think you can, it won’t build. There’s a supported way to speed up debug builds by a huge factor, for the price of less runtime checks. https://docs.microsoft.com/en-us/cpp/standard-library/iterator-debug-level?view=vs-2017 https://docs.microsoft.com/en-us/cpp/standard-library/iterat...
- maccard 8y agoSure it does, I use it every day!
- Const-me 8y agoYou can't link debug builds with release CRT. Won't link. Here's an example of missing symbols: __imp__invalid_parameter __imp__CrtDbgReport __imp__CrtDbgReportW You can workaround by undefining DEBUG, _DEBUG, defining NDEBUG, etc., to make headers think it's a release build, but then it won't be a debug build anymore.
- ncmncm 8y agoIf your goal is to be able to step it in the debugger and follow what is going on, then it is definitely still a debug build. If you want sanitization instead of stepping through the debugger, use that. The people who are best at debugging avoid debuggers entirely, and use logging statements inserted in release-built code, and little proofs. It is hard to persuade people who don't think that way, but it is the only route to mastery.
- Const-me 8y agoI'm very good at debugging and when I have choice I use all the tools I can get. Not only I use debuggers, I extend them to do what I need for particular project, e.g. autoexp.dat/natvis/custom plugins when coding C++ in VC. At the same time I also use logging a lot. These two things aren't always mutually exclusive. Only sometimes they are. Some platforms don't have good debuggers. For other stuff it's the opposite, e.g. you can't log from code running on GPU (very hard to accomplish, borderline impossible) but there're specialized GPU debuggers. I don't think people who use just logging, or just debuggers, are the best.
- pjungwir 8y ago> I made a meshopt_Allocator class that could allocate large blocks of typed data and remember the allocated pointer; at the end of the scope all allocated blocks would be deleted. I only write occasional C, and no C++ since college, but I think this Arena pattern is drastically underused. I'd love to see it in web frameworks, where every allocation inside a request comes from the same place, and at the end you just give it all back. It seems like you could even offer that in Ruby. Does Rocket use anything like this? It seems like the kind of thing Rust folks would take advantage of. Postgres memory allocation follows an Arena pattern, where you use palloc to grab memory, but you rarely have to free it, because it is all freed when the "context" completes. There are nested contexts for things like the connection, the transaction, the sql statement, a function call, etc., so as long as you're allocating from the right context, you can practically ignore cleaning up after yourself. (This is from the perspective of a frequent extension author but someone who has only barely contributed to the core project, so perhaps a more expert contributor can correct me and/or elaborate.) EDIT: Btw, I really enjoyed the article; thank you for sharing your experiences with us!
- gmueckl 8y agoI recently introduced similar patterns in a few things I work on. The speedup I got over having to manage tons of small allocations surprised me a little.
- barrkel 8y agoA nicely tuned generational GC should end up with first and second generations effectively as arena allocators that get recycled very cheaply. When the time comes around to collect the second generation, it should have almost no live data, as it will only be filled with whatever was in flight across all the times the first generation was collected. When I understood this, it was an aha! moment for GC languages in stateless server contexts. I could see that for a properly written server - ideally keeping any long-lived mutable memory like caches off-heap - GC overhead could be negligible.
- anarazel 8y agoI'm very doubtful that's really true for databases. A lot of memory freed this way is not short lived. E.g. the contexts for running [parts of] an analytics query can both be large and quite long lived. And it's crucial that the memory is available again the instant the last query [part] finished, otherwise it's quite realistic to run out of memory. And adding latency by running through the many GB of otherwise used memory in hypothetical older generations isn't acceptable either.
- Const-me 8y agoIndeed, standard library is often slow. Sometimes by orders of magnitude. Here’s an example where a fast linked list implementation (not mine, `CAtlList` from ATL) approaches or outperforms std::vector: https://github.com/Const-me/CollectionMicrobench https://github.com/Const-me/CollectionMicrobench Fortunately, in C++ we don’t pay for features we don’t use. If you want fast code, no need to go back to C, you only need better C++ containers.
- lordnacho 8y agoWith great power comes great responsibility. C++ is no different. If you want it to be blazing fast, you need to tell it exactly what you want. What data structure do you want, where do you want it, where should it ask for more memory if needed? If you don't do that, there's some sane defaults but the compiler doesn't know whether you are going to relocate the structure or how you plan to access it.
- kllrnohj 8y agoI'm confused by the premise: > dropping C++11 requirement allowed me to make sure anybody can compile the library on any platform, removing std::vector substantially improved performance of unoptimized builds What platform doesn't have C++11 support? And why is the performance of an unoptimized build interesting? -O2 is basically a standard compiler flag...?
- jlarocco 8y agoInteresting, but not necessarily surprising. It's pretty well known that the STL favors generality and interface consistency over speed. In any C++ conference talk about performance the speaker's almost guaranteed to mention that their project uses their own optimized library instead of the STL.
- MauranKilom 8y agoMain criticisms from my side (that have not been discussed to death already): - Why O2? Why not O3? You're tuning your code for maximum performance and then leave the optimizer halfway? That'll of course disadvantage code with more abstractions. - Compile times are improved by dropping STL headers - that's why precompiled headers exist. Having that factored into the comparison would indeed be interesting, sadly this was not done. - MSVC is slow in debug mode because it's doing every debug check it can. If that's not what you want, you can just change the iterator debug level. I was hoping the author would figure as much after finding the gcc/clang switches (and complaining that they were off by default - very ironic) with note 6 but no dice. - Regarding the choice of title: Almost all performance improvements were of algorithmic/data structure nature. That's completely natural for the level of performance tuning here. And I wouldn't really care if those specialized implementations are in C++ or C, do what you want. Just don't implement them in C and then draw conclusions from comparing them against the C++ STL under the flag of "Is C++ fast?". I think a better title for the article would've been "Are defaults fast?".
- deleted 8y ago[deleted]
- wmu 8y agoThere was an article on HN, where the author stated that they didn't like to write own algorithms "like in '90". In this context, the Arseny's article should be titled: "How to be old-school and tailor algorithms & data structures to your needs". I have this haunting feeling that people nowadays are afraid of "reinventing wheel" even though it'd pay off. A recurring argument is that the standard library or boost or something available on github is better than a custom solution. But as we see, there are cases where saying "nope, I'll do better job myself" has sense.
- londons_explore 8y agoI don't see why anyone considers compilation time. Your code will be compiled only once for your customers, yet run millions of times. Clearly runtime is millions of times more important than compilation time. If compilation is taking too long, start compiling in the cloud.