Live data from Hacker News

Modern C++ Won't Save Us (2019)

alexgaynor.net

251–260 of 266 posts

Re: Modern C++ Won't Save Us (2019)

#251

Earlier quoted context omitted.

> three issues: "How big is it", "Who owns it", and "Who locks it" The issue with this kind of thinking is the belief that there is a "it", not "they". Say, you are writing a HTTP server [1]. A beginner mindset (what seems to be demonstrated in this post) with start allocating left and right, for every HTTP header, for every piece of string, for every piece of metadata record, etc. Thus, "how big is it" become no big…

What asiachick wrote below. Muratori has demonstrated to me one time too many that he hasn't a got a clue about the things he's talking about. To be precise: He may or may not have a clue about some things he's talking about, but I don't know enough about them to form an opinion. But in more than one occasions he went on to talk (sometimes at great length) about things where I know he's mostly or completely wrong in…

> You can't criticize the design as being too complicated...

I hadn't finished reading the ETW rant; but I don't see anything wrong with it. All he seems to be saying is that for communicating with OS, ioctl-like all-in-one APIs are bad while epoll like APIs are better. Not really a controversial statement.

> After a couple of those I don't trust him

Who said anything about trust? I have made enough mistakes and learnt enough lessons, and it's nice to see other well-respected programmers (like Casey and Mike) come to the same conclusions like mine. To paraphrase your post: "You shouldn't repeat those mistakes but you're welcome to".

EDIT: Actually, after finishing reading the blog post, wow! I thought Linux's perf_event API was shit, but this ETW crap takes the cake. Well done, Microsoft; you had one job.

Re: Modern C++ Won't Save Us (2019)

#252
post #119

Earlier quoted context omitted.

Yes, C++ the good parts is known as C++ Core Guidlines, https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

This is great stuff. Thanks! I sympathize that compilers do not do "enough" of this for us. But there's really no substitute for a good code standard and developer culture focused on quality.

Unfortunely C++ inherited the perfomance focused culture from C and sees such kind of tooling as needless bloat, only required for developers that need an extra hand while coding.

https://isocpp.org/files/papers/CppDevSurvey-2021-04-summary...

If you check other well known survey among C++ community you will see similar results in static analysis tooling and coding standards adoption.

So while, contrary to the C community, WG 21 members do strive to push the code quality forward and publish tooling that helps to enforce it, the adoption could be much higher than it actually is on the field.

Re: Modern C++ Won't Save Us (2019)

#253

Earlier quoted context omitted.

I'm pretty confident Minecraft, Roblox, and Dreams are not following Casey's practices.

What makes you say that?

Minecraft was created in Java with ducktape and hacks and a lot of ugliness and perf characteristics which wouldn't fly past a pedantic programmer like Casey

Re: Modern C++ Won't Save Us (2019)

#254

Earlier quoted context omitted.

> three issues: "How big is it", "Who owns it", and "Who locks it" The issue with this kind of thinking is the belief that there is a "it", not "they". Say, you are writing a HTTP server [1]. A beginner mindset (what seems to be demonstrated in this post) with start allocating left and right, for every HTTP header, for every piece of string, for every piece of metadata record, etc. Thus, "how big is it" become no big…

> You allocate a memory arena when the HTTP request comes. From that point on, every "allocation" is equivalent to bumping a pointer in that arena. If the arena gets full, we allocate another and chain it to the previous one, as a linked list. Didn't you just describe the regular memory management as applied by OS in a process scope? At the completion of the process, all memory allocated by the process is reclaimed.…

Asking the OS for memory is a low-level operation, which means it is not portable and should be done by a library or framework. Also, since it's a syscall (involving a context switch), you don't want to do in performance-sensitive code.

Some applications might benefit from allocating huge pages. By default, the page size is something of the order of 4KB or 16KB. Large numbers of pages result in huge overhead. By allocating fewer, large pages on the order of MBs can help.

Re: Modern C++ Won't Save Us (2019)

#255
post #129

Here's the point of view from someone who has only written 1 piece of production software with both C++ and Rust. Trying to write C++ I was constantly fighting accidental memory copies. With Rust all of this was trivial and everything works as I expect. This is pretty much the reason I chose Rust over C++. As a beginner I have no idea how I would begin to elegantly write immutable data, parallel code. With Rust it's…

> the project folder takes multiple gigabytes even for small-ish projects

This is a workaround but you can specify a global directory for all those artifacts with the env var CARGO_TARGET. This will deduplicate common dependency versions and lets you stick it in a temp disk/exclude from backups.

Re: Modern C++ Won't Save Us (2019)

#256
post #24

My comment for too many years is that C/C++ fails to deal with three issues: "How big is it", "Who owns it", and "Who locks it". C++ has, with difficulty, made progress on "How big is it" through templates, but raw pointers keep leaking out. "Who owns it" has been tough to deal with, although "owned pointers" at least try. "Who locks it" has yet to be addressed at the language level, although at least there's now som…

> three issues: "How big is it", "Who owns it", and "Who locks it" The issue with this kind of thinking is the belief that there is a "it", not "they". Say, you are writing a HTTP server [1]. A beginner mindset (what seems to be demonstrated in this post) with start allocating left and right, for every HTTP header, for every piece of string, for every piece of metadata record, etc. Thus, "how big is it" become no big…

Another optimization would be to keep the request buffer around and use it as backing storage for all the strings objects that result. The weakness of this strategy is that it doesn't make sense to keep it around if you only need a small part of it. It's a common source of memory leaks in Java where `String.substring()` just creates a new object with different start and end indexes for the same backing buffer.

A possible disadvantage of arena allocators is that everything is now tied to the lifetime of the arena, even though it might not be required that long. You'd need to practice a strategy like subarenas, RAAI, garbage collection or reference counting within the request as well.

The occurrence and advantages of all these strategies unfortunately are heavily workload-dependent. Most strategies work well for 80% of the workload. Different workloads of course.

Re: Modern C++ Won't Save Us (2019)

#257
post #220

Earlier quoted context omitted.

Well if you have aesthetic objections to the way rust does it, I can't argue with you. Kotlin does it the way you like, though. Anyway, I started in on this thread because I was objecting to the claim that optional types always have overhead. They don't. That's all I wanted to show.

> Well if you have aesthetic objections to the way rust does it, I agree with GP that it is harder to maintain ("messy" they say) if there is more than one variable referring to the same value. It is not so subjective as you make it to be.

The idiomatic way to solve that in rust is to re-bind to the same variable name.

   if let Some(foo) = foo { /* ... * / }
That's possible because in Rust name shadowing

    let foo = grab_foo_bytes();
    let foo = parse_foo_bytes(foo);
makes the previous binding of the variable no longer namable and thus no longer accessible, but doesn't drop it (and trigger RAII destructors).

Now someone will probably come in and say "oh no, this isn't exactly like c, how will anyone ever understand it". To that I reply why is it that c users get to say "if you don't know how c works exactly you're holding it wrong" and then comment about other languages "I don't want to have to learn anything to hold it right".

Re: Modern C++ Won't Save Us (2019)

#258

Earlier quoted context omitted.

> three issues: "How big is it", "Who owns it", and "Who locks it" The issue with this kind of thinking is the belief that there is a "it", not "they". Say, you are writing a HTTP server [1]. A beginner mindset (what seems to be demonstrated in this post) with start allocating left and right, for every HTTP header, for every piece of string, for every piece of metadata record, etc. Thus, "how big is it" become no big…

> You allocate a memory arena when the HTTP request comes. From that point on, every "allocation" is equivalent to bumping a pointer in that arena. If the arena gets full, we allocate another and chain it to the previous one, as a linked list. Didn't you just describe the regular memory management as applied by OS in a process scope? At the completion of the process, all memory allocated by the process is reclaimed.…

Interestingly it's exactly what the PHP memory model does, each request is a shared nothing VM space, nothing survives the end of the request, even if allocated memory objects don't get free'd.

Re: Modern C++ Won't Save Us (2019)

#259

Earlier quoted context omitted.

> Well if you have aesthetic objections to the way rust does it, I agree with GP that it is harder to maintain ("messy" they say) if there is more than one variable referring to the same value. It is not so subjective as you make it to be.

The idiomatic way to solve that in rust is to re-bind to the same variable name. if let Some(foo) = foo { /* ... * / } That's possible because in Rust name shadowing let foo = grab_foo_bytes(); let foo = parse_foo_bytes(foo); makes the previous binding of the variable no longer namable and thus no longer accessible, but doesn't drop it (and trigger RAII destructors). Now someone will probably come in and say "oh no,…

  > The idiomatic way to solve that in rust is to re-bind to the same variable name.
OK, that's reasonable. Is the idiomatic way to use optionals to introduce a layer of nesting? I prefer keeping functions very "flat"-looking. It sounds like Rust's optionals will give people an excuse to create labyrinthine functions where I'm constantly scrolling around to remind myself of what level of nesting I'm at and whether I'm in a loop or not, etc.

As you say, you don't need a new block to shadow a previous var, so hopefully that style catches on.

  > "oh no, this isn't exactly like c, how will anyone ever understand it"
Not very persuasive, sure, but the network effect of the C/C++ culture (including its general syntax and imperative nature) is a strength in and of itself. New languages would do well to coddle the existing C++, Java et. al. users wherever it doesn't contradict the language's central mission.

Re: Modern C++ Won't Save Us (2019)

#260

Earlier quoted context omitted.

> Well if you have aesthetic objections to the way rust does it, I agree with GP that it is harder to maintain ("messy" they say) if there is more than one variable referring to the same value. It is not so subjective as you make it to be.

The idiomatic way to solve that in rust is to re-bind to the same variable name. if let Some(foo) = foo { /* ... * / } That's possible because in Rust name shadowing let foo = grab_foo_bytes(); let foo = parse_foo_bytes(foo); makes the previous binding of the variable no longer namable and thus no longer accessible, but doesn't drop it (and trigger RAII destructors). Now someone will probably come in and say "oh no,…

It's exactly like Lisp:

  (let* ((x (this))
         (x (that))
         ...)
    ...)
In C you cannot even do:

  { int x = 42;
    { int x = x + 1; // x + 1 refers to this second x
      ... } }
This is because the scope of the identifier being declared already starts at the =. So even if the redeclaration were allowed without opening a new block scope, it wouldn't work.

However, there is a good reason for that: initializers can be self-referential, so they have to have their own identifier in scope:

   // define circular structure in one step, no assignments:
   struct node n = { .next = &n, .prev = &n };
In this regard, the scoping rule is like letrec in Scheme or labels in Common Lisp.
Post reply on HN