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

How does this packing/unpacking scheme compare to just using a lookup table?

A 256x3=768-byte lookup table will almost certainly be faster on a CPU. It will fit easily in L1 cache, and can extract 3 bytes at a time.

Ironically, x86 has an instruction, XLATB, that does almost exactly this (looking up a byte in a 256-byte table) -- but using it is actually slower than using an equivalent MOVZX RAX,AL; MOV AL,[RBX+RAX] sequence on modern CPUs.


Compared to the naive implementation presented in the blog post yes, but the PR uses AVX, which can process 256 bits (32 bytes) at a time, so I'm not so sure

I was wondering the same thing.

Obviously, it partly depends on the implementation machine, how big a hole the tables blow in your cache, how fast the multiplies are, etc.

But it probably also hugely depends on the format that you want your trits in. If you use them unpacked, e.g. one trit per byte, then even if you're using tables, you still have to do a lot of manipulation (e.g. either shifting and oring, or having different tables, and a table lookup per trit and adding together to get the binary).


You can have a single 3x256=768-byte table -- just multiply the input byte by 3 to get the offset into the table, and read out the 3 trits (1 trit per byte) beginning at that offset.

ETA: If you're prepared to waste a byte per entry so that entries are 4 bytes wide, on x86 you can use effective address calculation to do the multiplication for you, letting you decode 3 trits in 1 CPU instruction:

    MOV EAX,[RBX+RCX*4]

There are 5 trits.

In any case, if there were 3 trits, I'm not sure what the practical difference between a single table you describe and 3 different 256 byte tables, again depending on the architecture.

Yes a single table would have better cache effects for a single read, but presumably? you're doing a lot of reads in an unpacking phase.


You wouldn’t need five tables. Each trit takes up two bits when unpacked into 0,1,2 values.

You can do a full unpacking-via-lookup with a uint16[256] and then do bit shifting and masking to extract the individual trits, but using an extra byte in each entry (or 3 tables) would let you extract with just two shifts.

This starts to vary a lot with the microarchitecture, and there’s the added dimension of SIMD vectorization, so accurate timing in a realistic context becomes important.


I agree.

But then, I'm pretty sure you haven't said anything different than what I said in the great-great-grandparent of your comment, other than slightly fleshing it out for a couple of particular scenarios.

But you haven't covered the packing, which is the primary thing I was suggesting you might need multiple tables for if you really wanted to use tables and really didn't want to do shifting or multiplication.


> There are 5 trits.

Whoops, of course!

I think storing 5 consecutive bytes per entry in a size-1280 table will still be faster than 5 single-byte lookups in separate size-256 tables, since it only requires 1 (unaligned) dword MOV and 1 byte MOV.


You're getting confused between lifetimes (the static analysis that prevents use after free and similar errors) and lifecycles (more commonly discussed under the heading of ownership) which determines when objects (and thus memory) are allocated and deallocated.

Ownership is automatic. You don't have to explicitly drop things when they go out of scope. (although the responsibility for that is split between the compiler and the library code).

Lifetimes are not automatic, but also have no effect on memory allocation. They are purely a static analysis path and you can make a functioning rust compiler that completely ignores lifetimes.


> You're getting confused between lifetimes (the static analysis that prevents use after free and similar errors) and lifecycles (more commonly discussed under the heading of ownership) which determines when objects (and thus memory) are allocated and deallocated. Ownership is

That's a semantic distinction which does not matter in the point OP is making. And contrasting rust static approach to general GC.


Yes - I think the proof of the pudding will be whether they put in the effort to eliminate these unsafe blocks. The conversion to Rust is the starting point that makes this possible, but it's definitely not "done" at this point.


If the majority of those are at an FFI boundary to a language without lifetime analysis, I don't really see how they could be fixed without rewriting all that downstream software?


1. Seems hard but doable (assuming there is some way to inject "inputs" into the build process, that can be recorded). However, I'm not sure exactly what you mean by a build here? Many package managers do not distribute binaries at all, so what is being reproduced?

2. Codegen is extremely common (serialisation, reflection, bindings, etc.) to the extent that even requiring a whitelist of packages that can do code-gen seems like a non-starter (because everyone will just whitelist everything). Sandboxing build-time execution seems like a more practical approach than denying it.

3. It's not clear to me that this distinction is meaningful for many package managers. Crates.io for example distributes a packaged set of sources. What are you comparing these against? There's no requirement that the original sources be hosted anywhere public, and I definitely don't think it should be mandating use of GitHub or anything.

4. Needs to be a whitelist rather than mandatory, but yes.

5. Yes, with the caveat below.

6. What do you mean by "locked"? You cannot lock transitive dependencies and still have a usable package infrastructure. If I have packages A and B that both depend on C, then it's critical that the version of C used is maximally flexible, otherwise it becomes impossible for downstream packages to find a version of A and B that are inter-compatible.

In my experience there's a danger for security experts to neglect the practical consequences of the limitations they enforce. This is because they incorrectly place security as the most important requirement, when by definition it cannot be:

Without a product, there is nothing to secure. When a policy becomes too onerous, people don't simply stop doing their work, they work around it. It's a mistake to judge a security policy on what it prevents: a security policy should be judged on what it allows, because an unused or bypassed security policy is less effective than a weak one.

This is why I think thinks like sandboxed execution far more effective than trying to blanket deny things: it enables people to work in a more secure way rather than pushing them towards less secure alternatives. It's why the best way to get people to use secure cryptographic libraries is not just to berate them for doing things themselves, it's to build secure libraries that they want to use because they work well and are ergonomic.


> Yes, and the amount of study and knowledge required had a tendency to filter out people with the inclination to do such things. The Venn diagrams weren't completely empty, but they were close, which is why such incidents were rare.

People do exercise their freedom and do terrible things all the time - it's not rare. There are lots of ways to cause harm that don't require any study or knowledge at all, we just seem hyper-focused on the possible "sci-fi" consequences of AI for some reason.

I would argue the reason people don't go and kill someone (or worse...) even more often than they do is not because it's difficult but because most people have no desire to cause that kind of harm, and because of the consequences to themselves of doing so.

So yes: technical difficulty put some kinds of harm out of reach of people, and AI can lower that barrier somewhat, but in the grand scale of "harm people can do" I think it's receiving undue attention.

And from a practical standpoint: how do you get from there to arguing that we should set some impossible-to-define threshold of "frontier" at which point it becomes so evil that we need to forcefully delete it from existence? Don't you see the problem with trying to put such black and white restrictions on something that's so inherently amorphous and slippery? (And by definition, if you delete the "frontier" model from existence then the next best model is now "frontier" ad infinitum...)

On top of that you have the issue that model weights are just information, so in some sense you're legislating the knowledge that is allowed to exist. That's quite a bit more draconian that current laws which usually focus on what knowledge you can share.


You would have had to run `jj edit` in order for this to happen, so I think it's a stretch to say you didn't ask for the edit?

This is the main difference though: in git files can be `staged`, `unstaged` or `committed`, so at any one time there are 3 entire snapshots of the repo "active".

In `jj` there is only one kind of snapshot (a change) and only one is "active" (the current working directory). When you make changes to the working directory you are modifying that "change".

As others have mentioned, the equivalent to `git checkout` would be `jj new`, which ensures a new empty change exists above the one you are checking out, so that any changes you make go into that new change rather than affecting the existing one.


I really, REALLY wish jj picked a different subcommand name for that than 'jj new'.


Thanks for the explanation! I wish I could edit my comment to reflect the truth.


> git was originally implemented as a handful of low level binaries stitched together with shell scripts.

A bunch of low level binaries stitched together with shell scripts is a lot faster than python, so not really sure what the point of this comparison is.

Python is an extremely versatile language, but if what you're doing is computing hashes and diffs, and generally doing entirely CPU-bound work, then it's objectively the wrong tool, unless you can delegate that to a fast, native kernel, in which case you're not actually using Python anymore.


Well, you can and people do use Python to stitch together low level C code. In that sense, you could go the early git approach, but use Python instead of shell as the glue.


Their point was that by offloading the bottlenecks to C, you've essentially conceded that Python isn't fast enough for them, which was the original point made above


Fair point!


Which works for about 5 minutes until someone leaks a manufacturer's private key or extracts it from a device...


Yes this stood out to me as well...


Stored procedures don't eliminate serialization anomalies unless they are run inside a transaction that is itself SERIALIZABLE.

There's essentially no difference between putting the logic in the app vs a stored procedure (other than round trip time)


Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

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

Search: