Does anyone have a collection of these things?
Does anyone have a collection of these things?
bool is_leap_year(uint32_t y) { // Works for Gregorian years in range [0, 65535] return ((!(y & 3)) && ((y % 25 != 0) || !(y & 15))); }
But do you know what's not free? Memory accesses[1]. So when I'm optimizing things, I focus on making things more cache friendly.
[1] http://gec.di.uminho.pt/discip/minf/ac0102/1000gap_proc-mem_...
I'm amazed by the fact there is always someone who will say that such optimization are totally unnecessary.
What does this mean? Free? Optimisations are totally unnecessary because... instructions are free?
The implementation in TFA is probably on the order of 5x more efficient than a naive approach. This is time and energy as well. I don't understand what "free" means in this context.
Calendar operations are performed probably trillions of times every second across all types of computers. If you can make them more time- and energy-efficient, why wouldn't you?
If there's a problem with modern software it's too much bloat, not too much optimisation.
It's >3 machine instructions (and I admire the mathematical tricks included in the post), but it does do thousands of calculations fairly quickly :)
Not to mention that the final code is basically a giant WTF for anybody reading it. It will be an attractive nuisance that people will be drawn to, like moths to a flame, any time there is a bug around calendar operations.
> The world could run on older hardware if software optimization was a priority
How many people are rolling their own datetime code? This seems like a totally fine optimization to put into popular datetime libraries.
More broadly, it 100% depends on your workload. You'd be surprised at how many workloads are compute-bound, even today: LLM inference might be memory bound (thus how it's possible to get remotely good performance on a CPU), but training, esp. prefill, is very much not. And once you get out of LLMs, I'd say that most applications of ML tend to be compute bound.
And since you are talking about memory. Code also goes in memory. Shorter code is more cache friendly.
I don't see a use case where it matters for this particular application (it doesn't mean there isn't) but well targeted micro-optimizations absolutely matter.
How is cache / memory access relevant in a subroutine that performs a check on a 16bit number?
> Not to mention that the final code is basically a giant WTF for anybody reading it. It will be an attractive nuisance that people will be drawn to, like moths to a flame, any time there is a bug around calendar operations.
1: comments are your friend
2: a unit test can assert that this function is equivalent to the naive one in about half a millisecond.
I didn't find any way to get a compiler to generate a branchless version. I tried clang and GCC, both for amd64, with -O0, -O5, -Os, and for clang, -Oz.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110001
Binary searches on OpenZFS B-Tree nodes are faster in part because we did not wait for the compiler:
https://github.com/openzfs/zfs/commit/677c6f8457943fe5b56d7a...
Eliminating comparator function overhead via inlining is also a part of the improvement, which we would not have had because the OpenZFS code is not built with LTO, so even if the compiler fixes that bug, the patch will still have been useful.
https://graphics.stanford.edu/~seander/bithacks.html
It is not on the list, but #define CMP(X, Y) (((X) > (Y)) - ((X) < (Y))) is an efficient way to do generic comparisons for things that want UNIX-style comparators. If you compare the output against 0 to check for some form of greater than, less than or equality, the compiler should automatically simplify it. For example, CMP(X, Y) > 0 is simplified to (X > Y) by a compiler.
The signum(x) function that is equivalent to CMP(X, 0) can be done in 3 or 4 instructions depending on your architecture without any comparison operations:
https://www.cs.cornell.edu/courses/cs6120/2022sp/blog/supero...
It is such a famous example, that compilers probably optimize CMP(X, 0) to that, but I have not checked. Coincidentally, the expansion of CMP(X, 0) is on the bit hacks list.
There are a few more superoptimized mathematical operations listed here:
https://www2.cs.arizona.edu/~collberg/Teaching/553/2011/Reso...
Note that the assembly code appears to be for the Motorola 68000 processor and it makes use of flags that are set in edge cases to work.
Finally, there is a list of helpful macros for bit operations that originated in OpenSolaris (as far as I know) here:
https://github.com/freebsd/freebsd-src/blob/master/sys/cddl/...
There used to be an Open Solaris blog post on them, but Oracle has taken it down.
Enjoy!
The bigger issue is that probably you don't need to do leap-year checks very often so probably your leap-year check isn't the place to focus unless it's, like, sending a SQL query across a data center or something.
https://cr.yp.to/talks/2015.04.16/slides-djb-20150416-a4.pdf (though see https://blog.regehr.org/archives/1515: "This piece (...) explains why Daniel J. Bernstein’s talk, The death of optimizing compilers (audio [http://cr.yp.to/talks/2015.04.16/audio.ogg]) is wrong", citing https://news.ycombinator.com/item?id=9397169)
https://blog.royalsloth.eu/posts/the-compiler-will-optimize-...
http://lua-users.org/lists/lua-l/2011-02/msg00742.html
https://web.archive.org/web/20150213004932/http://x264dev.mu...
They'll "optimize" your code by deleting it. They'll "prove" your null/overflow checks are useless and just delete them. Then they'll "prove" your entire function is useless or undefined and just "optimize" it to a no-op or something. Make enough things undefined and maybe they'll turn the main function into a no-op.
In languages like C, people are well advised to disable some problematic optimizations and explicitly force the compiler to assume some implementation details to make things sane.
And I call this thing “bit gymnastics”.
https://youtube.com/watch?v=PpaQrzoDW2I
It's hard to imagine being in a situation where the cost of the additional "year divisible by 100" check, or even the "year divisible by 400" check is too much to bear, and it's trivial enough that the developer overhead is negligible, but you never know when you need those extra handful of bytes of memory I guess...
You aren't going to beat the compiler if you have to consider a wide range of inputs and outputs but that isn't a typical setting and you can actually beat them. Even in general settings this can be true because it's still a really hard problem for the compiler to infer things you might know. That's why C++ has all those compiler hints and why people optimize with gcc flags other than -O.
It's often easy to beat Blas in matrix multiplication if you know some conditions on that matrix. Because Blas will check to find the best algo first but might not know (you could call directly of course and there you likely won't win, but you're competing against a person not the compiler).
Never over estimate the compiler. The work the PL people do is unquestionably useful but they'll also be the first to say you can beat it.
You should always do what Knuth suggested (the often misunderstood "premature optimization" quote) and get the profile.
For example:
if (p == NULL) return;
if (p == NULL) doSomething();
It is safe to delete the second one. Even if it is not deleted, it will never be executed.What is problematic is when they remove something like memset() right before a free operation, when the memset() is needed to sanitize sensitive data like encryption keys. There are ways of forcing compilers to retain the memset(), such as using functions designed not to be optimized out, such as explicit_bzero(). You can see how we took care of this problem in OpenZFS here:
> modern CPUs are so fast that instructions are almost free.
Please don't.These things compound. You especially need to consider typical computer usage involves using more than one application at a time. There's a tragedy of the commons issue that's often ignored. It can be if you're optimizing your code (you're minimizing your share!) but it can't be if you're not.
I guarantee you we'd have a lot of faster things if people invested even a little time (these also compound :). Two great examples might be Llama.cpp and FlashAttention. Both of these have had a huge impact of people (among a number of other works) but don't get nearly the same attention as other stuff. These are popular instances but I promise you that there's a million problems like these waiting to be solved. It's just not flashy, but hey plumbers and garbagemen are pretty critical jobs too
char *allocate_a_string_please(int n)
{
if (n + 1 < n)
return 0; // overflow
return malloc(n + 1); // space for the NUL
}
This code seems okay at first glance, it's a simple integer overflow check that makes sense to anyone who reads it. The addition will overflow when n equals INT_MAX, it's going to wrap around and the function will return NULL. Reasonable.Unfortunately, we cannot have nice things because of optimizing compilers and the holy C standard.
The compiler "knows" that signed integer overflow is undefined. In practice, it just assumes that integer overflow cannot ever happen and uses this "fact" to "optimize" this program. Since signed integers "cannot" overflow, it "proves" that the condition always evaluates to false. This leads it to conclude that both the condition and the consequent are dead code.
Then it just deletes the safety check and introduces potential security vulnerabilities into the software.
They had to add literal compiler builtins to let people detect overflow conditions and make the compiler actually generate the code they want it to generate.
Fighting the compiler's assumptions and axioms gets annoying at some point and people eventually discover the mercy of compiler flags such as -fwrapv and -fno-strict-aliasing. Anyone doing systems programming with strict aliasing enabled is probably doing it wrong. Can't even cast pointers without the compiler screwing things up.
Not true if you use astronomical year numbering: https://en.m.wikipedia.org/wiki/Astronomical_year_numbering
Which is arguably the right thing to do outside of specific domains (such as history) in which BCE is entrenched
If your software really has to display years in BCE, I think the cleanest way is store it as astronomical year numbering internally, then convert to CE/BCE on output
Without that design constraint, testing for leap years becomes locale-dependent and very complex indeed.
No one does this
The optimizations that compilers can achieve kind of amaze me.
Indeed, the latest version of cal from util-linux keeps it simple in the C source:
return ( !(year % 4) && (year % 100) ) || !(year % 400);
https://github.com/util-linux/util-linux/blob/v2.41/misc-uti...There is no silver bullet.
It was that generally the fast hardware multiplication operations in ALUs didn't have very many bits in the register word length, so multiplications of wider words had to be done with library functions that did long multiplication in (say) base 256.
So this code in the headlined article would not be "three instructions" but three calls to internal helper library functions used by the compiler for long-word multiplication, comparison, and bitwise AND; not markedly more optimal than three internal helper function calls for the three original modulo operations, and in fact less optimal than the bit-twiddled modulo-powers-of-2 version found halfway down the headlined article, which would only need check the least significant byte and not call library functions for two of the 32-bit modulo operations.
Bonus points to anyone who remembers the helper function names in Microsoft BASIC's runtime library straight off the top of xyr head. It is probably a good thing that I finally seem to have forgotten them. (-: They all began with "B$" as I recall.
Everything before the introduction of the gregorian calendar is moot:
"In 1582, the pope suggested that the whole of Europe skip ten days to be in sync with the new calendar. Several religious European kingdoms obeyed and jumped from October 4 to October 15."
So you cannot use any date recorded before that time for calculations.
And before that it gets even more random:
"The priests’ observations of the lunar cycles were not accurate. They also deliberately avoided leap years over superstitions. Things got worse when they started receiving bribes to declare a year longer or shorter than necessary. Some years were so long that an extra month called Intercalaris or Mercedonius was added."
When the Julian calendar was really adopted I don't know. Certainly not 0001-01-01. And of course it varies by country like Gregorian.
At work we had discussions what date format to use in our product. It's for trained users only (but not IT people), English UI only, but used on several continents. Our regulatory expert propsed ISO8601. I did not agree, because that is not used anywhere in daily life except by 8 millions Swedes. I voted 15-Apr-2025 is much less prone to human error. (None of us "won". Different formats in different places still...)
However, in general, I think proleptic Gregorian is simpler. But in astronomy do what the astronomers do. And in history, dates between 1582 and 1923 (inclusive), you really need to explicitly mark the date as Gregorian or Julian, or have contextual information (such as the country) to determine which one to use.
1923 because that was when Greece switched from Julian to Gregorian, the last country to officially do so. Although various other countries in the Middle East and Asia adopted the Gregorian calendar more recently than 1923 - e.g. Saudi Arabia switched from the Islamic calendar to the Gregorian for most commercial purposes in 2016, and for most government purposes in 2023 - those later adoptions aren’t relevant to Julian-Gregorian cutover since they weren’t moving from Julian to Gregorian, they were moving from something non-Western to Gregorian
Large chunks of the Eastern Orthodox Church still use the Julian calendar for religious purposes; other parts theoretically use a calendar called “Revised Julian” which is identical to Gregorian until 2800 and different thereafter - although I wonder if humanity (and those churches) are still around in 2800, will they actually deviate from Gregorian at that point, or will they decide not to after all, or forget that they were officially supposed to
Warning: This may increase or decrease your popularity with fellow programmers, depending on how lucky you are in encountering problems where they make an important performance difference rather than a readability problem for people who have not deeply internalized bit twiddling.
Multiply and mask for varrious purposes is a thing I commonly use in my own code-- it's much more attractive now that it was decades ago because almost all computers we target these days have extremely fast multipliers.
These full-with logic operations and multipliers give you kind of a very parallel computer packed into a single instruction. The only problem is that it's a little tricky to program. :)
At least this one was easy to explain mechanically. Some bit hacks require p-adic numbers and other elements of number theory to explain.
>The Julian calendar was proposed in 46 BC by (and takes its name from) Julius Caesar, as a reform of the earlier Roman calendar, which was largely a lunisolar one.[2] It took effect on 1 January 45 BC, by his edict.
Not knowing the year seems unhinged somehow.
Does it matter? MM-DD-YYYY is used in America and makes DD-MM-YYYY ambiguous, but as far as I know nobody uses YYYY-DD-MM, so ISO8601 should be perfectly fine, especially if users are trained. Besides, if you're not used to it, starting with the year forces you to think, which is desirable if you want to avoid human error.
I guess this only applies when the compiler knows what version of > you are using?
Eg it might not work in C++ when < and > are overloaded for eg strings?
Well, we can actually multiply long binary numbers asymptotically faster than Ancient Egyptians.
>“So, it’s a bug in Lotus 123?”
>“Yeah, but probably an intentional one. Lotus had to fit in 640K. That’s not a lot of memory. If you ignore 1900, you can figure out if a given year is a leap year just by looking to see if the rightmost two bits are zero. That’s really fast and easy. The Lotus guys probably figured it didn’t matter to be wrong for those two months way in the past. It looks like the Basic guys wanted to be anal about those two months, so they moved the epoch one day back.”
https://www.joelonsoftware.com/2006/06/16/my-first-billg-rev...
Whether that matters comes down to how this function integrates into the rest of the program.
It is very very hard to write C without mistakes.
When not-actually-dead code gets removed, the consequences of many mistakes get orders of magnitudes worse.
This is the kind of optimization that makes you need a 1.5MHz CPU instead of a 1MHz CPU, but saves devs weeks of effort. It's the kind of thing you give up when you move optimization from priority 1 to 2, or from 2 to 3. It would still run blazingly fast on a 30 year old computer. It's a perfectly good tradeoff.
The stuff that bogs down modern hardware is optimization being priority 8 or not even on the list of considerations.
if ((y % 25) != 0) return true;
was actually checking for different from 0 (which in hindsight makes also sense because the century years by default are not leap unless they divide by 400)I'd be surprised if someone found this solution before, as it seems both relatively difficult to find and a small optimisation.
For more on this, I recommend reading and implementing a function that calculates the day of the week [1]. Then you can join me in the special insanity hell of people that were trying to deal with human calendars.
And then you should implement a test case for the dates between Thursday 4 October 1582 and Friday 15 October 1582 :)
[1] https://en.m.wikipedia.org/wiki/Determination_of_the_day_of_...
However, due to a mistranslation the Roman pontifices got it wrong at the introduction of the Julian calendar. The Romans counted inclusively, which means: counting with both the start and end included. (That is why Christians say in a literal translation from Latin that Jesus has risen on the third day, even though he died on a Friday and is said to have risen two days later, on the next Sunday.)
In the first years of the Julian calendar, the Roman pontifices inserted a leap day “every fourth year”, which in their way of counting means: every 3 years. Authors differ on exactly which years were leap years. The error got corrected under Augustus by skipping a few leap years and then following the “every 4 years” rule since either AD 4 or AD 8. See the explanation and the table in https://en.wikipedia.org/wiki/Julian_calendar#Leap_year_erro...
Also note that at the time, years were mostly identified by the names of the consuls rather than by a number. Historians might use numbers, counting from when they thought Rome was founded (Ab urbe condita), but of course they differed among each other on when that was. The chronology by Atticus and Varro, which placed the founding of the city on 21 April 753 BC in the proleptic Julian calendar, was not the only one.
int main() {
// this should be properly formatted
return 0;
};
It’s also worth calling out angr as an interface between capstone and z3, which can take this to another level.
Terrible nitpick, but this is actually 3 operations, not instructions. On x86 you get 4:
is_leap_year_fast:
imul eax, edi, 1073750999
and eax, -1073614833
cmp eax, 126977
setb al
ret
On ARM you get a bit more due to instruction encoding: is_leap_year_fast:
ldr r1, .LCPI0_0
mul r0, r0, r1
ldr r1, .LCPI0_1
and r1, r0, r1
mov r0, #0
cmp r1, #126976
movwls r0, #1
bx lr
.LCPI0_0:
.long 1073750999
.LCPI0_1:
.long 3221352463
Compiler explorer reference: https://godbolt.org/z/7ajYqbT9zHere's a 2018 example.
https://github.com/mruby/mruby/commit/180f39bf4c5246ff77ef71...
https://github.com/mruby/mruby/issues/4062
while (l >= bsiz - blen) {
bsiz *= 2;
if (bsiz < 0)
mrb_raise(mrb, E_ARGUMENT_ERROR, "too big specifier");
}
> bsiz*=2 can become negative.> However with -O2 the mrb_raise is never triggered, since bsiz is a signed integer.
> Signed integer overflows are undefined behaviour and thus gcc removes the check.
People have even categorized this as a compiler vulnerability.
https://www.kb.cert.org/vuls/id/162289
> C compilers may silently discard some wraparound checks
And they aren't wrong.
The programmer wrote reasonable code that makes sense and perfectly aligns with their mental model of the machine.
The compiler took this code and screwed it up because it violates compiler assumptions about some abstract C machine nobody really cares about.
if(is_leap_year_fast()) {...}
Then the ret would obviously go away and the setb wouldn't be necessary as it could generate directly a conditional jmp from the result of the cmp.There's also the difference between multiplying by a hard-coded value, which can be implemented with shifts and adds, and multiplying two variables, which has to be done with an algorithm.
The 8086 did have multiply instructions, but they were implemented as a loop in the microcode, adding the multiplicand, or not, once for each bit in the multiplier. More at https://www.righto.com/2023/03/8086-multiplication-microcode.... Multiplying by a fixed value using shifts and adds could be faster.
The prototype ARM1 did not have a multiply instruction. The architecture does have a barrel shifter which can shift one of the operands by any number of bits. For a fixed multiplication, it's possible to compute multiplying by a power of two, by (power of two plus 1), or by (power of two minus 1) in a single instruction. The latter is why ARM has both a SUB (subtract) instruction, computing rd := rs1 - Operand2, and a RSB (Reverse SuBtract) instruction, computing rd := Operand2 - rs1. The second operand goes through the barrel shifter, allowing you to write an instruction like 'RSB R0, R1, R1, #4' meaning 'R0 := (R1 << 4) - R1', or in other words '(R1 * 16) - R1', or R1 * 15.
ARMv2 added in MUL and MLA (MuLtiply and Accumulate) instructions. The hardware ARM2 implementation uses a Booth's encoder to multiply 2 bits at a time, taking up to 16 cycles for 32 bits. It can exit early if the remaining bits are all 0s.
Later ARM cores implemented an optional wider multiplier (that's the 'M' in 'ARM7TDMI', for example) that could multiply more bits at a time, therefore executing in fewer cycles. I believe ARM7TDMI was 8-bit, completing in up to 4 cycles (again, offering early exit). Modern ARM cores can do 64-bit multiplies in a single cycle.
Interesting.
And then I literally said “hostile” in my comment and you didn’t find it toxic? Strange but it’s okay :)
is_leap_year(unsigned int):
xor eax, eax
test dil, 3
jne .L1
imul edi, edi, -1030792151
mov eax, 1
mov edx, edi
ror edx, 2
cmp edx, 42949672
ja .L1
ror edi, 4
cmp edi, 10737418
setbe al
.L1:
ret
I have been known to write patches converting signed integers to unsigned integers in places where signed arithmetic makes no sense.
Even if you only need one of cosf() or sinf(), many CPUs calculate both values at the same time, so taking the other is free. If you only need single precision values, you can do this in double precision to avoid much of the errors you would get by doing this in single precision.
This trick can be used to accelerate the RoPE relative positional encoding calculations used in inference for llama 3 and likely others. I have done this and seen a measurable speed up, although these calculations are such a small part of inference that it was a small improvement.
The problem is, which "specific" year? The English were using "old-style" dates long after 1582. Better not to try to solve this intractable problem in software, but instead annotate every old date you receive with its correct calendar, which may even be a proleptic Gregorian calendar in some fields of study.
(How do you determine the correct calendar? Through careful inspection of context! Alas, people writing the dates rarely indicated this, and later readers tend to get the calendars hopelessly mangled up. Not to mention the changes in the start of the year. At least the day of week can act as an indicator, when available.)
static int leap_year(const struct cal_control *ctl, int32_t year)
{
if (year <= ctl->reform_year)
return !(year % 4);
return ( !(year % 4) && (year % 100) ) || !(year % 400);
}
Where reform_year is the year the Gregorian calendar was adopted in the specific context specified (defaults to 1752 which is the year it was adopted by GB and therefore also the US).So it does account for Julian dates.
OK, great. 16 years into my career, and I've never needed to count the number of parameters of a JavaScript function. And if you needed to, that wasn't going to be the property you'd read anyway.
It turns out that multiplication in modern ALUs is very different. The Pentium, for instance, does multiplication using base-8, not base-2, cutting the number of additions by a factor of 3. It also uses Booth's algorithm, so much of the time it is subtracting, not adding.
$ cal 9 1752
September 1752
Su Mo Tu We Th Fr Sa
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Daniel Berlin's https://news.ycombinator.com/item?id=9397169 does kind of disagree, saying, "If GCC didn't beat an expert at optimizing interpreter loops, it was because they didn't file a bug and give us code to optimize," but his actual example is the CPython interpreter loop, which is light-years from the kind of hand-optimized assembly interpreter Mike Pall's post is talking about, and moreover it wasn't feeding an interpreter loop to GCC but rather replacing interpretation with run-time compilation. Mostly what he disagrees about is the same thing Regehr disagrees about: whether there's enough code in the category of "not worth hand-optimizing but still runs often enough to matter", not whether you can beat a compiler by hand-optimizing your code. On the contrary, he brings up whole categories of code where compilers can't hope to compete with hand-optimization, such as numerical algorithms where optimization requires sacrificing numerical stability. mpweiher's comment in response discusses other scenarios where compilers can't hope to compete, like systems-level optimization.
It's worth reading the comments by haberman and Mike Pall in the HN thread there where they correct Berlin about LuaJIT, and kjksf also points out a number of widely-used libraries that got 2–4× speedups over optimized C by hand-optimizing the assembly: libjpeg-turbo, Skia, and ffmpeg. It'd be interesting to see if the intervening 10 years have changed the situation, because GCC and LLVM have improved in that time, but I doubt they've improved by even 50%, much less 300%.
$ ncal -sIT 10 1582
October 1582
Mo 1 18 25
Tu 2 19 26
We 3 20 27
Th 4 21 28
Fr 15 22 29
Sa 16 23 30
Su 17 24 31
France apparently took a couple months to get on board (or maybe just to find out): $ncal -sFR 12 1582
December 1582
Mo 3 20 27
Tu 4 21 28
We 5 22 29
Th 6 23 30
Fr 7 24 31
Sa 1 8 25
Su 2 9 26
`ncal -p` gives a list of the country codes it accepts. (These are current countries so it's a bit ahistorical for, say, Germany.)Sadly they don't implement the weird thing Sweden did in the early 18th century: https://en.wikipedia.org/wiki/Swedish_calendar
https://godbolt.org/z/nGbPhz86q
If you did not inline the operator overloads and had them in another compilation unit, do not expect this to simplify (unless you use LTO).
If you have compound comparators in the operator overloads (such that on equality in one field, it considers a second for a tie breaker), I would not expect it to simplify, although the compiler could surprise me.
https://github.com/openzfs/spl/commit/8fc851b7b5315c9cae9255...
Jason had noticed that GCC’s assembly output did not match the original macro when looking for a solution to the unsigned integer overflow warning that a PaX GCC plugin had output (erroneously in my opinion). He had conjectured we could safely adopt GCC’s version as a workaround. I gave him the proof of correctness for the commit message and it was accepted into ZFS. As you can see from the proof, deriving that from the original required 4 steps. I assume that GCC had gone through a similar process to derive its output.
>> You especially need to consider typical computer usage involves using more than one application at a time. There's a tragedy of the commons issue
These resources include: - disk/ssd/long term memory
- RAM/System memory
- Cache
BUT ALSO
- Registers
- CPU Cores
- Busses/Lanes/Bandwith
- Locks
- Network
My point is that I/O only dominates when you're actually acting efficiently. This is dominating in the case of measuring a single operating program.You're forgetting that when multiple programs are running that there's a lot more going on. There's a lot more communication going on too. The caches are super tiny and in high competition. To handle interlacing all those instructions. Even a program's niceness can dramatically change total performance. This is especially true when we're talking about unoptimized programs because all those little things that the OS has to manage pile up.
Get out your computer architecture book and do a skim to refresh. Even Knuth's Book (s)[1] discuss much of this because to write good programs you gotta understand the environment they're running in. Otherwise I'd be like trying to build a car but not knowing if you're building it for the city, Antarctica, or even the moon. The environment is critical to the assumptions you can make.
I guess on all those crappiest of 32 bit devices with 2.6 based kernels (which are practically unupgradeable due to vendor patches[1]) which will never make it to 2100 to the "year divisible by 100" check, because their time_t overflows in 2038.
On the plus side, that overflow turns the date into 1901, so perhaps they won't actually blow up due to leapyear bugs for another couple of years? That'll be a fun vintage computing archeological bug fix for someone.
[1] I am at least partly responsible for one of these products from a failed hardware startup I work at in 2012/13.
The issue of signed versus unsigned is tangential at best. There are good arguments in favor of the use of signed and unsigned integers. Neither type should cause compilers to screw code up beyond recognition.
The fact is signed integer overflow makes sense to programmers and works just fine on the machines people care to write code for. Nobody really cares that the C standard says signed integer overflow is undefined. That's just an excuse. If it's undefined, then simply define it.
Of course you can test for INT_MAX. The problem is you have to somehow know that you must do it that way instead of trying to observe the actual overflow. People tend to learn that sort of knowledge by being burned by optimizing compilers. I'd very much rather not have adversarial compilers instead.