Hacker Newsnew | past | comments | ask | show | jobs | submit | more gsliepen's commentslogin

Early programming languages had to work with the limited hardware capabilities of the time in order to be efficient. Nowadays, we have so much processing power available that the compiler can optimize the code for you, so the language doesn't have to follow hardware capabilities anymore. So it's only logical that the current languages should work the limitations of the compilers. Perhaps one day those limitations will be gone as well for practical purposes, and it would be interesting to see what programming languages could be made then.


> Nowadays, we have so much processing power available that the compiler can optimize the code for you, so the language doesn't have to follow hardware capabilities anymore.

That must be why builds today take just as long as in the 1990s, to produce a program that makes people wait just as long as in the 1990s, despite the hardware being thousands of times faster ;)

In reality, people just throw more work at the compiler until build times become "unbearable", and optimize their code only until it feels "fast enough". These limits of "unbearable" and "fast enough" are built into humans and don't change in a couple of decades.

Or as the ancient saying goes: "Software is a gas; it expands to fill its container."


At least we can build software systems that are a few orders of magnitude more complex than in the 90s for approximately the same price. The question is whether the extra complexity also offers extra value.


True, but a lot of that complexity is also just pointless boilerplate / busywork disguised as 'best practices'.


I am eager to have an example to explain how a "best practices" is making the software unbearable or slow?


Some C++ related 'best practices' off the top of my head:

- put each class into its own header/source file pair (a great way to explode your build times!)

- generally replace all raw pointers with shared_ptr or unique_ptr

- general software patterns like model-view-controller, a great way to turn a handful lines of code into dozens of files with hundreds of lines each

- use exceptions for error handling (although these days this is widely considered a bad idea, but it wasn't always)

- always prefer the C++ stdlib over self-rolled solutions

- etc etc etc...

It's been a while since I closely followed modern C++ development, so I'm sure there are a couple of new ones, and some which have fallen out of fashion.


> - put each class into its own header/source file pair (a great way to explode your build times!)

Only if you fail to use binary libraries in the process.

Apparently folks like to explode build times with header only libraries nowadays, as if C and C++ were scripting languages.

> - generally replace all raw pointers with shared_ptr or unique_ptr

Some folks care about safety.

I have written C applications with handles, doing two way conversions between pointers and handles, and I am not talking about Windows 16 memory model.

> - general software patterns like model-view-controller, a great way to turn a handful lines of code into dozens of files with hundreds of lines each

I am old enough to have used Yourdon Structured Method in C applications

> - use exceptions for error handling (although these days this is widely considered a bad idea, but it wasn't always)

Forced return code checks with automatic stack unwinding are still exceptions, even if they look differently.

Also what about setjmp()/longjmp() all over the place?

> - always prefer the C++ stdlib over self-rolled solutions

Overconfidence that everyone knows better than people paid to write compilers usually turns out bad, unless they are actually top developers.

There are plenty of modern best practices for C as well, that is how we try to avoid making a mess out of people think being a portable assembler, and industries rely on MISRA, ISO 26262, and similar for that matter.


> put each class into its own header/source file pair (a great way to explode your build times!)

Is that really sufficient to explode build times on its own? Especially if you're just using the more basic C++ features (no template (ab)use in particular).


Not at all, you can write in the C subset that C++ supports and anti-C++ folks will still complain.

Meanwhile the C builds done in UNIX workstations (Aix, Solaris, HP-UX) for our applications back in 2000, were taking about 1 hour per deployment target, hardly blazing fast.


The problem is: "the platform" is never defined.

When you decouple the language from the hardware and you don't specify an abstraction model (like java vm do), "the platform" is just whatever the implementer feels like at that moment.


Isn't that the tail wagging the dog? If you build the language to fit current compilers then it will be impossible to ever redesign those compilers.


Maybe, but if you don't consider the existing compilers you run the risk of making something that is unimplementable in one of the existing compilers, or perhaps at all. (C++ has had some issue with this in the past, which is I think why it's explicitly a consideration in the process now)


Why would that be impossible? Most programming languages are still Turing complete, so you can build whatever you want in them.


You said this was an efficiency issue, and Church-Turing says nothing about efficiency.


"Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy."

- Alan Perlis, Epigrams on Programming


It's not really about "limitations" of the hardware, so much as it is about the fact that things have crystallized a lot since the 90s. There are no longer any mainstream architectures using big-endian integers for example, and there are zero architectures using anything but two's complement. All mainstream computers are Von Neumann machines too (programs are stored; functions are data). All bytes are 8 bits wide, and native word sizes are a clean multiple of that.

Endianness will be with us for a while, but modern languages don't really need to consider the other factors, so they can take significant liberties in their design that match the developer's intuition more precisely.


I was thinking more about higher-order things, like a compiler being able to see that your for-loop is just counting the number of bits set in an integer, and replacing it with a popcount instruction, or being able to replace recursion with tail calls, or doing complex things at compile-time rather than run-time.


At least the popcount example (along with some other 'bit twiddling hacks' inspired optimizations) is just a magic pattern matching trick that happens fairly late in the compilation process though (AFAIK at least), and the alternative to simply offer an optional popcount builtin is a completely viable low-tech solution that was already possible in the olden days (and this still has the the advantage that it is entirely predictable instead of depending on magic compiler tricks).

Basic compile time constant folding also isn't anything modern, even the most primitive 8-bit assemblers of the 1980s allowed to write macros and expressions which were evaluated at compile time - and that gets you maybe 80% to the much more impressive constant folding over deep callstacks that modern compilers are capable of (e.g. what's commonly known as 'zero cost abstraction').


Nope. Performance really matters. Even today. And even for web applications! Just remember how you feel using a slow sluggish website vs. a snappy fast one. It's night and day.


It's amazing how many people try to write generic containers for C, when there is already a perfect solution for that, called C++. It's impossible to write generic type-safe code in C, and this version resorts to using GCC extensions to the language (note the ({…}) expressions).

For those afraid of C++: you don't have to use all of it at once, and compilers have been great for the last few decades. You can easily port C code to C++ (often you don't have to do anything at all). Just try it out and reassess the objections you have.


Except that I find C++ far from being perfect. In fact, I switched from C++ to C (a while ago) to avoid its issues and I am being much happier I also find my vec(int) much nicer.

In fact, we are at the moment ripping out some template code in a C code base which has some C++ for cuda in it, and this one file with C++ templates almost doubles the compilation time of the complete project (with ~700 source files). IMHO it is grotesque how bad it is.


My problem with C++, and maybe this is just me, is RAII.

Now, Resource Aquisition Is Initialization is correct, but the corollary is not generally true, which is to say, my variable going out of scope does not generally mean I want to de-aquire that resource.

So, sooner or later, everything gets wrapped in a reference counting smart pointer. And reference counting always seemed to me to be a primitive or last-resort memory managment strategy.


> my variable going out of scope does not generally mean I want to de-aquire that resource.

But it does! When an object goes out of scope, nobody can/shall use it anymore, so of course it should release its (remaining) resources. If you want to hold on the object, you need to revisit its lifetime and ownership, but that's independent from RAII.


Your problem is not with RAII, but with reference counting, which you correctly identified should be the last resort, not the default; at least for the applications typically written in C++.


Why should reference counting be a last resort?


Because it's insanely slow, and doesn't support cyclic structures.


... in C++. Because, at least in C++, it is a bad, slow and inconvenient GC. If you want or need generalized GC there are significantly better languages for that.


Instead of reference counting, consider having two types. An "owner" type which actually contains the resource and the destructor to dequire the resource. And "lender" types which contain a reference (a pointer or just logically (e.g., an fd can just be copied into the lender but only closed by the owner) to the resource which don't dequire on destruction.

Same thing as what Rust does with `String` and `str`.


If you want to take back manual control, use the release() function


But this makes me wonder how it works when there are concurrent requests. What if a second thread requests data that is being written to memory by the first thread? Shouldn't it also wait for both the write intent record and completion record having been flushed to disk? Otherwise you could end up with a query that returns data that after a crash won't exist anymore.


It's not the write ahead log that prevents that scenario, it's transaction isolation. And note that the more permissive isolation levels offered by Postgres, for example, do allow that failure mode to occur.


If thats the hypothesis, it would be good to see some numbers or proof of concept. The real world performance impact seems not that obvious to predict here.


A lot of people don't realize that all these so-called issues with TCP, like slow-start, Nagle, window sizes and congestion algorithms, are not there because TCP was badly designed, but rather that these are inherent problems you get when you want to create any reliable stream protocol on top of an unreliable datagram one. The advantage of QUIC is that it can multiplex multiple reliable streams while using only a single congestion window, which is a bit more optimal than having multiple TCP sockets.

One other advantage of QUIC is that you avoid some latency from the three-way handshake that is used in almost any TCP implementation. Although technically you can already send data in the first SYN packet, the three-way handshake is necessary to avoid confusion in some edge cases (like a previous TCP connection using the same source and destination ports).


They also tend to focus on bandwidth and underestimate the impact of latency :)

Interesting to hear that QUIC does away with the 3WHS - it always catches people by surprise that it takes at least 4 x latency to get some data on a new TCP connection. :)


It was an act of Google^H^H^H^Hd.


When is it "safe" though? What if the row-polymorphic record has more fields than the named record? Should those just be discarded? If it has exactly the same fields, then in C++ at least you can use std::get<NamedRecordType>(row_polymorphic_record) if you are using static polymorphism, and dynamic_cast<NamedRecordType*>(row_polymorphic_record) in case of dynamic polymorphism. Note that the compiler cannot determine anything at compile time when you have polymorphic objects, so it's just going to emit code that at runtime will throw an exception.


What if you find one word that covers all the letters, like "highlighted"?


There are two Ts in the prompt. Choose a long second word with a T in it.


AFAIK compilers will perform tail call optimization without [[musttail]], it's just not guaranteed (and probably it won't if you don't enable optimizations at all).


  pg_usleep(1000L);
Virtually any time you put a fixed sleep in your program, it's going to be wrong. It is either too long or too short, and even if it is perfect, there is no guarantee your program will actually sleep for exactly the requested amount of time. A condition variable or something similar would be the right thing to use here.

Of course, if this code path really is only taken very infrequently, you can get away with it. But assumptions are not always right, it's better to make the code robust in all cases.


The usleep here isn't one that was intended to be taken frequently (and it's taken in a loop, so a too short sleep is ok). As it was introduced, it was intended to address a very short race that would have been very expensive to avoid altogether. Most of the waiting is intended to be done by waiting for a "transaction lock".

Unfortunately somebody decided that transaction locks don't need to be maintained when running as a hot standby. Which turns that code into a loop around the usleep...

(I do agree that sleep loops suck and almost always are a bad idea)


> there is no guarantee your program will actually sleep for exactly the requested amount of time

This is technically true, but in the same way that under a preemptive operating system there's no guarantee that you'll ever be scheduled. The sleep is a minimum time you will sleep for, beyond that you already have no guarantee about when the next instruction executes.


Spurious wakeups enters the chat.


Any reasonable sleep API will handle spurious wake ups under the hood and not return control.


It will also ignore those pesky signals and restart the sleep :)

On a more serious note, any reasonable sleep API should either report all wake ups, no matter the reason, or none except for the timeout expiration itself. Any additional "reasonable" filtering is a loaded footgun because different API users have different ideas of what is "reasonable" for them.


I was working through some ffmpeg timing stuff and the ai kept insisting on putting in sleep functions to try and "solve" it


Generative coding models are trained on GitHub. GitHub is completely full of inadvisable code. The model thinks this is normal.


Sort of by definition it is normal


If I were reviewing that code I would've asked if they tried yielding. Same effect, but doesn't delay when there is no one to yield to.


Or since C++20, just default operator<=>: https://en.cppreference.com/w/cpp/language/default_compariso...


Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: