Why is it unreadable? I actually find LLM bug reports/breakdowns to be far more detailed and concise that classical human written ones. If you read the linked repo it clearly goes it depth where the bug was found, how to reproduce it (and in depth). Most disclosures that are human written don't do this at all, they barely even tell you _how_ to reproduce the bug. Just look at the "3.3 The self-store tear", the LLM clearly describes exactly what went wrong, so you can verify it by hand.
I'm reasonably confident that the author has a reproduction case on their hands. That's easy to directly verify. I'm way less confident in that long and rambling analysis. As fwlr observes, it's not clear that this has been reproduced off the original machine: https://news.ycombinator.com/item?id=49134357
If it is the case that the original machine has an intermittent hardware fault, that analysis is exactly what I'd expect of an current AI. Voluminous and confidently wrong. Also interesting that the AI itself doesn't note this... having been biased by the conversation to consider kernel bugs, in the "cross-machine data" [1] it blames the kernel and doesn't appear to consider alternate explanations.
It doesn't get mentioned much around here but one major issue with AIs today is that they fall into tunnel vision very easily and often need some help getting out of it. There's a structural reason for tunnel vision built into their context limits, and there's also situations like this, where if you bias them in a certain direction they can get monomaniacally stuck on it. You can also see it where they do things like try to do something on your system like view a certain file, then if for some reason they fail due to permission issues, if you don't stop them they'll go absolutely insane trying different ways to access the file, rather than just stopping and saying "Hey, I need help". Even if you prompt them directly to do that, it only helps, it doesn't fix the issue. I suspect the RHLF training they get to be better coding agents reinforces this behavior because in the coding quality tests they're rewarded for one-shotting all the various benchmarks, where that "bull in a china shop" approach works better than giving up. But I'd actually like them to give up a bit more often. I've had the same problem with some particularly go-getting junior devs, too... I appreciate the ambition and I look forward to harnessing it in other situations but I'd rather you didn't spend five hours to create a terrible work around to something I could have gotten you in two minutes. For the junior dev, it's OK, they take the feedback and adjust... the AIs never adjust.
That reminds me of one ticket I had to handle. It was generated by an agent from a customer report and the different hypotheses were wrong. Also a huge wall of text.
The issue was alongside the API boundary (timezone shenanigans) but the agent invented a lot of reasons with reasonable looking arguments because of the tunnel vision that there should be something wrong inside the project.
Because any writing needs a core intent they need to convey, which you can summarize down to according to the audience and why it should be important to them. Kinda like the same idea of elevator pitches, “explain like I’m five” and tactical reports when there’s a time constraints.
You got none of that here. It’s just realms of text.
Maybe the person writing the report isn't an expert in this domain or doesn't have the time to commit to it? From my point of view as long as the information is accurate and reproducible, it's valuable.
The other part is that almost no one would have the energy to spend hours instrumenting and rebuilding musl, reading the kernel mm code, and thinking hard about how they might interact.
Claude is probably not right about the root cause here and is probably bsing, I agree. But it's collected enough raw data to point some expert humans at the right interaction. I'd take this bs bug report and start asking Claude some questions that would guide it to a more plausible actual answer, but that requires a little more experience in kernel development.
> The other part is that almost no one would have the energy to spend hours instrumenting and rebuilding musl, reading the kernel mm code, and thinking hard about how they might interact.
Maybe because it isn’t needed. That’s the reason we have experts and professionals, because it’s more economical to use them than for everyone to start from scratch. It’s easier to go to a mechanic shops to fix my engine block than to try to do it myself. I’ve heard that a lot of shops charge more if you said an amateur try to fix things first.
It’s a lot of stuff, but that’s not how you experiment for an hypothesis.
I've been a systems dev for a long time and have debugged many such tricky issues before. Even on teams of experts, this type of issue would take someone really capable weeks to track down, especially because the repro environment is hard to obtain. And the opportunity cost of doing this type of work for a rare bug is high, since you may never get to an answer and have burned a lot of time. So in practice I solved most of this type of issue early in my career before I had a family and before I had higher level work to do.
The ai doesn't have the same incentives as a human. It can also be applied to the repro where it exists.
Your mechanic analogy is a bit inapt. Sure they're experts and can replace your engine much better than you can. But tracking down a non obvious system level bug like this one is much less repeatable and much harder because the problem has already been filtered through a lot of reviews and testing. As a system becomes mature, the bugs become harder and harder individually to track down.
> Even on teams of experts, this type of issue would take someone really capable weeks to track down, especially because the repro environment is hard to obtain.
> As a system becomes mature, the bugs become harder and harder individually to track down.
What I'm talking about is that experts knows the system mechanism more than amateurs, so any hypothesis and experimental setup will be more focused and thus more economical than any amateurish one. And there's the matter of knowledge not present in some docs or other forms, such as past experiences.
Even with LLM tooling, we've seen the rises of harnesses and helper tools instead of relying on generation for everything. Who would you trust to build such harness, a domain experts or some random guy off the street?
> From my point of view as long as the information is accurate
That's the trillion-dollar catch, isn't it. LLMs love to write 30 paragraphs about some plausibly-correct-sounding explanation that is just as likely to be completely fucking wrong as it is accurate. The bug might be real, but that doesn't mean this analysis is accurate, and trying to figure out where the LLM went off the rails can be a nightmare. If you can actually understand the bug, it doesn't take 30 paragraphs to explain it. I would throw this bug report into my junk bin if I were on the receiving end of it, and I say that as someone who will spend days troubleshooting any issue a user will help me diagnose even if it only happens on their machine.
I'd rather read walls of AI slop than soulful meatbag bickering. Just because you might have a soul and intent doesn't mean you do anything useful with it. All I see is the intentful invention of more reasons to fight over arbitrary crap.
Nice article. There a few issues with your code however from a cursory glance; your dtor seems to allow for spurious/double frees due to custom deleter support (you wanna check up on that), you also seem to use seq_cst far too much even if not needed (you want to avoid them is queues as much as possible), lastly class FastQueueNodeSlot.. isn't aligned (plus 64b alignment is only a thing for amd64 cpus, apple silicon is larger).
a C++ experienced programmer, I spoke recently to, told me that using new and delete is basically prohibited nowadays in C++ in favour of std::make_unique, std::make_shared etc.
For 95% of all code that is correct. std::make_* is easy to use and prevents a lot of mistakes, while having no loss of performance. That last 5% though you are doing weird things and so need to do something manually. (as the other poster said, make_unique is implemented with new) Of course the 5% is overall. Some projects never have anything that gets into that last 5%, while others it is more like 50% of the code can't use make_*. If at all possible your use of new/delete should be limited to a data structure/container than handles it, and not scatters all over.
"For 95% of all code that is correct. std::make_* is easy to use and prevents a lot of mistakes, while having no loss of performance."
For 95% of code std::unique_ptr has no loss of performance. Perhaps. Just remember that it is not a zero-cost abstraction, the compiler won't always be able to entirely eliminate the overhead:
I watched that about 7 years ago when it first came out, and at an hour long I'm not about to watch it again... From what I can recall those are things that rarely are important. There is a cost, but in most real world code they only add a few nanoseconds - I have more important things to worry about.
Even at 32Mhz you typically have more important things to worry about. These nanoseconds are unlikely to add up - the overhead from the underlying new/delete is going to be much worse.
you can call release or reset as needed if you want the delete to happen early - and if you forget an obscure code path it still gets deleted on scope exit which is probably good enough for that path.
Classic C++, any issue is at most three years away from going away! There will definitely not be any other issues that are also three years from getting fixed when you reach that year though
Fair enough, three years might be more of an upper bound than a lower bound. And yeah, the issue of having to wait is common to standard-based processes, but I've never seen anything else based on a standard so frequently have people need to jump in and try to dissuade people that issues matter because of the rolling timeline so frequently, regularly, and for such a long time as C++. Obviously it would be unreasonable to expect it to become perfect and not need any more changes, but like, I was hearing people complain about various aspects of `unique_ptr` a decade ago, and it seems like we're not even past that yet.
I don't think adding unsafe will be possible - it will break too much existing code. We might be able to add something new that is only possible in an unsafe context, but there is too much existing code.
However I do expect a [[safe]] profile (or perhaps several, depending on which paper you read) that everyone is encourage to opt-in to. Likely combines with compiler warnings and static analysis to encourage that use. (Also syntax is still open for debate)
I was for Safe C++ paper, based on Circle experience, but it was shot down due to politics.
So we're left with the profiles camp actually delivering, followed by the remaining compilers caring to actually implement them, otherwise it will be static and dynamic analysis as usual.
Yeah, that is why there was a paper created specifically to kill any other proposal, by the profiles folks with WG21 majority, and ironically profiles are just as annotation heavy, but since it is profiles, it is alright.
I call that politics, and in the end the most likely outcome is that by C++29 nothing will be delivered by the profiles group, that is any better than using clang-tidy already today.
This is gonna be a long critique, I'll try to keep it concise.
> C-like C++ is good start, if code doesn’t require more complexity don’t add unnecessary C++ complexities.
C is almost obsolete nowadays. Not to mention that C++ is effectively a strict superset of C (nearly 99% of the C standard is in C++) and the few features that aren't are included as compiler extensions (VLA, restrict keyword, nested functions). There are a handful of C features that aren't in C++, and for very good reason (most of them suck).
When was the last time you ran into a C library that a pure C++ compiler couldn't compile? Only if someone decided to spam the new keyword all over the codebase (or something similar).
> In general case code should be readable to anyone who is familiar with C language.
Most C++ already is? Even very template heavy C++.
> Don’t do this, the end of “design rationale” in Orthodox C++ should be immedately after “Quite simple, and it is usable. EOF”.
A lot of the methods in that document are necessary to make C++ shine, especially template metaprogramming.
> Don’t use exceptions.
Optional but irrelevant.
> Don’t use RTTI.
.. Why? Reimplementing RTTI in C will give you almost the same overhead.
> Don’t use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
.. Why? Those wrappers all include the "raw C runtime" under the hood (literally they do #include <stdio.h|xx>. Near 0 compiletime overhead?
> Don’t use stream (<iostream>, <stringstream>, etc.), use printf style functions instead.
This is a design decision.
> Don’t use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.
There are many programs that are _impossible_ to write in a finite time without metaprogramming. How will you (with zero runtime overhead) dispatch a function with a variable arity of random types to a handler that requires exactly that type of function? Arbitrarily? In C++ it's possible, in C it isn't.
> When was the last time you ran into a C library that a pure C++ compiler couldn't compile? Only if someone decided to spam the new keyword all over the codebase (or something similar).
In C, you can use goto to jump over a variable declaration, and you can't in C++. I understand why this is, but it's the thing I see the most often that makes C code not compile as C++.
But, one of the annoying habits of WG21 (the C++ committee) is sending stuff to WG14 (the C committee) to have them make it part of their language rather than accept that it's a C++ problem. Even the stupid type qualifiers are actually C++'s fault, K&R doesn't have this abomination but the pre-standard C++ did so too bad now it's in C89.
One thing I've noticed about a lot of these "strict C" developers is that quite often they actually refuse to learn C++. One of the most common complaints of C developers regarding C++ is "it does things behind the scenes/performs magic", often with regards to operator overloading. When they refuse to actually look at the implementation (y'know you can check if an operator has been overloaded) AND they refuse to acknowledge that a huge chunk of "pure C" does HEAPS of magic behind the scenes (that the developer has no idea about) unless they've actually studied the spec in detail. Malloc and memory allocation methods are at least 10k+ lines of code for instance.
I don't think "refusing to learn C++" is the right way to frame it. I want to use the language features that are actually useful to me, without being forced into a specific programming style. I can't speak for every "orthodox C++" programmer, but for me that means using exclusively plain-old-data structs, non-member functions, and "dumb" pointers. I have no issue with learning to use a C++ feature when it's directly useful to a problem I'm trying to solve.
As one example, I recently found templated lambdas useful in making animations.
Right. Use/Choose those features you are most knowledgeable/comfortable with. That is why C++ has such a smorgasbord of features and supports multiple paradigms. Over time, as one learns "better ways" (for a certain definition and one is convinced of it) of doing something change/modify as needed.
I was programming in C++ before switching to C and I would say that C++ adds a huge amount of mental load compared to C. I think one can understand how much of a relieve it is to not worry about everything C++ does only after not using C++ for a quite a while.
There is a difference between "needing a library" and "implement my own". One can just pick one. And even when implementing things yourself, one has to do this just once.
> y'know you can check if an operator has been overloaded
And there lies the problem with C++: to be sure, you have to check. C++ code can't be taken at face value -- the most innocuous-looking code could be a ticking bomb.
Not really. C++ is on another level altogether: the code could be calling implicit conversion operators, the compiler could have instantiated some template code in an unforeseen way, and so on.
Years ago, I was really proficient in C++, but after a year of programming in C#, I realized that not once had the behavior of my code caught me off guard. In the following years, I only ran into quirky behavior a couple of times. I could finally program without the constant mental overhead of watching out for C++ pitfalls.
I suppose you're aware C# also has implicit conversion operators, operator overloading, reflection, aspect oriented programming, compiler plugins, interceptors.
Seems strange to talk down C++ while praising C#, which incidentally has been getting features to increase its use where Microsoft previously might have used C++ instead.
You catch pitfalls in any language the same way, using static analysis, which C authors introduced right in 1979, acknowledging the issues with language, which they decided to outsource to another tool, instead of improving the language.
Yes, C# is becoming more and more complex, but IMO C++ is still in a class of its own. Just compare how many different, sometimes competing ways there are to initialize variables in C++, each with its own subtleties.
I guess we'll have to agree to disagree here. And of course, even if C++'s user base seems to be shrinking, it still works well for some categories of programmers.
My favorite was the regex engine that was implemented using C++ operator overloading. The author was very proud of it, but you could not tell what code was regex code and what code was math code.
I went to some lengths with D to discourage such abusive operating overloading practice.
To the extent that one doesn't try to suppress compiler warnings & errors / actively break the language, you get what the compiler confirms is true about a function signature.
For Python, it's very little (nothing?). For Rust, you get more than most; lifetimes tell you whether it holds onto a pointer you give it.
A lot of us are too busy solving problems. Learning about the latest language features, which we often won't be able to use anyway due to the trouble of moving a large dev environment to a newer standard, feels like academic masturbation.
C++ folks are very much into their language, and can't seem to understand that most folks don't want to dedicate significant amounts of mental resources purely to language details.
Moving to new C++ is a non event, change the compiler / flags and done. Using the new features requires some learning but not a big deal since you can figure out what you need from a summary and learn what is useful for your problem.
The problems of the code I'm writing far exceeds the complexity of the language. Your complaint about complexity fall flat to me, unless you are working on a trivial program you need to deal with things far more complex than any language.
GCC was implemented in C and there are plenty of other C compilers written in C. GCC has been converted to C++ at some point, but large parts are still essentially C and I do not think the change to C++ was actually helpful (but others may disagree). In any case, the idea that one needs C++ to have C compilers is certainly simply wrong.
Clang/LLVM with its more permissive licence sees _much_ wider use and is written purely in C++. PlayStation, automotive, platforms and runtimes like Chromium/V8, Android, etc. are all built with Clang.
With Android, iDevices, PlayStation, Switch, and everyone that had proprietary compilers down using downstream forks from clang, due to the more appealing license.
Who is left still using GCC, other than existing projects lacking a clang backend for a snowflake embed CPU?
In any case, if it is a modern GCC release past 2012, it was compiled with C++ as well.
Naturally GCC was originally written in C, given its age, and the original GNU coding standards document.
With time, the GCC developers acknowledged the benefits of using C++ over C, and migrated the code.
GCC requires a C++ compiler to bootstrap since around 2012, and GNU coding standards has been updated to several languages beyond C, time to go up with times.
The question is whether GCC is a good example of the benefits of C++ for compilers. Considering the code looks to 95% like C code and uses data structures that we originally implemented in C, I don't see this argument.
I told him the same thing multiple times, just open a random code location in GCC, or a recently committed change, and see that it's basically C. But he keeps repeating that ridiculous argument like a broken record.
Memory corruption magnets like iterator invalidation, std::string_view or std::span are on a whole different level than the footguns that were inherited from C. At least with C style raw pointers you know that you have to be careful when you see one, in C++ the unsafety is lurking in hidden places sprinkled all over the stdlib and comes in all shapes and forms.
Trust me, I know more C++ than most or all of my peers (working two jobs simultaneously), and I know a million ways that C++ features suck. Also standard library and containers. If you want I'll point out the ways in which std::deque, and even std::map, std::unordered_map, even std::vector (!) suck. IMO, just don't do it.
The standard library implements really do suck (in some cases), but this should be separated from C++ (the language). Even the standard splits the language grammar from the standard library cleanly.
You can't really separate the two, firstly because some parts of the standard library interact directly with the language's syntax (e.g. <initializer_list>), and secondly because the language standard dictates things about the behavior of the standard library that limit implementation options.
For example, the standard says that adding elements to an <unordered_map> is not allowed to invalidate references to keys or elements within the map. That makes it impossible for any standards-compliant C++ implementation to use a high-performance implementation in which keys and elements are stored contiguously in a flat array.
The context of this thread is that someone stated that the C++ standard library sucks, and someone replied to them saying that it's just some implementations that suck, but that's separate from the language. The point I'm trying to make, in response, is that it is about the language. It's not just "some" implementations - there is no implementation of the C++ standard library that doesn't have these inefficiencies, because the language's own standard requires them.
(This is tangential but - this is why I often say that C++ is not actually the most complex language in the world, it's just over-specified. If you took almost any popular programming language and wrote a document dictating the behavior of every single feature and library to the same level of detail, you would end up with a document similar in length or even longer than the C++ standard.)
In my reading, they didn't say it's due to bad implementations, though. They were trying to separate the standard into two parts, the one about the language syntax and semantics, and the one about the standard library. And I think this is a fair separation actually. But that doesn't make the core language any better ;-)
and even std::map, std::unordered_map, even std::vector (!) suck
It's really hard to take your comment serious because of generalization like this. Maybe they're not usable for your particular usecase but that doesn't mean they suck. Just like there's a 'million' ways that C++ sucks in your book, there's a reason there's millions of lines of code out there where these containers are valid usecases and hence work without issues whatsoever nor a need to replace them with something else.
std::map and std::unordered_map are just unbelievably shitty implementations. The former is a red-black tree, which in my entire programming career I have needed to reach for like... twice? It's just not the right container for almost any problem you have, yet it's the one that gets the short, sweet name. The latter is a bucket-based hashmap, which is about the worst kind of hashmap that can be built. On top of that, their APIs are also really annoying to use compared to, say, Python or Rust's implementation. At least C++20 finally added a simple contains method, but something like setdefault is just a chore to get implemented.
Not really apropos any of this but a vivid career memory for me is leaving a startup that was all in C++ and going to Arbor Networks, which was all C, and porting the STLport map to rbtree.c and making it our default container. No, I have no idea why I did that. Different times!
They're not useable for anything serious, i.e. high throughput, low frequency, massively concurrent work. In other words, most of the things for which you shouldn't better have chosen a different language in the first place.
They're also unusable by the way because of ergonomic and software architecture factors, such as bad modularity, terrible compile times, unreadable error messages, unreadable symbol names...
Yes that is overgeneralizing a little bit but it's largely true.
The problem is typically not the containers themselves but all the other bad decisions that they push you to make in order to work around their "small issues".
The huge problem is that these containers can get you started quickly, i.e. leetcode type stuff and single threaded stuff, but at some point you'll realize your architecture ended up completely in the wrong place because of that.
If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant. I've just fixed another subsystem that was completely overwhelmed, seeing 8x bandwidth gains already on a small testsystem, but the factor is basically unbounded when moving to bigger systems, when it's about contended vs uncontended.
They're just clearly inferior in pretty much any situation.
The map stuff the other posters summed up well but even std::vector is dogshit with pretty much all implementations having inlined grow code in push_back, a not too great API and missed optimisations e.g. no trivial relocation when growing the vector / moving it and no useful APIs such as "grow but don't initialise"...
To be fair grow-but-don't-initialize is a pretty fundamental part of the API, the reserve() method.
But already the basic premise that you should push back without thinking is wrong. You will suffer reallocations and invalisations when you least expected them, and frankly you have to architect around that fact which is a terrible restriction. You can work around by pre reserving but at that point it's just a basic fixed heap allocated array but worse because the type gives you a weird look all the time, "I'll realloc as soon as you don't pay attention, harhar"!
std::vector lacks what I call the "bifurcated reservation API". It has Rust's Vec::reserve_exact but not Vec::reserve -- these APIs serve subtly different purposes, the former (which C++ calls just "reserve") says "Here's a hint for exactly how big this container will ever grow" while the latter says "Here's a hint for how big this container will get in my immediate future, but it might grow further later".
The implementation always tries to grow (if necessary) to the exact size chosen for Vec::reserve_exact, but for plain Vec::reserve if growth is needed it always grows exponentially, not to the exact size, preserving the O(1) push cost.
For a typical "doubling" growable array type, if we're pushing groups of ten items, reserve_exact or C++ grows like 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ... which is much worse than O(1) whereas the correct reserve grows 10, 20, 40, 80, 160 preserving O(1)
For trivial types you can work around this in C++ with a little work, and for the non-trivial types you can work around it with a bunch more extra code, but you probably won't.
Bjarne among other people teaching C++ recommend just not using the reservation API as a reservation API because of this problem†, and the resulting teaching definitely leaks into CS graduates and even into languages which have the correct API and so you have to un-teach the bad lesson.
In applications where you actually can't afford to pay for growth (or at least in some cases can't afford this) I also like Vec::push_within_capacity which I believe comes from Rust for Linux where the kernel legitimately needs this "If there's room push, otherwise I have a plan B" approach.
† To Bjarne this API is instead conceived of as a way to preserve reference validity. Since it won't grow, our references will still work.
Vec::reserve() is the behaviour you get from C++ std::vector push_back() (implicit just-in-time reserve), so right now I can't see a situation where I'd want the explicit Rust version even if I didn't think the whole push realloc thing is mostly a bad idea.
Yes, Rust version allows you to maybe skip a reallocation step or two by doing explicit up front reallocation. But remember most allocation work is always from the last grow anyway. The Rust version seems like a microoptimization, giving a little bit more explicit control in a situation where you've already pretty much given up control and gone like, throw hands in the air, we're doing push_back()!
As with the likely/ unlikely branch hint the problem is that programmers are wrong much more often than they expect on both sides. They're too often wrong to think they know the final size - hence Bjarne's caution - but they're also too often wrong that they've got no idea how much capacity they need at all. So hence this API.
You're correct that this isn't a huge optimization. But it more than pulls its weight directly because it's a small boon when you're right and it doesn't have the terrible penalty that Vec::reserve_exact has when inevitably the programmer is sometimes wrong. It's very much about saving pennies, but the growable array type is so widely used that counting pennies makes sense.
I have a lot more thoughts about reservation, but these suffice for specifically the growable array type.
There are many many situations where I know exactly what is the common path and what is the uncommon path. And when I don't know, it's much harder to optimize!
If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation. Maybe chunk lists could be better, overhead is bounded by chunk size and all operations are constant-time. No pointer invalidation either. And you can pool those chunks, preventing memory fragmentation and improving memory utilization, since there aren't a million different sized allocations in your process.
> If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation.
I think this is "Trump math" but assuming we're actually looking at the same over-allocation this isn't new, what you're calling "wasting a factor of 2x" was already what you were paying by using the amortized O(1) growable array type, that's its central trade off, so yeah, the type with that property does still have that property.
> Maybe chunk lists could be better, overhead is bounded by chunk size and all operations are constant-time.
Basically std::deque. Surely no more need be said ?
> was already what you were paying by using the amortized O(1) growable array type
As I said, I don't think growable array data structure is a good idea.
> Basically std::deque. Surely no more need be said ?
Ok now you're really out of your depth here. Pretty sure you haven't actually used it, it's a somewhat obscure data structure even though the more serious C++ programmers have heard of it, most have probably never used it.
From a web search, std::deque seems to be pretty universally thought of as a weird and very bad data structure. I've had to learn myself because I did actually try to use it myself recently. Beyond being unintuitive to use due to being "abstract" (had 2-3 serious bugs, e.g. unexpected iterator invalidations that happened only under load), apparently std::deque is not even specified as a chunk list. O(1) random access must be guaranteed. So it must be much more complicated than that, I don't know the details though.
And while actual implementations do use chunks in some way (just not in a simple chunk list), apparently "chunks" is not a part of the std::deque spec, and hence there isn't anything standardized about the size of these chunks either.
- On MSVC, chunk size is 16 elements so chunk size of std::deque char is 16 BYTES !!!!! I was thinking they can't be serious, but apprently it is the case
> As I said, I don't think growable array data structure is a good idea.
It is still quite hard for me to take this seriously as a belief.
> Ok now you're really out of your depth here.
I don't think so. As you found, the consensus is that this sort of thing is a terrible idea. The O(1) complexity looks good superficially but your actual performance is miserable.
> I don't know the details though.
Raymond Chen has a pretty good explanation of the three implementations of std::deque, like he did for their std::string but Raymond's goal is to help you do forensics, he's not here to tell us which data structures are a good idea. https://devblogs.microsoft.com/oldnewthing/20230810-00/?p=10... might work, or, given Microsoft, it might already be dead by the time you click it.
Without knowing more about the details of the spec and of real world implementations [0], I'll boldly claim that the O(1) is the precise problem why this is a weird data structure (I never needed or expected that sort of constraint). It is not a chunk list, like many (including me) seem to have assumed.
I don't even have performance problems measured with this, also given that my std::deque is not currently in production, it is used as a simple streaming queue that only has a couple megabytes/second of bandwidth requirements, and it is not being built with MSVC (so chunks are going to be 512 or 4096 bytes). But it is a latent problem.
[0] Btw. I don't want to know the messy details of this right now, I need simple not complex. I've learned not to add more complexity in a futile attempt to fix things that are fundamentally broken. What use case is this going to be solved by std::deque, name me one such use case and tell me with a straight face that it's not a completely made up case or a super niche case that would likely also be better served by a different approach.
Or take it from someone else if you would, is Chandler Carruth high-profile enough and free enough of conflict of interest to be credible for saying that std::deque is dumb? https://news.ycombinator.com/item?id=22962980
> It is not a chunk list, like many (including me) seem to have assumed.
The thing C++ programmers tend to expect (though perhaps not you) is Rust's std::collections::VecDeque - the growable array type again but used as backing for a ring buffer.
This type has amortized O(1) push and pop at both ends, I assume since you didn't like the amortized growth of Vec / std::vector you'll feel the same way but that's what most programmers actually want from such a type - in use as a queue it won't repeatedly cycle allocations so long as the size is constrained, because it's a ring buffer. If you actually know the needed size up front the overhead from this structure, which can grow, versus a structure which can't grow, is a single integer for the capacity.
But as you saw, std::deque is... not that. STL wasn't able to explain what it's for when I asked him, so, if anybody knows it apparently doesn't include the people maintaining the C++ standard library implementations.
> Or take it from someone else if you would
I think the trouble here is that you've misconstrued this entire part of the conversation. I was gesturing at std::deque because it's terrible, and your response, that it's terrible, isn't a rebuttal as I think you're expecting. We agree on that, std::deque is terrible, our disagreement seems to be that you think a slightly different terrible data structure would be a good idea somehow and I do not.
I don't know the size up front because it goes up and down depending on load. I want to be able to buffer jitters/spikes but not hog memory when there is no data in the queue. I was assuming it's natural to want a simple linked list of chunks to represent this, maybe chunks can be partially full here. So chunk -> chunk -> chunk, and each chunk is actually a node with a buffer pointer + size + fill fields, or maybe the data comes directly after the size + fill fields so no buffer pointer needed.
That easily gives O(1) access to enqueue/dequeue, and the queue naturally grows and shrinks (even within configurable bounds) as part of reading and writing. Blocks can be taken and returned from/to some fixed size block pool. This design would be completely satisfactory. It's also very easy to code from scratch, so why should I settle for anything less, for example a growable vector where a reallocation would mean blocking other threads for extended time? But I was told to "not reinvent the wheel" and to "not overengineer it" so I tried to change my mind and make use of an STL container data structure.
> our disagreement seems to be that you think a slightly different terrible data structure would be a good idea
I don't know why you think that I would be thinking that, and I'm confused that you say you were gesturing at how std::deque is terrible, and not sure why I should be the one misconstruing something, when my premise from the beginning was "STL containers are terrible in many ways". And I don't get at all why you responded with "basically std::deque" to my "maybe chunk lists would better". It doesn't make the least bit of sense to me.
But let's settle this here, this is a pointless fight...
> I was assuming it's natural to want a simple linked list of chunks
In the 1970s or 1980s that does feel entirely natural, but in the 1990s the 68040 and i486 both introduce L1 data cache and so now all list chasing hurts very badly, your structure is a list chase any time we index into the collection.
I think I can see a way to have what you're describing hit amortized O(1) push/ pop with the spikes which are amortized being more frequent (linear with capacity) but fixed size and smaller (allocate or free one block then do some housekeeping), it costs more RAM because of the block overhead and it is no longer contiguous, but I see that for your intended application you probably don't care about either problem.
Now that I think I understand your data structure better it's much less similar to std::deque than I had originally thought, it does seem very niche to me, but more power to you if you write such a type.
Yes, I am talking about a buffer for an outbound queue that should buffer on the order of MB/sec. That's decidedly not a niche thing. It's very basic systems engineering (and also how socket buffers are represented in a kernel for example).
You would chunk this at a reasonable size, at least multiple kilobytes per chunk. That is not slow, it is basically the only way to do it. Yes, that is amortized, bounded overhead. Sure it costs a bit of extra RAM, like one extra pointer and maybe 1-2 integers per chunk? For chunks of 4 KB, this overhead is predictably less than 1 % plus less than 1 chunk worth of fragmentation in the last chunk. That is NOT unpredictable spikes of > 100 percent during reallocation or 100000 percent due to low utilisation...
I believe you can even instruct the CPU to preload the next buffer while still streaming the last, but personally have never had the need to dive _that_ deep. It would probably be very hard to measure any performance benefit from doing so. I like to write very basic straightforward C code, just solve the data structure problem first, not hand-wave it.
Feels like typical improvement/perfection tunnel vision syndrome to me, though. That syndrome is engrained in C++ community and I think also Rust community, although it looks like the latter took the chance to do many things better from the start learning from C++'s mistakes.
Realloc is pretty much never the right way in whatever form, and I've never seen any need to include realloc in any of my own allocators (mostly blockallocators and linear allocators and pools/free lists, sometimes using malloc/free).
That is a cop-out. You made the assertion, you define it.
What about these particular workloads (and the environments they're used in) make them 'serious' and why are other workloads 'lesser' and therefore the standard library 'suffices'? Why not use better containers for everything? Google, for instance, universally recommends Abseil.
I stand by my statement. You are welcome to contend it, but then please actually suggest alternative workloads that could qualify as serious?
Google also recommends using Golang in many cases, which was explicitly designed for not-so-experienced people. It is more geared towards creating services quickly and robustly, not towards squeezing out the last 10x of performance.
I can't say anything about abseil, having never used it. "By Google" is not an immediate seal of quality, and it doesn't mean that one size fits all. I've recently seen a list of .so objects required in order link grpc (also by Google), there were like dozens of abseil .so's in the list IIRC. I don't like it! In my experience, validated countless times in my own practice, complexity => slow and hard to maintain and simplicity => fast and easy to change.
Templatized C++ containers are generic recipes, they can't make use of local context and hence don't lead to the simplest solutions. They produce tons of boilerplate. They give you a decent single-threaded baseline performance in many cases, sure, but now start hammering these data structures using 16 CPUs in parallel... you might find that you should have designed your application completely differently.
Like WalterBright says, I too use very simple data structures almost exclusively (arrays, linked lists, linear allocation). Most of these coded ad-hoc everywhere, very straightforward, very adaptable. Even hash maps I only use infrequently -- if I can, I just make keys such that I can index directly. I get performance from knowing exactly what happens, and minimize the work that needs to happen. I create immutable data objects (write-once) where possible. I've created my own pooled block-allocator for power of 2 sized allocations with book-keeping in shadow-memory (preventing fragmentation), massively reducing syscalls that modify virtual memory mappings and closely controlling memory used by various subsystems.
I don't expect a library like abseil to solve many of my problems, it probably makes some things "easy" by taking away the very control from me that I need to solve the problem I have, resulting in unsolved problems and complexity.
If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant
I do think about that, when needed. My point is that these containers can be 'good enough' in places where it doesn't really matter, not that they're always the go-to thing. E.g. I really don't see any issue using a map as part of a configuration type of object which gets read from args or json and which only gets used once at application start.
> Malloc and memory allocation methods are at least 10k+ lines of code for instance.
Only the really big ones, e.g. here is Emscripten's allocator that focuses on small binary size and is implemented in about 1.5 kloc (ignoring comments and whitespace it's actually under 1 kloc), and that allocator is perfectly fine for most use cases (especially C code bases which typically don't have a high allocation frequency):
Right tool for the job etc... big general-purpose allocators like jemalloc or mimalloc are usually a bandaid to somewhat salvage a failed memory management strategy.
c memory allocation functions can be implemented in 1000 lines or so. I've done it myself. Maybe more are needed to handle strange operating systems or architectures, as glib does.
If we accept the maximum that "any sufficiently advanced technology is indistinguishable from magic", then c++ is indeed magic. It's so advanced that one of the worlds foremost experts in the language(herb sutter) has determined that the language is too complex and we need a whole new language(confront) which is simpler and can be converted to c++.
C++ is actually obscenely complex, I don't deny that. Just mastering object lifetime rules is crazy difficult due to all the edge cases, but it comes with the territory.
In the sense that C++ is complicated because it's C++, which is complicated? That's just a tautology. If you mean "the territory" in some other sense there's no reason to believe this.
C programmers aren't complaining about the "magic" being tens of thousands of lines of code. They're complaining about the magic including bizarre side effects that brazenly violate the principle of least astonishment.
In C++, you can overload the comma operator to do shit. I've seen it done. There's no reason to do it, and no reasonable person would ever expect it in a code base they're unfamiliar with. To find bug in that ultimately roots back to that implementation, you have to go eliminate every other whack-job possibility before it even occurs to you that maybe the weirdo who wrote this code chose to overload the comma operator.
I'm not going to argue with anyone who wants to use C++ in their own projects, you do you. But let's be real about what C programmers are complaining about. It's not line count. It's syntactic obfuscation. I don't just level this criticism at C++ either. Basically every major new language has its own byzantine syntactic constructs to some degree.
I've developed a style that I legitimately call Heterodox C++ (mainly due to the popularity of Orthodox C++), it is effectively a purely functional & metaprogramming heavy style of C++. Quite the opposite of this, not everyones cup of tea, and it won't fit into every codebase but it is incredibly powerful. The template metaprogramming C++ offers is the most powerful of any imperative language, and (subjective opinion) is second only to Lisp, but few people make use of it. With some of C++26 features you can almost even replicate most of Rusts safety features in pure C++ (via function tagging + reflection)
Yuck. I'm sure your compile times suck and code is not readable (my subjective opinion as well). There's nothing worse than a ten-mile long lambda in C++. To each there own, however. C++ is four languages in one after all. :)
Templates are really amazing, once you learn the patterns there's basically a template version and a runtime version of things (policy types, etc.). All of that is great.
Legibility and understanding are in my opinion the most important aspect of any programming language, so Orthodox C++ is superior for maintainable code.
> The template metaprogramming C++ offers is the most powerful of any imperative language
I'm curious what languages you're comparing to here. Feels like it's only slightly more expressive than pure generics, but I admittedly haven't done much template metaprogramming myself. How does it compare to, say, Zig's comptime?
I’ll preface this by saying I mostly use C++ and have just basic experience in Zig, but as far as I understand comptime is much more procedural than C++ templates which are more declarative. With templates you get quite good pattern matching through the compiler’s machinery, for instance through template specialisation, while Zig, in my understanding, requires these to be handled manually in code. Personally, comptime feels like constexpr/consteval in C++ but with the ability to interact with the type system itself. The significant downside of C++ metaprogramming is that sometimes many features interact weakly and feel very much tacked on top of each other while Zig’s looks more cohesive. Perhaps someone with more Zig experience can weigh in.
> The significant downside of C++ metaprogramming is that sometimes many features interact weakly and feel very much tacked on top of each other while Zig’s looks more cohesive.
spot on, in my experience. nothing in zig triggers my lite OCD.
I've been using C++ for about 20 years and I've yet to see these scary template error messages people always complain about. Yeah, they're long, but they contain all the information you need to fix the issue, which is damn near always at the deepest point in the call stack that you still control.
C++ template metaprogramming is in some ways more powerful than Common Lisp macros, because it works at the type level: you can generate new types and dispatch into separate implementations by type. In contrast, Common Lisp type declarations are not available at macro expansion time unless you implement a full source-to-source translator in macros.
> VCs think, 'Apps are risky, infrastructure is safe,' so they invested in AI infra.
First off, this isn't even infra in the infra sense of the word. Infrastructure implied something physical, a pure software product can almost never be considered 'infra'. A tool maybe, but not 'infra'.
VCs can also be irrational and driven primarily by personal connections rather than reason. I didn't do a deep dive in this project/leadership, but often who you know is some important than what you produced. There's a reason why a lot of VCs go for the old motto of "I'd rather invest in an A team with a C product; than invest in a C team with an A product".
> The calculus in “buy or build” has shifted for me over the last six months especially. If I can make an agent build it, I get the version that’s tailored for me.
I feel like this is really going to change the software industry moving forwards. Historically it was tedious and time consuming to actually develop tailored dev tools which is why so many organizations relied on third party solutions. When nowadays you can easily half bake something in a few hours and get it working, tailored _specifically_ to your needs.
>When nowadays you can easily half bake something in a few hours and get it working, tailored _specifically_ to your needs.
The thing is this requires you are given liberty to actually do this yourself. Think of something like say LMS software. Every college in the country is using what either blackboard or canvas. Could they make some bespoke LMS that works great for physics 101 at State university? Absolutely. But they don't, because the course director for physics 101 does not care or have the time to muck around with LMS prototypes. They barely have time to learn how to use their paid for LMS for anything but hosting the slides and syllabus.
So on the one hand, yes, there is massive creative potential for people to roll their own tools. But this is not often met with the required time and liberty to then go on to roll their own tools. Buying off the shelf still serves the organizational need it ever did: defer the creating to "someone else" who has been anointed by marketshare as the thing to do already, so that if shit really hits the fan you can just say you did what anyone would have done in your position. Same function as management consultancy: insulating fallout from bad ideas from the people who could be fired for it and give them essentially an out where they won't get browbeaten over it.
I think our culture around work and responsibility and "free time" needs a revolution for the LLMs to take off as promised as this playdoh tool.
$7m actually isn't a whole lot, especially if they hired a (larger) engineering team. Assuming their cali based, that's easily 150-200k per engineer, a team of 20 easily eats through that. Idk the specifics, but I don't the organization was fradulent, it could also be that they're going commercial and no longer want to maintain their oss stack
150-200k is also just the employee’s salary, the actual cost to the company is significantly higher, you need to multiply that by something like 1.5 to get the fully loaded cost, people are expensive!
> very confident you'll be able to use them to justify another raise soon
That is indeed how the VC funding game is played. If you don't raise another round, you are dead anyway, so you spend down your seed round to try and justify that following round...
> Like OK someone vibecoded an FPS in COBOL or Pokémon emerald in a web browser with web assembly? Ok good for them, piss off karma farmer.
Sorry, but most of these discussions reek of extreme gatekeeping. First off, neither of these things are impossibly difficult and are easily doable with some dedication by hand. LLMs simply accelerate the process, the human still has to come up with the architecture, _idea_ and plan to do something like this.
If an “artist” comes up with ideas for some artwork then just prompts it out with an LLM I am similarly not impressed, and never will be, even if the image looks cool. It’s a cool image, not an impressive effort. That’s all these code projects are. Shiny objects.
I think what we’re seeing play out in this thread is the devaluation of software engineers. If a growing number of people have the sentiment that an engineer vibecoding an idea has less value than a human doing it then that is all that will matter in the end.
That’s the inevitable result of machines being invented that can largely do our jobs. Just like how artisanal products are often prized over factory productions. The effort put in to and the organic quality of products have pretty much always been of value to consumers and appraisers. “I clicked a few buttons and look what the machine produced” will never be as impressive.
> If a growing number of people have the sentiment that an engineer vibecoding an idea has less value than a human doing it then that is all that will matter in the end.
Because if you put them on balance that will be the truth.
> It's not impressive that Claude wrote it, it was impressive if you have written it, OP.
Do you have evidence that it's Claude written? Looking through the source it isn't clear to me, at all. Plus, even if it _was_ Claude/LLM assisted, why does that take away from the project?
It's like being offered a big mac at a fine dining restaurant. Yes, big mac is gonna taste fine, maybe you can dress it up nicely even. But the restaurant didn't make it, and you feel cheated for buying it (and in this case, wasting your time thinking someone coded it).
Something just existing isn't the same as something being made by someone with passion and effort beyond a one-shot prompt to Fable 5.
Because it is process that matters, in such projects, not the outcome. E.g. it is fascinating how one manages to port and run Doom on some microwave led screen... But nobody is going to play it eventually, it is not relevant as the "end product".
You have to remember that this forum is for people who have a passion and an intellectual interest in coding and development, and discussing such with like-minded people. Stimulating intellectual curiosity. An interest in what humans think and do is an implicit part of the experience.
An engineer vibe-coding a project generates an end product, yes, but what is there to be interested in or to discuss? Chances are said engineer isn't even capable of discussing the project in any depth. Are we going to be left discussing nothing but prompts and Claude workflows, and how we got the black box to do a thing? OK. Who cares? I guess we can all politely clap and move on.
I don't know if the posted project was written by an LLM or a human, but I have to agree with localhoster than AI has sucked a lot of the joy out of a lot of HN.
How this isn't causing an existential crisis in fellow developers, I don't understand. We're seeing our craft dissolve day by day, to the point where even our forums are filled with AI generated crap that we just don't find interesting or engaging.
It is. People have posted threads about how they hate that AI has sucked all of the joy and fun out of their work, and they can feel it rotting their brains, but they just can't not use it. Either because their employers require it or because the compulsion to minmax and optimize their "productivity" overrides all. The dialogue they have with themselves and with HN is a lot like the dialogue someone has questioning the tenets of their own religion.
Aside from that, I don't want to take responsibility for that which I don't understand. And even if I can read the code generated by a LLM, that's not the same as thinking through a solution or planning a design.
You can't call yourself an "engineer" if you just open Claude or Codex and hit "Y" on the keyboard to every prompt like that dipping flamingo when Homer Simpson took the wfh job (Simpsons predicted it).
I am facing that very dilemma. I lean more to the anti-AI side, I don't want to get my skills atrophied by relying on it, but I recognize its potential productivity boosts as tempting to use.
> An engineer vibe-coding a project generates an end product, yes, but what is there to be interested in or to discuss? Chances are said engineer isn't even capable of discussing the project in any depth.
And this is reductive to the point of absurdity.
Everybody screaming about AI forgets the fact... you STILL need the domain knowledge. That's where the value in engineering is now.
Imagine a wanna-be game designer thinking they can fire up codex or claude and even with a few weeks and a few hundred dollars in credit, coming out the other side with a game anybody would want to play.
That's not how it works.
I tried with godot. I can tell you everything you want to know about programming/networking/DevOps, and daily use that knowledge. But was very very quickly overwhelmed with the things I DON'T know about game design and development when I tried to develop retro-DQ clone in space.
reply