HNHacker News
TopNewBestAskShowJobs

mtklein

664 karma · joined August 27, 2010

submissionscomments
mtklein··on Laptops create systems. Phones feed algorithms. The asymmetry determines power
I very much used to agree with this, but some time this summer the ChatGPT iOS app started to change this for me. I have definitely had days where I've felt as coding-creative as I can be on a laptop but instead just texting my AI interns to handle the execution while I'm out for a walk.
mtklein··on Walking around the compiler
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.
mtklein··on Patrick Winston: How to Speak (2018) [video]
You're not wrong that he wasn't physically fit, but he was also one of the most human people many of us in this thread have ever met.
mtklein··on What Does will-change In CSS Do?
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!

mtklein··on Linear scan register allocation on SSA
I found this especially nice working with large SIMD constants.
mtklein··on Dicing an Onion, the Mathematically Optimal Way
Okay, I'll bite: 2 and 5 are prime, this is the perfect fifth problem, only approximate solutions are possible. Make me wrong!
mtklein··on My AI skeptic friends are all nuts
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.

mtklein··on Comparing floating-point numbers (2012)
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.

mtklein··on GCC 15.1
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
mtklein··on GCC 15.1
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.

mtklein··on GCC 15.1
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
mtklein··on Coding Neon Kernels for the Cortex-A53
"The peer malus" - Frederic Beal
mtklein··on It's a knowledge problem Or is it?
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?
mtklein··on The Inevitability of the Borrow Checker
The idiom I am familiar with here is to make the constructor private and provide a public static factory method that allocates on the heap.
mtklein··on Unorm and SNORM to float, hardware edition
It can be fun to explore the interactions of unorm and float bit representations even when you have float instructions. E.g. if you bit-or a unorm8 into 0x47000000 (32768.0f) then subtract 32768.0f, you'll get a number very close to right, just a float multiply of (256/255.0f) away. Reordering the math so that the subtraction and multiply can become a single FMA is a fun homework exercise.

        union {
            int bits;
            float  f;
        } pun = {x}, scale = {0x47000000};
        pun.bits |= scale.bits;
        pun.f    -= scale.f;
        pun.f    *= (256/255.0f);
This basically amounts to a software implementation of int->float conversion instructions; sadly I have never found a spot where it's actually worth doing when you have those int->float instructions available already, even with the FMA as a single instruction.

It's also worth considering whether your application can handle approximate conversion. If you have a [0,255] unorm in x, x + (x>>7) or equivalently x + (x>0x7f) will round it to a [0,256] fixed-point value. Crucially, this rounding does handle 0x00 and 0xff inputs correctly. Once in fixed-point with a nice power-of-two divisor, you can play all sorts of tricks, either again making use of the bit representation of floats, using ARM fixed-point instructions, etc. If you've ever looked longingly at the pmulhrsw family of instructions, this is a ripe area to explore.

mtklein··on Safety Goggles for Alchemists
Looks a little more like std::bit_cast? (Of course, there's a large overlap between the two.)
mtklein··on Ask HN: Which books/resources to understand modern Assembler?
I'd suggest working incrementally from areas of your existing strength. Tweak whatever code base you are most familiar with, starting with a tiny change, and see how the assembly changes. I use objdump -d and git diff --no-index for this all the time.
mtklein··on Xr0: C but Safe
I could have been more precise when I said it's easy to fool yourself into thinking you know _any_ language better than you do... both rustc and ghc have made it abundantly and repeatedly clear that I don't know Rust and Haskell well, and the "if it compiles, it's probably correct" trust that develops from that frustration is a great asset to both those languages.

But those are exceptions: I've definitely felt like I've known more than I really did about at least C, C++, Java, Scheme, Python, Go, bash, Javascript, cron, and several assembly variants, and those platforms did little to stop me from learning about my inexperience in difficult, sometimes embarrassing, sometimes expensive ways.

I'm a big fan of more compile time analysis any way it can be done, and whatever can't at runtime. More types, more proofs, more fuzzing, more sanitizers, and also where possible, more restricted execution models than Turing-complete. Any code that can't prove itself correct is a serious liability. I just also like C... it's got many footguns, yes indeed, it's practically constructed out of only footguns, but there are only like 8 of them, and they're all right there waiting for you to get to know them. It's like having a few good sharp knives in your kitchen. You get to know each well, using them one at a time---and if any is missing from the block you pause everything and look around very carefully.

mtklein··on Xr0: C but Safe
I think this is actually one of the selling points of C over other languages. Certainly it's easy to fool yourself into thinking you know any language better than you do, but C genuinely is relatively tiny, and I think it's reasonable to expect to become familiar with almost everything in the language in a month or so of full-time work. After a decade of day job C++ coding, I'd not say the same for myself there.

Rust and C++ lately have been particularly quickly moving targets to keep up with, where C is still pretty much the same as C99. The compilers are better, their warnings are better, sanitizer modes are incredible, but the things you type into your text editor to express your program structure are still pretty much the same thing.

mtklein··on Rive Renderer for real-time vector graphics is now open source
Looking at the GPU tech used and the committer names in that repo, I'm pretty confident this will at very least compete with both Skia and Pathfinder. I'm not super familiar with Vello.

These are solid tech choices by people who know what they're doing... definitely worth the effort to check its performance and quality out for yourself.

These GPU-first renderers all have slightly different approaches and each probably occupies a best-in-class niche for some multidimensional space of quality x performance x hardware/driver support x ease of integration. If you're not happy with one of the four, the others are all worth checking out.

mtklein··on Life lessons from the death bed
One bit I find easy to lose sight of is that if I do spend my day cultivating wheat for flour for a cake, to make sure to eventually make and eat that cake, and not just spend all my time getting better at cultivating more and more wheat.
mtklein··on Werner Herzog on beer, yoga and what he would ask God
Might try Simulacron-3.
mtklein··on The Prosecutor's Fallacy (2018)
On the topic of finding Bayes' theorem intuitively, I can never remember it on its own, but starting from the joint probability P(A,B) for any arbitrary A and B always helps me:

    // A and B are arbitrary.
    P(A,B) = P(B,A)

    // These two make sense if I sound them out in words.
    P(A,B) = P(A) P(B|A)
    P(B,A) = P(B) P(A|B)

    // Combine to give Bayes' theorem.
    P(A) P(B|A) = P(B) P(A|B)
mtklein··on The Development of the C Language (2003)
I agree that (void)bla is desirably clear, pithy and unambiguous. And I like something like this if I'm dealing with functions with more than a couple unused parameters:

    static void unused_(int x, ...) { (void)x; }
    #define unused(...) unused_(0, __VA_ARGS__)
This way you can just have one `unused(foo,bar,baz,quux);` line at the top of the function listing all unused parameters.
mtklein··on GCC always assumes aligned pointer accesses (2020)
If you've got control over the type of the pointee and not just the pointer, __attribute__((packed)) can work for this.

    struct foo {                         int x; };
    struct bar { __attribute__((packed)) int x; };

    _Static_assert(_Alignof(struct foo) == 4, "");
    _Static_assert(_Alignof(struct bar) == 1, "");
mtklein··on C and C++ prioritize performance over correctness
Ordinarily you're at the whim of the optimizer whether calls in tail position are made in a way that grows the stack or keeps it constant. musttail guarantees that those calls can and are made in a way that does not let the stack grow unbounded, even without other conventional -On optimization. This makes the threaded interpreter design pattern safe from stack overflow, where it used to be you'd have to make sure you were optimizing and looking carefully at the output assembly.

If nothing else musttail aids testing and debugging. Unoptimized code uses a lot more stack, both because it hasn't spent the time to assign values to registers, but people often debug unoptimized code because having values live on the stack makes debugging easier. The combination of unoptimized code and calls in tail position not made in a way that keeps stack size constant means you hit stack overflow super easily. musttail means that problem is at least localized to the maximum stack use of each function, which is typically not a problem for small-step interpreters. Alternatives to musttail generally involve detecting somehow whether or not enough optimization was enabled and switching to a safer, slower interpreter if not positive... but that just means you're debug and optimized builds work totally differently, not at all ideal!

mtklein··on “Once” one-time concurrent initialization with an integer
My only major objection to C++ magic statics is that you can only use them for statics. Sometimes you find yourself with a task that needs to be done once per object instead of globally, and you've got to roll your own, and then you might as well use that same mechanism for globals too.

My minor objection to magic statics is that you don't have control over the size or packing of the control word, and it usually ends up being something awkwardly mid-sized like a size_t where normally you'd want it either as small as possible or cache-line sized.

I like to handle this something like this:

     void once(char _Atomic *flag, void (*fn)(void *ctx), void *ctx) {
         char st = atomic_load_explicit(flag, memory_order_acquire);
         if (st == 0 && atomic_compare_exchange_strong_explicit(flag, &st, 1,
                                                                memory_order_acquire,
                                                                memory_order_acquire)) {
             fn(ctx);
             atomic_store_explicit(flag, st=2, memory_order_release);
         }
         while (st != 2) { st = atomic_load_explicit(flag, memory_order_acquire); }
      }
There's a commented version here: https://github.com/mtklein/best/blob/main/once.c
mtklein··on Valgrind recieves a development microgrant from FUTO
Valgrind is like all the sanitizers (memory, thread, leak, address, undefined) all together, plus performance and memory profilers, all for uninstrumented code.

For me the real key is the last part. If you're happy building and testing with sanitizers from source, I don't think there's much more to gain by adding Valgrind to the mix, but if sanitizers aren't available or you find yourself with a binary and not source, I'd check out Valgrind.

mtklein··on Supreme Court strikes down affirmative action in college admissions
Hey dude, I was in your class, and I can say firsthand that you deserved to be there like any of us.

Almost everyone at a place like MIT feels some impostor syndrome, and I think maybe even for good bogus-mathematical-theorem reasons: if a place like MIT tries to select from the top few percent of a normal distribution, that new distribution after selection will look bottom-heavy (and feel that way around campus). We all knew those few incredible superstars who could run laps around us, but that doesn't mean you're not a star too.

mtklein··on The least interesting part about AVX-512 is the 512 bits vector width
Oh! Yeah! I do forget about “base” avx-512 sometimes. No one but HPC folks really ever had any proximity to that. In practice if you’ve got a computer that supports avx-512, it supports VL.
← PreviousPage 2 of 4Next →