HN user

mtklein

635 karma
Posts0
Comments103
View on HN
No posts found.

I don't think there is necessarily one ideal middle ground here. It still feels to me like what's best is a function that depends on who and when.

I see it as something like a personal gradient descent. You're working on a problem, there are solutions down there somewhere, and you can kind of feel the gradient of the tools-and-techniques ground around you. Any way you walk means you're investing time improving some skill or another. So you should go the way that personally feels to you will best get you moving in the direction that you want to go.

For some people it's obvious LLMs are competent coders, getting better, sticking around... and those people should lean into that gradient. For some people what's obvious is nearly the exact opposites of all that, and I'd encourage those people to also follow their gradient/heart/nose down the path of sharpening their personal traditional coding skills. Some people are in a relatively flat area where nothing is obvious, and need to explore and maybe just keep doing their best to hedge with a bit of both.

Yeah it makes me sad too. It's a one-way veil, no going back once I experienced the new magic.

Sometimes I pop back into a terminal, mkdir, git init, crack my knuckles to do some all-natural hacking and the sadness sets in before the hour is up... "why am I insisting on doing this the long way? I know this project would be better with me in a different role now."

Coding for me has always been a balance of process and ends. Getting done what I wanted done mattered most, but I can't pretend that I didn't also enjoy being the moving parts of that process. And I am most grateful for what I learned by throwing myself over and over against problems I didn't quite know how to solve. There's a satisfaction to doing a thing well that I always love being a part of, still do, anything, even doing the dishes or laundry.

And just recently with Opus I found myself having some really joyously manic days and weeks, making calls, picking tech, designing APIs, insisting on quality, keeping agents spinning. But Fable just kind of came in with "oh, I can do that all for you too. You can relax," and that was both exactly what I wanted and what started making me realize this had all finally caught up to me.

I never wanted to be management, but I can't unsee that I am more effective now playing Fable's boss than I was the last few months as TLM of a squad of Opuses, and that was more effective than just coding myself the way I love.

On about day 2 of using Fable I realized that the .vimrc I'd been maintaining for 15-20 years would probably never change again.

With Opus I still feel like I'm pair coding and want to get in there and make some changes myself, but working with Fable (even Fable managing Opus agents) had me in a completely different mindset, one where I realized I would just be getting in the way.

I have never seen Codex or Claude get manual memory management wrong. I used to be pretty fastidious about using leak sanitizer or other such tools to catch my own memory management issues, and while not quite useless, that sort of testing has dropped way down my list of worries the more I lean on LLMs. I am constantly surprised by how many formerly tedious or error prone tasks stopped being either of those, and I expect to see practice shift away from middle-safe languages like C++ to not just much more safe languages like Rust but surprisingly also to much less safe ones like C and platform specific assembly.

Part of that could be ABI constraints. There are some surprising calling convention differences between a vector and a struct or union with vectors in it, and they vary platform to platform. E.g. on ARM a struct with two 128-bit vectors will pass in two registers where on x86 it must pass via the stack.

Using __attribute__ to tweak calling conventions can often really clean this up, but that's just as obscure and non-portable as the problem it fixes. So you either end up writing weird non-portable code one way or weird non-portable code another... Code working with these types doesn't get to benefit from zero-cost abstraction to the degree we're used to with normal scalar code.

what I mean here about NaNs is that from a testing perspective, I want to be able to write a test that expects NaN in the same way that I write other expectations, and you can't do that with ==.

    assert(x == 7);    // fine
    assert(y == NaN);  // never true
    assert(y != y);    // this is what you meant
so this equiv() helper fixes that,
    assert(equiv(x, 7));    // fine
    assert(equiv(y, NaN));  // also fine
now, as far as treating NaNs equivalently, the IEEE 754 float format has a huge number of possible representations of NaN, and if you did something like a bitwise comparison, you might think that 0x7fc00000, 0x7f800001, 0xffc00000, 0x7fc0f00d were all different and not equivalent, but they're all NaNs, and I find that when I'm looking for a NaN, I very rarely care about exactly which one I'm looking at. So checking (x!=x && y!=y) admits any two NaNs as equivalent.

My preference in tests is a little different than just using IEEE 754 ==,

    _Bool equiv(float x, float y) {
        return (x <= y && y <= x)
            || (x != x && y != y);
    }
which both handles NaNs sensibly (all NaNs are equivalent) and won't warn about using == on floats. I find it also easy to remember how to write when starting a new project.
Waymo Safety Impact 4 months ago

I am also not looking forward to the system transitioning from "big experiment, burn money to make it good" to "established business unit, tweak it to death for incrementally more money / personal promotion." We're still in the honeymoon period and I very much expect to hate Waymo in 10 or 15 years when they reach a steady state.

If I remember correctly, the AVX2 feature set is a fairly direct upscale of SSE4.1 to 256 bit. Very few instructions even allowed interaction between the top and bottom 128 bits, I assume to make implementation on existing 128 bit vector units easier. And the most notable new things that AVX2 added beyond that widening, fp16 conversion and FMA support, are also present in NEON, so I wouldn't expect that to be the issue either.

So I'd bet the issue is either newness of the codebase, as the article suggests, or perhaps that it is harder to schedule the work in 256 bit chunks than 128. It's got to be easier when you've got more than enough NEON q registers to handle the xmms, harder when you've got only exactly enough to pair up for handling ymms?

This was a nice surprise when learning to code for NES, that I could write pretty much normal C and have it work on the 6502. A lot of tutorials warn you, "prepare for weird code" and this pretty much moots that.

I don't understand why this article invents and explains a phony ranged-float fix when the real fix from the footnotes would have been just as simple to explain. The deception needlessly undermines the main point of the article, which I completely agree with.

For a good long while at least, this flag was a signal for the browser to use CPU rendering, because of the overhead of GPU setup for rendering very changing content was too high.

My knowledge is dated and second hand though. New GPU APIs hopefully changed this!

I completely agree that technology in the last couple years has genuinely been fulfilling the promise established in my childhood sci-fi.

The other day, alone in a city I'd never been to before, I snapped a photo of a bistro's daily specials hand-written on a blackboard in Chinese, copied the text right out of the photo, translated it into English, learned how to pronounce the menu item I wanted, and ordered some dinner.

Two years ago this story would have been: notice the special board, realize I don't quite understand all the characters well enough to choose or order, and turn wistfully to the menu to hopefully find something familiar instead. Or skip the bistro and grab a pre-packaged sandwich at a convenience store.

My preferred way to compare floats as being interchangeably equivalent in unit tests is

    bool equiv(float x, float y) {
        return (x <= y && y <= x)
            || (x != x && y != y);
    }
This handles things like ±0 and NaNs (while NaNs can't be IEEE-754-equal per se, they're almost always interchangeable), and convinces -Wfloat-equal you kinda know what you're doing. Also everything visually lines up real neat and tidy, which I find makes it easy to remember.

Outside unit tests... I haven't really encountered many places where float equality is actually what I want to test. It's usually some < or <= condition instead.

GCC 15.1 1 year ago

Ah, I can confirm what I see elsewhere in the thread, this is no longer true in Clang. That first clang was Apple Clang 17---who knows what version that actually is---and here is Clang 20:

    $ /opt/homebrew/opt/llvm/bin/clang-20 -O1 -c union.c -o union.o && objdump -d union.o

    union.o: file format mach-o arm64

    Disassembly of section __TEXT,__text:

    0000000000000000 <ltmp0>:
           0: f900001f      str xzr, [x0]
           4: d65f03c0      ret

    0000000000000008 <_create_d>:
           8: f900001f      str xzr, [x0]
           c: d65f03c0      ret
GCC 15.1 1 year ago

I have always thought that punning through a union was legal in C but UB in C++, and that punning through incompatible pointer casting was UB in both.

I am basing this entirely on memory and the wikipedia article on type punning. I welcome extremely pedantic feedback.

GCC 15.1 1 year ago

This was my instinct too, until I got this little tickle in the back of my head that maybe I remembered that Clang was already acting like this, so maybe it won't be so bad. Notice 32-bit wzr vs 64-bit xzr:

    $ cat union.c && clang -O1 -c union.c -o union.o && objdump -d union.o
    union foo {
        float  f;
        double d;
    };

    void create_f(union foo *u) {
        *u = (union foo){0};
    }

    void create_d(union foo *u) {
        *u = (union foo){.d=0};
    }

    union.o: file format mach-o arm64

    Disassembly of section __TEXT,__text:

    0000000000000000 <ltmp0>:
           0: b900001f      str wzr, [x0]
           4: d65f03c0      ret

    0000000000000008 <_create_d>:
           8: f900001f      str xzr, [x0]
           c: d65f03c0      ret

It hadn't yet been at the time this program was in practice. I wager the enthusiasm for nudging was probably around its peak at the time we're talking, somewhere early 2010s?