Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

They're simpler only if you could care less about performance. C and C++ are primarily used in situations where you do care about performance.


Memory safety and performance are not a contradiction, they are widely independant. Yes, some strategies like GC induce an overhead. But in the age of multiprocessor machines, this overhead gets very much reduced by parallel collection. And practice has shown, in programs with a lot of dynamic memory allocation, garbage collected languages often perform even better. There are different approaches, like memory safety by the compiler as Rust does it. And with "performance" in mind, you shouldn't forget, that most large C/C++ programs tend to use libraries to provide some amount of safety, but those libraries are of course adding some overhead too. Finally, the less time you spend on questions of memory management, the more time you have to write an actually fast program.


Sorry, this is hand wavy nonsense. One counter example, we switched from Cassandra (JVM) to Scylla (C++). It was a win in terms of both query latency and infrastructure costs as we required fewer machines to handle the same load.

As for having more time to write a fast program... that's funny. If you want a fast program on something JVM based you're pretty much going to be spending the majority of your time writing things in a way where the GC plays as little role as possible.


Sorry, this is hand wavy nonsense. One counter example, we switched from Cassandra (JVM) to Scylla (C++). It was a win in terms of both query latency and infrastructure costs as we required fewer machines to handle the same load.

Sorry, this is not hand wavy nonsense. And what you are providing is called annectodal evidence.

Also, your universe seems to consist only of the JVM as a memory-safe alternative to C++. Yes, there are a lot of bloated, badly performing programs implemented in Java. However this isn't a given. Yes, some design decisions for Java introduce the risk of bloat, but you can avoid them at much less effort (and risk) than memory corruptions and new features like the value classes are reducing the bloat quite a bit. But still, the JVM is extremely high performance, so for speed in surprisingly many cases, it often beats C++. Virtual method calls are just better optimizable at run time, the Java JIT creates excellent code and Java has some of the best garbage collectors, so at really dynamic memory loads, it beats any manual management by a wide marging.

And of course, there is a whole world beyond Java as alternatives. Rust has been explicitly designed to excel at tasks C++ traditionally shines for, while giving your full safety.


There is an open-source project by LMAX (a forex trading company) called Disruptor[1] that squeezes as much as possible out of the JVM. It's awesome. I ported it to C++ years ago when I wanted to learn about low-latency techniques. However, if you look at the code they need to actually break out of the JVM's safety net to get the performance they need[2]. I couldn't help but ask myself why they didn't just use C++, and when asked one of the devs did admit that their own C++ ports had an approx 10% performance increase (although this was ~7 years ago maybe)

Rust is certainly interesting and it's on my radar. I wonder though, when it comes to having it in use in anger if its guarantees turn out to be over sold, just like the JVM's safety claims were. Time will tell.

Edit: I tend to focus on comapring against the JVM because pretty much any framework you use on The Cloud is JVM based. I'm of the opinion that there are cost savings to be had if these were ported to more appropriate languages, hence the Cassandra vs Scylla comparison. The money saved was 'noticeable'.

[1] https://github.com/LMAX-Exchange/disruptor [2] http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot...


Which is why such projects use the JVM, they save money in developer salaries, developer pool, bug fixes due to security exploits, available set of tooling and libraries, while caring to hand optimize a tiny set of libraries for specialized use cases.

Java 15 just accepted the JEP for native memory management, yet another stepping stone for having value types support.

If the cadence continues, Java will eventually have all the features that it should have had in 1996, had Sun properly taken into consideration languages like Modula-3 and Eiffel.

Which you can get today in a language like Swift, C#, Nim or D, productivity of GC, type safe, while having the language features to do C++ like resource management.


There are notable examples where garbage collection has both better throughput and latency than manual memory collection: persistent data structures come to mind, because in the absence of garbage collection, you have giant awful cascades of reference count manipulation any time a structure would get freed.

You'll never actually see anyone tout the benefits of using GC for this, though, because the performance characteristics of persistent data structures are so horrendous compared to mutable ones, no one actually uses them in C++.


You can use GC to implement the persistent structure in C++/Rust just fine. But then you pay the GC cost for only that structure, not for all the other things.


Well yes, if you are comparing GC to refcounting then yes, gc wins.


I wouldn't be so sure. There is no easy answer. It typically wins in trivial benchmarks, where the total heap size is small. However once you have a big heap of other long lived stuff, there is a significant indirect cost caused by scanning the heap, memory barriers and evicting good data from caches. This cost is negligible only if your total heap size is much larger than the live memory set. Also modern manual memory allocators are not as slow as they used to be a few decades ago and they actually allocate in low tens of nanoseconds.


If your problem demands many heap allocations, GC wins also vs. manual memory management. Heap allocation in a generational GC is as fast as stack allocation, you don't get fragmented heaps.


It is nowhere near stack allocation, please stop this nonsense. There was a paper claiming that, but the requirement was to set heap size 7x bigger than needed.

Stack is also very hot in cache. Memory that GC is handing allocations from is not.

I recently ported some of Cassandra code from Java to stack-only Rust and I got ~25x performance improvement, most from avoiding heap allocations and GC.


That depends on the allocator used. The default libc one works but isn't great performance-wise. It is possible to intercept calls to malloc/free by using LD_PRELOAD on Linux. That will allow you to use allocators such as jemalloc or tcmalloc instead.

Of course, repeatedly allocating and freeing is poor for performance. Cache/pre-allocate when you can. This goes for managed languages too.


It's usually a trade-off since C++ may be more time consuming and therefore most costly to write. It's also a lot harder to get bug-free because of the manual memory management.

Both Java and C# may be somewhat slower, but the maintainability and freedom from memory management issues more than makes up for this.

Any engineer worth his salt will take this into account.


> Yes, some strategies like GC induce an overhead. But in the age of multiprocessor machines, this overhead gets very much reduced by parallel collection.

If you unlucky and given GC is not well optimized for given workload, then memory usage overhead can be huge. Just a few days ago had such problem with Go (HeapIdle grew until OOM).

Go GC is less mature than Java GC, but Java GC is not free too - sometimes you need to spend a lot of time time to tune GC settings or to optimize code to avoid GC problems.

In my case case it would be faster to use malloc/free than to spend time fighting with GC (looking for a workarounds).

On average GC saves development time and allows to avoid memory management bugs, but in some cases overhead is big and developers have to spend more time, not less.


I have not claimed that a GC is always faster. Indeed, having a GC enables some to write a totally bad performing program. However this doesn't contradict that fact, that in many cases a GC does not only not mean a slower program, but sometimes a faster program. Usually, you don't have to "fight" the GC. In situations, where manual management is vastly better, you do use object pools and preallocated arrays even in a language with a GC.


There are quite a few areas in software where determinism is a must, such as games, real-time and systems software.


Yes, for those there are real time GCs as well.

https://www.ptc.com/en/products/developer-tools/perc

https://www.aicas.com/cms/en/JamaicaVM

One of the Java vendors acquired by PTC, Aonix, used to sell real time Java implementations for military deployments, including weapons controls and targeting systems.

You don't want a couple of ms pause when playing with such "toys".


Sounds more like snake-oil to me.


Both C and C++ have zero built in knowledge about parallelism, so in a modern world where most things are parallel and asynchronous I don’t agree at all with that statement.


> Both C and C++ have zero built in knowledge about parallelism, so in a modern world where most things are parallel and asynchronous I don’t agree at all with that statement.

And ?

C (even Fortran) had threads and was used to create high performance program with high degree of parallelism before any "concurrent" modern language was even born.

You can not agree if you want. But fact are there, 98% of programs running on the biggest "parallel" machines nowadays (supercomputers) are C, C++ or Fortran.

You don't need to be "designed" concurrent to be efficient at it. The same way you do not need to be designed "Cloud-native (bullshit)" to run on a virtual machine.


I think that it sets a different mentality if it is part of tools you use, you solve problems differently. It is of course possible to write highly concurrent code in traditional sequential languages and your examples proves that, but supercomputers are a special case with budgets for that. I'm talking about the general case, and I think the post I replied to also assumed that.

We have to give programmers, with different backgrounds & training, tools to write high performant code in their everyday job. Many of the tools we use today are not designed for that. We are stuck in a mental model 50 years old that is no longer true.

Here are some interesting stackoverflow answers. You are of course free to dismiss these answers as anecdotal.

https://stackoverflow.com/a/14637833

https://stackoverflow.com/a/2799779


Both C and C++ have had a concurrent memory model and threads in the standard library for a decade now. And POSIX threads which are pretty much the same thing have been there for a quarter century on *NIX.


Sure, there are libraries for concurrency, but there are no language constructs like in other languages.


What do you mean by language constructs? As in, a `synchronized` keyword? Or are you thinking along the lines of `async` `await`?

If you mean the `synchronized` keyword, then correct, they don't have that. Most languages do not have that concept. C++ does have mutexes and has had them since C++11 (nearly a decade). C++ also has as part of the language spec the concept of threads, again, there since C++11.

If you meant co-routines, then C++ just added them with C++20.

Or do you mean something different like green threads (ala go)?

C++ certainly has concurrency constructs and has been expanding them since C++11.


Like Gos goroutines or Erlangs processes. And more of message passing. C++ has added many of these things later on in the standard library, bolted on afterwards.


If you think that using libraries to do things in C++ means they are "bolted on", then you don't understand C++.

The entire design of C++ is to enable efficient libraries for these kinds of things to be built. And it does, and has, and will continue to for decades more.


You forgot about OpenMP.


The real point of using C and (less so, but still) C++ is not to have the ultimate performance, but to have ultimate control over the program execution. Garbage collection and real-time code are not compatible, because GC introduces random, unexpected stalls in the program flow; even simple reference counting can be harmful, if the last reference get eliminated in the wrong time, and creates a cascade of deallocations.


Then using C and C++ is a mistake, because there isn't an ultimate control about how memory allocator interacts with the OS.

So much that in the 90's there were vendors that could make a business out of selling specialized memory allocators.


Really? What about an OS kernel code? How does "it's allcator" interacts "with the OS"? And what these allocators were written in?


Usually in a mix of Assembly and whatever is used in OS systems implementation language, not necessarily C.

https://inf.ethz.ch/personal/wirth/ProjectOberon/Sources/Ker...

The malloc() exposed in a ISO C standard library implementation isn't necessarily related to whatever means the OS does memory allocation.


Nowadays, back in the 80's and early 90's no sane developer would used them on 8 and 16 bit home platforms if they cared about performance.

What C and C++ have going for them is 40 years of investment in optimizing compilers, to detriment of other languages, and abuse of UB in optimizers.

Now with shared compiler backends becoming a mainstream thing across most OSes, that advantage is getting thinner.


They do well in those contexts. They’re used in other places too, but generally with poorer justification and results.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: