HNHacker News
TopNewBestAskShowJobs

haberman

20,594 karma · joined May 11, 2009

submissionscomments
haberman··on An AI agent published a hit piece on me – more things have happened
> So many of our foundational institutions – hiring, journalism, law, public discourse – are built on the assumption that reputation is hard to build and hard to destroy. That every action can be traced to an individual, and that bad behavior can be held accountable. That the internet, which we all rely on to communicate and learn about the world and about each other, can be relied on as a source of collective social truth. [...] The rise of untraceable, autonomous, and now malicious AI agents on the internet threatens this entire system.

I disagree. While AI certainly acts as a force multiplier, all of these dynamics were already in play.

It was already possible to make an anonymous (or not-so-anonymous) account that circulated personal attacks and innuendo, to make hyperbolic accusations and inflated claims of harm.

It's especially ironic that the paragraph above talks about how it's good when "bad behavior can be held accountable." The AI could argue that this is exactly what it's doing, holding Shambaugh's "bad behavior" accountable. It is precisely this impulse -- the desire to punish bad behavior by means of public accusation -- that the AI was indulging or emulating when it wrote its blog post.

What if the blog post had been written by a human rather than an AI? Would that make it justified? I think the answer is no. The problem here is not the AI authorship, but the actual conduct, which is an attempt to drag a person's reputation through mudslinging, mind-reading, impugning someone's motive and character, etc. in a manner that was dramatically disproportionate to the perceived offense.

haberman··on Open source is not about you (2018)
> he eventually needed to write bluntly

Is there a history of that here? Were there earlier clear statements of expectations (like CONTRIBUTING.md) that expressed the same expectations, but in a straightforward way, that people just willfully disregarded?

I don't mean to "ding" anybody, I mostly just felt bad that things had gotten to the point where the author was so frustrated. I completely agree that project owners have the right to set whatever terms they want, and should not suffer grief for standing by those terms.

haberman··on Open source is not about you (2018)
I don't see anything wrong with the way he expressed himself, and I think his point is totally legitimate. I mostly just felt bad that he experienced so much grief about it, on account of a gift he was offering to the world.
haberman··on Open source is not about you (2018)
If I'm criticizing the linked post, then I'm also criticizing myself, because I could easily imagine having written it.
haberman··on Open source is not about you (2018)
Lately I'm seeing more and more value in writing down expectations explicitly, especially when people's implicit assumptions about those expectations diverge.

The linked gist seems to mostly be describing a misalignment between the expectations of the project owners and its users. I don't know the context, but it seems to have been written in frustration. It does articulate a set of expectations, but it is written in a defensive and exasperated tone.

If I found myself in a situation like that today, I would write a CONTRIBUTING.md file in the project root that describes my expectations (eg. PRs are / are not welcome, decisions about the project are made in X fashion, etc.) in a dispassionate way. If users expressed expectations that were misaligned with my intentions, I would simply point them to CONTRIBUTING.md and close off the discussion. I would try to take this step long before I had the level of frustration that is expressed in the gist.

I don't say this to criticize the linked post; I've only recently come to this understanding. But it seems like a healthier approach than to let frustration and resentment grow over time.

haberman··on C isn't a programming language anymore (2022)
More concretely, I think the magic lies in these two properties:

1. Conservation of mass: the amount of C code you put in will be pretty close to the amount of machine code you get out. Aside from the preprocessor, which is very obviously expanding macros, there are almost no features of C that will take a small amount of code and expand it to a large amount of output. This makes some things annoyingly verbose to code in C (eg. string manipulation), but that annoyance is reflecting a true fact of machine code, which is that it cannot handle strings very easily.

2. Conservation of energy: the only work that will be performed is the code that you put into your program. There is no "supervisor" performing work on the side (garbage collection, stack checking, context switching), on your behalf. From a practical perspective, this means that the machine code produced by a C compiler is standalone, and can be called from any runtime without needing a special environment to be set up. This is what makes C such a good language for implementing garbage collection, stack checking, context switching, etc.

There are some exceptions to both of these principles. Auto-vectorizing compilers can produce large amounts of output from small amounts of input. Some C compilers do support stack checking (eg. `-fstack-check`). Some implementations of C will perform garbage collection (eg. Boehm, Fil-C). For dynamically linked executables, the PLT stubs will perform hash table lookups the first time you call a function. The point is that C makes it very possible to avoid all of these things, which has made it a great technology for programming close to the machine.

Some languages excel at one but not the other. Byte-code oriented languages generally do well at (1): for example, Java .class files are usually pretty lean, as the byte-code semantics are pretty close to the Java langauge. Go is also pretty good at (1). Languages like C++ or Rust are generally good at (2), but have much larger binaries on average than C thanks to generics, exceptions/panics, and other features. C is one of the few languages I've seen that does both (1) and (2) well.

haberman··on Fighting Fire with Fire: Scalable Oral Exams
The rate of college attendance has increased dramatically in the last 250 years, and especially in the last 75.

In 1789 there were 1,000 enrolled college students total, in a country of 2.8M. In 2025, it is 19M students in a country of 340M. https://educationalpolicy.org/wp-content/uploads/2025/11/251...

In 1950, 5.5% of adults ages 25-34 had completed a 4 year college degree. In 2018, it was 39%. https://www.highereddatastories.com/2019/08/changes-in-educa...

With attendance increasing at this rate (not to mention the exploding costs of tuition), it seems possible that the methods need to change as well.

haberman··on Python 3.15’s interpreter for Windows x86-64 should hopefully be 15% faster
I’ll repeat what I said at that time: one of the benefits of the new design is that it’s less vulnerable to the whims of the optimizer: https://news.ycombinator.com/item?id=43322451

If getting the optimal code is relying on getting a pile of heuristics to go in your favor, you’re more vulnerable to the possibility that someday the heuristics will go the other way. Tail duplication is what we want in case, but it’s possible that a future version of the compiler could decide that it’s not desired because of the increased code size.

With the new design, the Python interpreter can express the desired shape of the machine code more directly, leaving it less vulnerable to the whims of the optimizer.

haberman··on OpenSCAD is kinda neat
A long time ago I read that CadQuery has a fundamentally more powerful geometry kernel than OpenSCAD, so I dropped any attempt to try OpenSCAD.

Years later, I never actually got the hang of CadQuery, and I'm wondering if it was a mistake to write off OpenSCAD.

I am pretty new to CAD, so I don't actually know when I would run into OpenSCAD's limitations.

haberman··on Oracle made a $300B bet on OpenAI. It's paying the price
> Hotspot is the choice for high performance programs. Approaching its performance even with C++ requires a dedicated team of experts.

It's very surprising to hear you say this, as it's so contrary to my experience.

From the smallest programs (Computer Language Benchmarks Game) to pretty big programs (web browsers), from low-level programs (OS kernels) to high-level programs (GUI Applications), from short-lived programs (command-line utilities) to long-lived programs (database servers), it's hard to think of a single segment where even average Java programs will out-perform average C, C++, or Rust programs.

I hadn't heard of QuestDB before, but it sounds like it's written in zero-GC Java using manual memory management. That's pretty unusual for Java, and would require a team of experts to pull off, I'd think. It also sounds like it drops to C++ and Rust for performance-critical tasks.

haberman··on Garbage collection for Rust: The finalizer frontier
I agree, but in my experience arena allocation in Rust leaves something to be desired. I wrote something about this here: https://blog.reverberate.org/2021/12/19/arenas-and-rust.html

I was previously excited about this project which proposed to support arena allocation in the language in a more fundamental way: https://www.sophiajt.com/search-for-easier-safe-systems-prog...

That effort was focused primarily on learnability and teachability, but it seems like more fundamental arena support could help even for experienced devs if it made patterns like linked lists fundamentally easier to work with.

haberman··on Python 3.14 is here. How fast is it?
Good to know! Thanks for confirming. Yes, I would guess that the tail call interpreter explains part of the difference between 3.13 and 3.14. Previously the overall improvement to the interpreter has been measured at 1-5%, or even 10-15% depending on the compiler version you are using: https://blog.nelhage.com/post/cpython-tail-call/

If your benchmark setup is easy to re-run, it would be awesome to see numbers that compare the tail call interpreter to the build where it is disabled, to isolate how much improvement is due to that.

haberman··on Python 3.14 is here. How fast is it?
Do any of these tests measure the new experimental tail call interpreter (https://docs.python.org/3.14/using/configure.html#cmdoption-...)?

I couldn't find any note of it, so I would assume not.

It would be interesting to see how the tail call interpreter compares to the other variants.

haberman··on Memory access is O(N^[1/3])
The article says exactly this in bold at the bottom:

> If you can break up a task into many parts, each of which is highly local, then memory access in each part will be O(1). GPUs are already often very good at getting precisely these kinds of efficiencies. But if the task requires a lot of memory interdependencies, then you will get lots of O(N^⅓) terms. An open problem is coming up with mathematical models of computation that are simple but do a good job of capturing these nuances.

haberman··on Why I chose Lua for this blog
LuaJIT bucks the trend of slow-warmup JITs. It is extremely quick to compile and load, and its interpreter is very fast -- faster than the JIT-compiled code from LuaJIT v1 IIRC, and certainly faster than the interpreter of Lua.

It wasn't until LuaJIT that I realized that JIT didn't inherently have to be these slow lumbering beasts that take hundreds of milliseconds just to wake from their slumber.

haberman··on If all the world were a monorepo
This was an interesting article, but it made me even more interested in the author's larger take on R as a language:

> In the years since, my discomfort has given away to fascination. I’ve come to respect R’s bold choices, its clarity of focus, and the R community’s continued confidence to ‘do their own thing’.

I would love to see a follow-up article about the key insights that the author took away from diving more deeply into R.

haberman··on WASM 3.0 Completed
These features look compelling. When will they all be available in mainstream browsers?
haberman··on Protobuffers Are Wrong (2018)
I advocated for PrefixVarint (which seems equivalent to vint64 ) for WebAssembly, but it was decided against, in favor of LEB128: https://github.com/WebAssembly/design/issues/601

The recent CREL format for ELF also uses the more established LEB128: https://news.ycombinator.com/item?id=41222021

At this point I don't feel like I have a clear opinion about whether PrefixVarint is worth it, compared with LEB128.

haberman··on F-35 pilot held 50-minute airborne conference call with engineers before crash
For me, that was a moment when I realized that the received wisdom about military things can be just completely wrong.

I had considered myself to be reasonably informed about the F-35, and how "everyone knows" it's a boondoggle. I think this started with a long-form article I read in 2013, "How the U.S. and Its Allies Got Stuck with the World’s Worst New Warplane": https://medium.com/war-is-boring/fd-how-the-u-s-and-its-alli...

Here is the HN discussion at the time, full of confident assertions that the F-35 is useless: https://news.ycombinator.com/item?id=6211029

Fast forward to this year, when Israel's F-35s operated over Iran with total impunity. Not a single plane lost AFAIK.

haberman··on Starship's Tenth Flight Test
Landed on target in the Indian Ocean! Engines relit successfully and it touched down vertically (and then promptly exploded, which I guess was the plan :)
haberman··on Why is choral music harder to appreciate?
Ha, I don't know much about Dolby Atmos and spacial placement. But from prior experience I'm somewhat skeptical about what this kind of clever DSP can do for choral music.

For example, when I learned about convolution reverb, and how it should theoretically be able to simulate the unique reverb pattern of any room, I was initially excited about the possibilities. But after trying it I was underwhelmed.

That said, I'm open to being convinced. If you know of any compelling demos of this kind of spatial placement, I'd be interested to see.

I had not heard of Nonsuch Palace, despite having a passing interest in Henry VIII and certainly a large interest in Tallis! Is it thought that Spem was performed there?

haberman··on Why is choral music harder to appreciate?
I completely agree that one-per-part singing really brings out the beauty in 16th century choral music. I sing in a choir that specializes in music of this period, and while our live performances usually use two singers per part to fill a room, our recordings are more often one-per-part with relatively close micing.

We do record in churches because we like the reverb, so it's not quite the dry studio sound you're describing, but we do prioritize a clear sound stage where all of the parts can be clearly heard.

We've found that a Blumlein mic configuration (two figure-8 pattern microphones placed at a 90 degree angle from each other) helps to create this clarity of texture, where all the parts can be heard individually across the stereo image, especially when listening with headphones. I can't take credit for this idea though: we learned it from the sound engineer who records the Tallis Scholars, who told us that they record in this configuration.

Here are a couple examples of tracks recorded using this style:

https://www.youtube.com/watch?v=IZgo2Z17nNQ

https://www.youtube.com/watch?v=6r0iyq7AvaU

haberman··on Electromechanical reshaping, an alternative to laser eye surgery
As someone who had perfect vision until 40 but now must wear reading glasses, I am super intrigued to hear about lens exchange!

When I heard that LASIK is not a solution to farsightedness, I specifically asked my eye doctor if there are other solutions for this. The only one mentioned was monovision (getting two different prescriptions for different eyes).

> Do note though that artificial lenses, while incredibly advanced, still cannot accommodate as well as the real deal as of now.

Cannot accommodate what as well?

haberman··on Show HN: Bolt – A super-fast, statically-typed scripting language written in C
I love the concept -- I've often wished that lean languages like Lua had more support for static typing, especially given the potential performance benefits.

I also love the focus on performance. I'm curious if you've considered using a tail call design for the interpreter. I've found this to be the best way to get good code out of the compiler: https://blog.reverberate.org/2021/04/21/musttail-efficient-i... Unfortunately it's not portable to MSVC.

In that article I show that this technique was able to match Mike Pall's hand-coded assembly for one example he gave of LuaJIT's interpreter. Mike later linked to the article as a new take for how to optimize interpreters: https://github.com/LuaJIT/LuaJIT/issues/716#issuecomment-854...

Python 3.14 also added support for this style of interpreter dispatch and got a modest performance win from it: https://blog.reverberate.org/2025/02/10/tail-call-updates.ht...

haberman··on Arvo Pärt at 90
Shameless plug, I helped produce and sang on this disc of Pärt by tbe Byrd Ensemble of Seattle: https://www.youtube.com/watch?v=xc96YWX4qAE&list=PLbeKTzt34V...

Excerpts of it were later featured in a French movie called L'Apparition whose soundtrack features a lot of Pärt: https://en.wikipedia.org/wiki/The_Apparition_(2018_film)

haberman··on Hyperpb: Faster dynamic Protobuf parsing
I think you can alias the input data using Cord fields? As long as the input is Cord.
haberman··on Parsing Protobuf like never before
This kind of "expected next field" optimization has a long history in protobuf, but results are mixed.

The generated code in C++ used to check for the expected next field before falling back to the switch() (example here: https://github.com/protocolbuffers/protobuf/blob/460e7dd7c47...) but this was removed in 2016 when load tests found that it hurt more than it helped.

One tricky part of making this optimization work is making a good guess about what the next field should be. Miguel's article alludes to this:

> Each field specifies which fields to try next. This allows the compiler to perform field scheduling, by carefully deciding which order to try fields in based both on their declaration order and a rough estimation of their “hotness”, much like branch scheduling happens in a program compiler. This avoids almost all of the work of looking up the next field in the common case, because we have already pre-loaded the correct guess.

> I haven’t managed to nail down a good algorithm for this yet, but I am working on a system for implementing a type of “branch prediction” for PGO, that tries to provide better predictions for the next fields to try based on what has been seen before.

One delightful thing about the tail-call parser design is that the CPU's branch predictor effectively takes over the job of guessing. With a tail call parser, the dispatch sequence ends up looking like this:

    cmp    QWORD PTR [rdi+0x8],rsi               # Bounds check
    jbe    .fallback
    movzx  r10d,WORD PTR [rsi]                   # Load two bytes of tag
    mov    eax,r10d
    and    eax,0xf8
    mov    r9,QWORD PTR [rcx+rax*2]              # Load table data
    xor    r9,r10
    mov    rax,QWORD PTR [rcx+rax*2+0x8]         # Load field parser function
    jmp    rax                                   # Tail call to field parser
That "jmp rax" instruction is an indirect branch that can be predicted by the CPU. The CPU effectively guesses for us!

And unlike any kind of guess we might have performed ahead-of-time, the branch predictor will constantly adapt to whatever patterns it is seeing in the data. This is good, because statically guessing ahead-of-time is hard.

haberman··on Parsing Protobuf like never before
I think I can shed some light on this, as the creator and lead of upb.

Calling a Protobuf Parser an "interpreter VM" is a little bit of rhetorical flourish. It comes from the observation that there are some deep structural similarities between the two, which I first observed in an article a few years back: https://blog.reverberate.org/2021/04/21/musttail-efficient-i...

> It may seem odd to compare interpreter loops to protobuf parsers, but the nature of the protobuf wire format makes them more similar than you might expect. The protobuf wire format is a series of tag/value pairs, where the tag contains a field number and wire type. This tag acts similarly to an interpreter opcode: it tells us what operation we need to perform to parse this field’s data. Like interpreter opcodes, protobuf field numbers can come in any order, so we have to be prepared to dispatch to any part of the code at any time.

This means that the overall structure of a protobuf parser is conceptually a while() loop surrounding a switch() statement, just like a VM interpreter.

The tricky part is that the set of "case" labels for a Protobuf parser is message-specific and defined by the fields in the schema. How do we accommodate that?

The traditional answer was to generate a function per message and use the schema's field numbers as the case labels. You can see an example of that here (in C++): https://github.com/protocolbuffers/protobuf/blob/f763a2a8608...

More recently, we've moved towards making Protobuf parsing more data-driven, where each field's schema is compiled into data that is passed as an argument to a generic Protobuf parser function. We call this "table-driven parsing", and from my read of the blog article, I believe this is what Miguel is doing with hyperpb.

The trick then becomes how to make this table-driven dispatch as fast as possible, to simulate what the switch() statement would have done. That question is what I cover at length in the article mentioned above.

haberman··on C++: Zero-cost static initialization
I wrote a portable abstraction for this that works across Linux, MacOS, and Windows: https://github.com/protocolbuffers/protobuf/blob/4917ec250d3...

I call them "linker arrays". They are great when you need to globally register a set of things and the order between them isn't significant.

haberman··on Tree Borrows
The paper mentions that the authors implemented tree borrows in Miri. Is this change likely to be adopted by Miri as the default model going forward?
← PreviousPage 2 of 34Next →