> In my experience simple blocking code is incredibly misunderstood.
I don't think so. You can't even implement a full-duplex protocol properly with sync IO since reading and writing can't happen at the same time on the same thread. Splitting them to different threads also won't help since accessing shared state requires synchronization with e.g. mutex that kills again simultaneous reading and writing.
Only in poorly designed code you have problems like this where Async Await goes viral. It can be avoided by spiting libraries into an IO part that uses Async/Await and a protocol part that is sans-io. Using async code to access data that is already in the application memory is inefficient and should be implemented synchronously with regular functions instead of asynchronously.
This seems a no true Scotsman argument. In actual real applications it is not always possible to split IO from non I/O parts and the virality of async prevents composition and encapsulation without significant refactoring.
> In actual real applications it is not always possible
This depends on what are your expectation. IO operations must suspend to wait for data read and writes, therefore it is not possible to avoid Async/Await. In other cases you might have multiple tasks depending on a specific IO operation, for example, one connection to a database that is used by multiple HTTP sessions, here it is also not possible to avoid Async/Await because those sessions are bound to an IO operation.
The real problem however comes when tasks that can complete synchrously are implemented with an asynchronous interface, for example
This is a poor design because a socket read can pull multiple messages from the kernel buffers into user space and there should be a way to consumed them without Await. Many libraries however don't for watch this problem and that results in the everything is async madness.
Here is a low hanging fruit for governments around the world fighting low birth rates. Allow your people to work from home and they will start having more babies, simplle as that. I am my wife had to switch jobs just so we could work more from home, an unnecessary annoyance.
It's unclear to me what you're implying, but kills would always need to be returned to the group. For large animals, they would have done some degree of butchering and then returned the meat as manpower allowed. For instance elk, one of the examples they mentioned, can yield 300+ pounds of premium meat. Since our ancestors were probably eating/drinking parts we might discard, you can even substantially bump that figure up. That's going to feed an entire group of people for quite a long time.
For directions there's a million tricks to orientate yourself in the wild that they would certainly have been aware of. For a relevant one in contemporary times, the claim that moss only grows on the e.g. north side of trees is mostly true. A more accurate description would be that moss likes to avoid the sun and so tends grows to grow on the side of trees least exposed to the sun, which in the northern hemisphere tends to be the north side. It's going to vary by location/environment/etc but they would have known these things (and countless others) and so the environment itself would have been a living compass.
Apparently there's one tribe that doesn't even have a term for left/right/etc but instead uses solely cardinal directions. No idea if this is another bbc style claim or actually true, but it's at least completely viable.
There are countless means of preserving meat such that it can last months or even years - smoking, salting, freezing (naturally), the million ways of fermenting things - such as through burying, and so on endlessly.
They could chase it in circles. Prey typically escapes pretty predictably (away from you), so you can chase from side to force some direction. Also if you have group, you can use everyone in group in loose circle.
Group can walk to the site. Butchering and preserving something like horse, buffalo or elephant with stone tools, is hard work, and takes a lot of work from everyone. Hunting is just a first step!
It was not like today, when "rest of the groups" sits at home all day, and expects everything on silver platter!
So why rotten meat smells terrible and a steak delicious? Why would we evolve that perception of something that nutritious and which could prevent me from starving. Why my nose keeps telling me to keep away from rotten meant?
IIUC, the digestive system (and the gut bacteria in it) provide feedback to your taste buds over time.
If the appropriate "paleo diet" bacteria settled in (e.g. using the gradual method to build tolerance), your nose would probably change its mind soon enough.
There is an interesting related point in that cruciferous vegetables and allium roots synthesize specific chemicals which have been correlated with healthy effects. Many of these chemicals have significant amounts of sulfur and tend to smell quite strong not necessarily in a pleasant way. Olfactory associations seem to be complex.
I haven't created Redis itself but a Redis client library. It all started with a chat server I wanted to write on top of Boost.Beast (and by consequence Boost.Asio) and at the time none of the clients available would fill my needs and wishes
- Be asynchronous and based on Boost.Asio.
- Perform automatic command pipelining [1]. All C++ libraries I looked at the time would open new connections for that, which results in unacceptable performance losses.
- Parse Redis responses directly in their final data structures avoiding extra copies. This is useful for example to store json strings in Redis and read them back efficiently.
With time I built more performance features
- Full duplex communication.
- Support for RESP3 and server pushes on the same connection that is being used for request/response.
- Event demultiplexing: It can server thousands of requests (e.g. websocket sessions) on a single connection to Redis with back pressure. This is important to keep the number of connections to Redis low and avoid latency introduced by countermeasures like [3].
This client was proposed and accepted in Boost (but not yet integrated), interested readers can find links to the review here [2].
> But as more and more engineers joined us, some shortcomings
> of C++ came to bite us: unreadable coding style, memory leak,
> segmentation fault, and more.
* unreadable coding style: This is not a C++ problem.
* memory leak: Memory leak is an old C++ problem, since C++11 there is no reason for not using smart pointers.
The only point that could be attributed to C++ is the segfaults perhaps, due to its lack of safety. But your other two points made me skeptical about Rust bringing you much benefit. I am specially concerned about throwing away months worth of code. Rewriting everything from scratch is what newbies love to do and will most likely get you in the same mess you were in. Try to learn with the error of others [1]. And btw, the problem is usually who writes the code and not which language is used.
I used to code primarily in C++ around 12 years ago, then moved over to Python. I've now started with Rust and I really love it. I find myself constantly fighting with the compiler, it telling me what it won't allow me to do.
In C++, threading was my only way to execute parallel tasks, Python 3 showed me a about coroutines, which I've really started to love.
So in Rust I'm experimenting a lot with Tokio right now, and I'm trying to duplicate what I've made in Python. I often think about how good this compiler is that it won't let me do things which I clearly would have done in C++ either to cut corners or, more importantly, out of lack of understanding the consequences.
I was creating this WebSocket server which would serve as a bridge to a MQTT broker, all in async code, all in one big file. I was constantly fighting with the compiler about reusing variables or handing them over to tasks.
When I then attempted to refactor the code by splitting it into several files, the core of the server in main.rs, some WebSocket-related stuff in websocket.rs, all WebSocket-stuff related to the communication with the clients into websocket_handler.rs and all MQTT stuff in mqtt.rs, all my fighting against the compiler fell together. In my inner eye I could see how the variables were handed over to which file during execution, and that this was the reason why I was no longer allowed to use them in the previous file. So much logic, so clear, and the compiler forced me to do it that way.
Then there's the ease of integrating external libraries, which, at least in Windows back then used to be non-trivial in C++.
Cloudflare Workers (serverless) + WebSockets is the lingua franca of the internet now. Using languages with built-in concurrent stream processing features is I feel key to moving at internet speeds ;)
Those are both mostly niche technologies, they're really nowhere near "the lingua franca of the Internet". In fact, "serverless" may well be past its peak, with many having tried it in AWS or Azure a few years ago, and then moving on to Kubernetes to avoid the extreme vendor lock-in. And while there are "serverless" frameworks for K8S, they are not as popular as more traditional deployments.
I'm quite nooby in both Rust and C++, but for the little interactions I had, I felt like Rust had a much clearer path forward than C++ when I was in doubt, and "how to write in good style" was more obvious. This leads me to the assumption that building a Rust culture is probably easier than building a C++ culture, because it is easier to get junior developers and those coming from other languages up to speed.
Yep. The one big beginner “mistake” I see people make in rust is overusing Box / String / Vec. Rust code that allocates everywhere can be even slower than javascript. The reason? Malloc is slower than you think. Slower than short allocations in V8 or Go. If you want performance, make friends with &str, &[], <I: Iterator<…>>, bumpalo, SmartString and SmallVec. (Or similar crates). Removing allocations from the hot path can improve performance by 10-100x.
That said, most code isn’t a hot path. Performance doesn’t matter in most programs. And if you’re a veteran C++ programmer, none of this will come as a surprise.
I slightly disagree. Using heap allocated types is perfectly fine. The biggest thing I have to keep reminding myself coming from higher level languages is to re-use data structures, and to architect things in a way that this is possible.
Allocating a new String/Vec every single time you do something is killer for performance, but if you do it once up front then clear the data structure for the next use it should be performant enough.
That's funny. I've been going mostly the other direction. I'm avoiding mutable structures whenever possible. I have a much easier time reasoning about stuff when I know that things aren't going to change mid-life.
That is certainly true, but if you are talking about things that are too big to stack allocate, the cognitive overhead of reusing heap allocated items is the price for getting back the throughput overhead of memory allocation.
Does anyone know of any guide/tutorials/books about how to avoid common Rust performance issues. Like real world examples, "instead of doing X, do Y". I am maybe beginner to intermediate in Rust, but I think I'm still very susceptible to those things.
I've observed some level premature optimization in the Rust community, surely coming from being performance oriented.
I received a review comment some time ago about changing a line of code in order to spare one allocation, for a call that was executed _once_ in the whole lifetime of a program. That was not the only time I've observed this attitude.
I think starting with being handy with profiling and only after measurement, thinking about solutions, rather than starting with the idea that certain patterns are harmful per se.
Same here. Clippy and rust compiler made me switch to Rust from c++. Now after experiencing cargo, I find python to be insufferable (tooling, not the language).
> since C++11 there is no reason for not using smart pointers
There are many reasons for not using smart pointers, first amongst them for me being performance.
Smart pointers do allow you to remove a large class of memory bugs from your code (albeit not memory leaks) in the same way that Rust's safe references do. However C++ smart pointers impose a run time performance penalty on your code, whereas in Rust the checks happen at compile time leading to better runtime performance.
> However C++ smart pointers impose a run time performance penalty on your code.
i'm not sure if this is the case. unique_ptr doesn't really do much other than use the type system to ensure that only one instance of the pointed-to value exists at once. as far as i understand, it doesn't strictly do anything at runtime.
For unique_ptr, in a release build, no: but in a debug build there's overhead, and more to the point, stepping into the deference in gdb/lldb at least in my experience requires stepping through the internals, to the point some of the code we use with them is #ifdeffed hackily to use raw ptrs in some cases to avoid this annoyance.
For shared_ptr, the atomic ref counting can cause surprising overhead due to contention in multi-threaded scenarios, even if just accessing the pointer, depending on how the shared_ptr is passed through functions...
There are settings you can place in .gdbinit to avoid stepping in to internals of anything you don’t want to step into, be it std library classes/functions or your own code.
> There are settings you can place in .gdbinit to avoid stepping in to internals of anything you don’t want to step into, be it std library classes/functions or your own code.
This is exactly what I need. For both, C++ and Rust smart pointers.
Can you share any more info on how to set this up?
> unqie_ptr have overhead. It need to run its dtor etc
Technically it is correct, it causes an extremly light overhead due to the inability of the compiler to optimise a part of it.
In practice, even in HPC, I never notice it to have an impact ever.
And as an advise for any new C++ programmers: Please JUST use unique_ptr, everywhere, all the time...
Trying to manage the lifetime manually with new/delete in modern C++ is not worth the cost in 99.99% of the case.
If the impact of unique_ptr is noticeable in your program, you are very likely in a realm where you should not even doing memory allocation anyway.
You're misinformed. Overhead is not about the running the dtor, unless you're willing to leak the memory in your code regardless of the smart ptr usage or not.
What the "overhead" of unique_ptr is usually attached to is the inability for a compiler to pass the unique_ptr as a value through a register but will instead have to use the stack (memory). And that even doesn't apply in the general case but _only_ for unique_ptrs holding an object with non-trivial copy-constructors or non-trivial destructors. This is due to the platform ABI and not the C++ compiler limitation.
Anyway, calling that an overhead is a far stretch and almost purely theoretical unless someone is able to measure the negative effect of such code transformation in the real-world codebase. And I say this as not particularly heavy user of smart pointers.
Also, many other codegen transformations will not fit into a very limited amount of ABI registers so should we argue about not using those as well?
In my opinion, this was only a "campaign" of Google trying to use a unique_ptr as a leverage to persuade the committee to accept their break-the-world ABI suggestion. We know how it all went.
Yes. You can provide a stubbed out deleter or a stack based allocator. But you're then responsible for making sure the unique_ptr doesn't outlive that stack memory. Though that's true for any reference to stack memory.
What's the overhead of an unique_ptr vs the equivalent rust pointer (yes yes, I know that the Itanium ABI has non optimal calling convention for unique ptrs, but I would be surprised if you can measure it)?
Aside from you being completely uninformed because free(nullptr) or, for that matter, delete nullptr does absolutely nothing and is well defined operation so you don't need to check for that condition in the first place.
But even if you had to, what implications would it have, if you care to explain?
Extra branch which is going to be taken 99.99999% of the time is not going to present any runtime overhead. CPU BP unit handles it for us.
That said, if this really had been an overhead, virtually every language out there would suffer from it, including Rust. Every language out there at some point needs to call into the libc.
While I agree with your point overall, it's important to note that libc is definitely not a must on some platforms. On Linux in particular, the Kernel user-space ABI is stable, so you can write your own system calls in whatever language you want.
Also, even if you chose to use some libc for system calls, there is no reason to use malloc()/free(). You only need mmap() or similar, and then your own language runtime can write their own user-space memory manager.
As a side-note, on Windows, libc isn't even a system library. The official way to execute system calls is using win32, and things like malloc()/free() are simply wrappers around HeapAlloc() and HeapFree().
Yes, you can write your own runtime ... which will have to rely on the OS internals to do any meaningful work, and in case of memory allocation or deallocation it will have to rely on brk/mmap and then you will be back to square one.
Overhead is almost purely theoretical and it cannot be avoided and is not anything common or specific to unique_ptr's.
Not miri. I mean the actual ASAN, UBSAN, TSAN, MSAN and LSAN sanitizers that are also commonly used in C and C++ codebases. Descendant is the same: LLVM [1]
> Can you point me to any significant primarily rust codebase that runs sanitizers? I've never heard of that
Not sure what do you mean by that, surely there are. At least the ones who are serious about the development. Sanitizers are part of official documentation in Rust [2].
The compiler is happy to compile without sanitizers enabled. Why should it require to implement a null check when deallocating when it doesn't when dereferencing?
The argument for Rust is rarely that C++ cannot do the same task, it's that Rust provides better guardrails.
Unreadable code is not a C++ specific problem, but does Rust make it easier to write readable code? Probably. Same for memory leaks and other similar problems.
Non-trivial Rust code can be just as unreadable as code one would write in any other language. In fact, some of Rust's features (e.g. lifetime identifiers, functional-ish constructs that people use to create huge call chains with closures everywhere) arguably make it easier to write unreadable code than one might find in other languages.
Some features do both: make it easier to write elegant code, while also making it easier to write unreadable code. Introducing such features is a trade-off and I think they generally choose wisely.
> since C++11 there is no reason for not using smart pointers.
Smart pointers lure you into a dark alley where each tiny object lives in its own tiny heap allocation, and before you know it you have so much memory management sprinkled decentralized all over your code base that the memory management overhead becomes a performance problem, but at the same time it's too late to do anything about it because it would mean rewriting everything (this is then usually when the desperate search for a silver bullet like a "high performance memory allocator" starts).
(not that Rust is necessarily better in that regard when people start to work around borrow checker restrictions with Box and Rc...)
I'm sure it is the same for C++, but in my Rust code things that are put in a Box/Arc etc. are carefully considered and typically my top level business objects. I even wrote my own inline String struct a while back (flexstr) to ensure I don't do allocations for strings smaller than 22 bytes. I don't use allocations "all over the place" without thought and like any language feature, design and placement is important.
That's much more thought put into memory management than what I was used to in the C++ world from 5..15 years ago where many people seemed to have the impression that allocating and freeing memory or the overhead for refcounting is free.
If this is starting to change then it's a good thing.
It's not about what you use to point to the thing. It's about how you allocate the thing in the first place. Smart pointers are like a free pass to do things without consideration: they allow you to make progress without caring how your things are allocated, because they ease the pain that results from just allocating stuff on the global heap, lacking a systematic approach.
Note that while smart pointers ease pain in the short term, there are subtle complications that arise from the constraints that they introduce. One is performance, in some cases the cost of generalized memory management can be too much. Memory fragmentation can be an issue too. RAII the mechanism is not free but requires compatible code and containers. I'm sure there are lots of projects that have broken under the added constraints introduce by such smart classes.
And while smart pointers are orthogonal to systematic memory management, if you get the management right the use of smart pointers can easily become a net negative.
Yes, but people need to be aware that "auto obj = make_unique..." or "auto obj = make_shared..." should be a very rare thing and not the norm (and I've seen plenty code bases like that). There needs to be a proper memory management strategy with the goal of minimizing heap allocation in random places in the code. E.g. automatic memory management doesn't resolve you from thinking just as much about memory management than doing it manually in the first place.
The idea that ref-counting is automatically "better" than a garbage collector is still pretty popular unfortunately (and that's just the tip of the iceberg).
A bump allocator + state of the art compacting GC is MUCH faster on a naïve basis which is why it is important to minimize number of allocations in non-GC langs and use stack based RAII when possible. When building things like trees in a non-GC language, arena allocation should be considered for max performance. This is just another low level vs high level trade off IMO.
> A bump allocator + state of the art compacting GC is MUCH faster
I can see it repeated very often, but can you point me to any evidence for such claim, based on modern low-pause GCs and real data (not simulation)?
I find low pause GCs have pretty substantial overhead and have way lower allocation throughput than old stop-the-world GCs. And the difference between manual memory allocation wasn't that big either (still within 2x).
> I am specially concerned about throwing away months worth of code. Rewriting everything from scratch is what newbies love to do and will most likely get you in the same mess you were in.
This was my first thought, but then I realised newbies would have started with Rust. :)
In the first few months of a big build a huge amount of time and effort goes into the foundation: design, POCs, setting up environments, CI, testing infrastructure…
My guess is that they weren’t very deep in production code, and the fact that they didn’t fall for the sunk cost fallacy speaks highly for them.
I've a feeling it's easier to write decent Rust than decent C++.
The post contains comments about how difficult they found it to keep coding style consistent and language feature use consistent. This suggests to me an inexperienced team without the skills to manage a project of the size and complexity they need.
Now whether with the right team they could produce a better solution in C++ or Rust, i've no idea, but if this is the team they have, then it sounds like Rust might be the right choice for them.
It’s good from a technical perspective but not a business perspective. Startups only get a limited amount of time/money investment in order to prove their profitability. “Writing a better version” can come later, when the startup isn’t trying to come up with a version in the first place.
The point of safer languages is that it can protect you from the person writing the code. Sometimes that person is someone else. Sometimes that person is you. But no matter who the person is they will eventually have a bad day and some of those bad days will not be caught by a C++ compiler and linter. So your last point is I think probably not correct. The truth is that you need both a competent coder and also a language that catches when you are a little off your game. Rust is one of the compilers that help to catch when you are off that game.
Your point that a rewrite is very risky is well made regardless though.
> * unreadable coding style: This is not a C++ problem.
I would argue that it is, after a few years of Rust. My style in C++ was formed by my exposure to other people's code. I guess this is true for most people.
Certainly, in C++, as well as in any language, Rust included, there are a myriad of ways to express yourself when solving a problem. Is my C++ coding style unreadable? I don't think so. But I used that language for 30+ years. Let's say I only used it for five years -- what then? I would have much less exposure to other people's code and hence to well written/readable code.
Rust has rustfmt (a code formatter) and clippy (a very opinionated linter). And they get used lots. Most Rust CI now includes `cargo fmt --check` and `cargo clippy` runs than need to come out clean.
This goes a long way in making code canonical. Clippy's amazing suggestions also made me a much better Rust developer in a much shorter time.
Are rustfmt & clippy part of "Rust the language"? No. But in a way they are since there is nothing else they compete with for opinion on how Rust code should be formatted and written.
When you onboard new people and they have adapted the pov that this makes lots of sense, it goes a long way in avoiding code being produced that is difficult to read for others.
And because of this, I do indeed think that it is a problem of a programming language, in 2023, if the default tooling does not include such utilities. And a strong incentive through the community, pretty much all the available learning materials and repos in the wild, encouraging their use.
Came to say this exactly. C++ encountered coding styles are often a hodgepodge of "personal preferences" and urban legends. Rust coding style is partly enforced by rustfmt and clippy. The former makes you not worry about mixing styles into a project (after 30 years doing C/C++ I love this), the latter has really good suggestions which makes you reflect on your code.
Using a code formatter in C++ (e.g. via githook or the like) seems still the exception in most commercial C++ codebases I got to look at, when consulting in recent years.
I.e. often there is a mix of styles, indentation/line wrap preferences and even the occasional tab in a codebase that otherwise uses spaces. Etc. etc.
And that's just the formatting part.
Linters seem to be mostly used manually by experienced developers but rarely are part of the commit pipeline or even if they are, their suggestions enforced.
Your mileage may vary. I mostly look at code from a very specific industry. But I can't help but notice these things now, because of my exposure to Rust.
As a long time c++ developer there's a big difference between when something shouldn't happen and when it can't happen. Rust makes certain classes of issues fundamentally impossible (ironically, leaks are possible in Rust), while in C++ it depends on how well you can enforce certain code guidelines on yourself and anyone that works on your same codebase. Also, you get like zero assurances that other people code has been written with the same quality standards as yours, as long as something is very easy to mess up, it will probably be inadvertantly messed up by someone.
> Memory leak is an old C++ problem, since C++11 there is no reason for not using smart pointers.
C++11 did not invent smart pointers, pretty much every C++ project had already been using them long before they were added to the standard. They help with leaks to some extent, but are not a panacea, I can do a memory leak with shared_ptr in just a few lines of code. I've seen this line of reasoning many times before and it needs to stop.
There was no way to implement a smart pointer in even a halfway usable and reliable way without move semantics, so C++11 is absolutely essential to use them well. Also I fully agree with the general sentiment. I have not produced a memory leak in many, many years in dozens of hobby projects or in a handful of professional C++ use. It's truly is a giant game changer. You can produce a leak with shared_ptr, but only if you do shit that looks dangerous and stinks to high heavens (a bit similar to the unsafe keyword).
Indeed, but to be fair, Rust's smart pointers can be used to create memory leaks just as easily as C++'s. (Overall I think Rust is a much better language than C++, though).
Tooling, existing ecosystem, built during 40 years of production deployment.
Rust is naturally safer than C++, however there are still many domains where a bit of yak shaving is required before actually coding what one cares about.
So it boils down to where to spend resources, and as note, I am definitly not a fan of Orthodox C++ ideas, rather having C++ improve its safety story as much as possible.
In Rust you can allocate small structs on the stack while being confident that the plain pointer passed down the stack will be valid throughout the function execution.
In C++, you need to use a plain pointer or a unique pointer. The former makes the function leak when passed a pointer to heap.
The latter requires a heap allocation and free for the struct contents.
You probably want to revisit your understanding because what you said doesn't make much sense. Allocating variables on stack and passing them over to other functions is perfectly fine. If you want them to live on a heap that's perfectly fine too.
Decent test coverage and ASAN should be all you need to never have a memory leak in C++. ASAN will even tell you what backtrace allocated the thing in the first place.
Human beings have been living in europe for over 100,000 years. White skin doesn't seem to be older than a 10,000 years (see the skin analysis of the first briton). I don't think it would have taken so much time to evolve white skin had it really had the importance that is being attributed to it nowadays. I am very skeptical about its importance on preventing not only covid but also many other deseases.
Anything that kills you mostly only after prime child-birthing years does not matter to evolution, and human behavior and diet has changed just a bit over the last 10000 years.
> Anything that kills you mostly only after prime child-birthing years does not matter to evolution
That's...very much not true. Any disease that doesn't effect the chances of children closely related to the exposed person being born and surviving to adulthood does not matter much to evolution, but killing the exposed person before or during prime childbearing years isn't the only way to effect that.
I don't think so. You can't even implement a full-duplex protocol properly with sync IO since reading and writing can't happen at the same time on the same thread. Splitting them to different threads also won't help since accessing shared state requires synchronization with e.g. mutex that kills again simultaneous reading and writing.