4,224 karma · joined May 21, 2015
What exactly does that roundtrip latency number measure (especially your 1us)? Does zero copy imply mapping pages between processes? Is there an async kernel component involved (like I would infer from "io_uring") or just two user space processes mapping pages?
I'm still not sure that it brings a lot to the table for ordinary application development.
So such a strong "at the very least" is misapplied. All this template crap, I've done it before. All but the thinnest template abstraction layers typically end up in the garbage can after trying to use them for anything serious.
Compare to <iostream>, which is stateful and slow.
There's also std::format which might be safe and flexible and have some of the advantages of printf. But I can't use it at any of the places I'm working since it's C++20. It probably also uses a lot of template and constexpr madness, so I assume it's going to be leading to longer compilation times and hard to debug problems.
There may be other situations where you have an object in a specific state that makes it effectively owned by a thread, which might make it possible to forgo locking it. These are all very ad-hoc situations, most of them would surely be very hard to model using the borrow checker, and avoiding a lock would most likely not be worth the hassle anyway.
Not sure how this can help me reduce complexity or improve performance of my software.
I keep very little state on the stack -- mostly implicit stuff like mutex lock / mutex unlock. By "state" I mean object type things that get mutated or that need cleanup. I always have a "database schema" of my global state in mind. I define lots of explicit struct types instead of hiding state as locals in functions. I've found this approach of minimizing local state to be the right pattern because it enables composability. I'm now free to factor functionality into separate functions. I can much more freely change and improve control flow. With this approach it's quite rare that I produce bugs while refactoring.
So yes, I have lots of locals but I share basically none of them with other threads. Also, I avoid writing any code that blocks on other threads (other than maybe locking a mutex), so there's another reason why I would not intentionally share a local with another thread. Anything that will be shared with another thread should be allocated on the heap just for the reason that we want to avoid blocking on other threads.
In that sense, the borrow checker is a tool that would allow me to write code more easily that I never wanted written in the first place.
I can see that there are some cases where you have heap-data that is only visible in the current thread, and the borrow checker might be able to see that. But I can imagine that there are at least as many cases where it would only get in the way and probably nudge me towards unnecessary ceremony, including run-time overhead.
Mutex is for where you have that ability, and ensures at runtime that accesses get serialized.
Exactly, that's what I do. Now notice that there is not a good incremental way to go from this object model stuff, to a toolbox that lets you build many more routines out of reusable primitives (implemented mostly as POD structs and freestanding functions). It becomes worse if one has also made a lot of use of these silly access protectors, public/protected/private.
Which means that, when you start out with the C++ object model, and evolve the codebase, you are invariably going to paint yourself into a corner, and proceeding requires first undoing all the fluffy object stuff.
That's not some furious ranting disconnected from reality on my end. I've seen it many times in practice. Show me any large complex serious systems codebase that makes extensive use of that silly implicit stuff, where all the important function calls are basically out of your control, hidden in the "whitespace"... no, that's not the way to write a complex program. It doesn't work.
For balance, I concede that even large programs can use some self-contained container types, like std::vector, or even custom built ones. That's where the object model still works -- small, isolated stuff. However, as you scale, you tend to not include many isolated types. You program probably needs a good grip of memory allocation for example.
The problem with the object concept is that there are many ways to partition all the data in a program into distinct objects. But types and object models need you to decide on one way of looking at the world, and it wants you to see the world like that throughout the codebase.
Taking copying as a specific example, there may well be more than one useful way of copying stuff. The best way to achieve what you need depends not only on functional factors but also non-functional factors (such as performance and concurrency for example). The more serious and involved a program gets, the more apparent it becomes that there is not one way to copy. And the harder and more arbitrary it becomes to single out one way of copying that should be "blessed" with a special syntax.
It's an arbitrary choice to single out a blessed way to copy, and that is also a choice against a lot of other useful ways to copy stuff. Having one blessed way makes a few things easier, but makes everything more complicated. There is now one way to copy that gets done quite implicitly using syntax, which draws on a mountain of special C++ semantics, and there are other ways that must be done using regular function calls. The end result is something that is more complex than just always making regular freestanding functions that do the thing that you want done.
(What copy routine will be chosen implicitly by syntax/semantics is mostly predicated on types, so you can always opt to add wrapper types to accomodate other ways of doing things. Now try wrapping the members inside the thing that you want copied, working against all the implicit semantics... that must be about the point where the boilerplate becomes unbearable, where C++ gets much more verbose and unreadable than just straightforward C).
The downside of always using function calls is that you can't profit from all the built-in implicit semantics stuff, but as said there is always a theshold of program complexity beyond which that stops being useful because there's much more stuff that the program needs to do that cannot profit from it.
But when you try to use all these funny features you're enumerating there for something serious, it will invariably end up in an overcomplicated slow compiling morass. Even just trying to make the types click for inserting something into a non-trivial templatized hashmap becomes a tedious act, and the IDE cannot help anymore either.
(Last issue I had was with catching some exception just to ignore it. Turned out catch(std::bad_alloc) doesn't work, you need write catch (std::bad_alloc&).)
I prefer writing simple C-style C++ where I write whole subsystems from scratch, and I can be very clear about the semantics from the start, design in what matters, and leave out what doesn't. Adding all the built-in object semantics baggage is too much overhead.
Not at all. Just general smaller projects to hopefully grow and be able to do better work in the future. Desktop GUI, rendering, networking, ...
Work has grounded me in such a way that in hindsight I regret staying around uni as long as I did instead of entering the workforce as early as possible. While I was less than a brilliant student, I wouldn't go as far as saying that uni was a waste for me. There are some things that I might never have learned otherwise. But I would love to know the other paths I could have taken in my profession.
It's still logically the same thing with these optimizations, obviously -- since they aren't supposed to change the logic.
I'm very doubtful that multi-threading can be abstracted behind a library. Simple message passing can cover a lot of use cases but not everything by far. I've also seen the Javascript model work fine, but it's not real multi-threading (no parallelism). As to async frameworks, they too are restrictive and they come with a lot of complexity.
I can see how college can serve to lose one's arrogance, but why couldn't that happen while doing real work? (in general -- don't know about Palantir)
Btw. kindly look at the other issue that I overconfidently waved away as "probably a false positive"?
The C programmers I know are certainly not deluded or overconfident. They don't even think "their" language is a perfect one, or even a very good one. They just avoid black-and-white thinking. They take a practical approach about memory issues, seeing them more like any other kind of bug. It's a different aesthetics than you would maybe see from many Rust folks. They want to be productive and want to be in control and want to understand what their code does. In turn, they accept that in some cases, bugs (possibly memory bugs) creep in, some of which could go unnoticed for some time. They tend to not see that as a huge issue, at least in general, because an issue that has gone unnoticed (or didn't manifest) is often less of a problem than one that is immediately obvious. (In case of data corruption, it _can_ be a huge issue, and you have to add safeguards to prevent it, and have to be accepting some residual risk).
They understand that everything is a trade off and that with experience and practice, good architecture, good tooling etc. you can prevent many bugs early, and detect them early. They have tried many approaches to prevent bugs, including fancy languages and constructs, and have concluded that in many cases, perfect safety is not possible, in particular it's not possible without seriously hurting other requirements, such as productivity.
As to valgrind, I can say that it was a bit of a mixed bag for me. It did help me finding bugs a number of times, but I also had to configure it a bit because it was producing a lot of noise for some external libraries (such as libc). I don't really understand the underlying issues.
With a library that encapsulates a low number of patterns (like message passing) you'll be very limited. If you never start learning about lower level multi-threading issues you'll never learn it. And it's not _that_ hard.
I'm not writing multi threaded every day (by far), but often enough that I can write useful things (using shared memory, atomics, mutexes, condition variables, etc). And I'm looking forward to learn more, better understand various issues, learn new patterns.
With a little bit of experience and a bit of care, multithreading isn't _that_ hard. You just need to design for it. You can reduce the number of critical pieces.
I've looked at them -- not all of them but all that I've looked at were the same kind recommending memcpy_s instead of memcpy, and were ridiculously easily to classify as false positives. So yes.
> How come "which isn't that easily to provide in compiler explorer, " suddenly becomes me telling that was impossible?
You claimed it wasn't easy but it is easy. VERY easy. It's one flag to disprove your point. Be honest.
> The first concern is that some parts of the industry keep reaching to C when there are better alternatives, even the language authors have moved on to creating better languages.
Many still enjoy it, are productive, and are creating infrastructure for billions of people to use. Let's keep things in relation.