6,887 karma · joined June 7, 2011
evan.martin@gmail.com
My posts on HN: https://news.ycombinator.com/from?site=neugierig.org
https://neugierig.org/software/blog/2023/11/retrowin32-unico...
My experience with it was that it was a lot slower than my (not very good) emulator, I imagine because I was some using it wrong somehow (? qemu is high quality software). And also as I was using it I was managing to corrupt state somehow in a way that meant it didn't end up ultimately helping with the thing I was trying to debug.
https://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2...
https://neugierig.org/software/blog/2023/01/compiling-advent...
As a dumb script it was not designed to be especially flexible. One thing I remember needing to fix was that by its nature it was archiving old data and preserving it, which meant that it was accidentally deadnaming trans people. My recollection is this was a small code fix, but an interesting lesson in social consequences of oblivious software.
We had the occasional HR interaction but to my recollection never anything nasty.
You could store a dense mapping to integers in (1) and then represent (2) as a separate file that is literally just packed floats. Photo number n has its embeddings start at file offset n * 512 * sizeof(float). Updating any given photo's embeddings is an easy seek+write.
Given that the use case of similarity search is to stream through the embeddings and not any complex seeking/rewriting/indexing/etc, feels like a relatively simple bit of code.
The post talks about serializing 100k photos. With 32-bit floats each embedding vector is 4*512=2048 bytes, so that's ~205mb of data, much less than the >500mb quoted in the post.
E.g. "Passing by value is slower, since a new variable has to be created. Passing by reference just passes an address and the code uses that address – no new variable is created." is not right for the example involving an int*, as passing a pointer is clearly as much data to copy as an int (and in the non-pointer case it will all end up in (faster) registers).
Similarly if you paste the "Returning a copy of a structure" example into godbolt you'll see GetDateTime4 writes directly into the caller's pointer. https://godbolt.org/z/Wj4Gn1YaP
* likely corrupted by memory's distance
It includes some pretty big cases like "if the filesystem is encrypted" or "if the write extends the file": https://learn.microsoft.com/en-us/previous-versions/troubles...
It feels like in the big picture sense it would be better to just always using some sort of smarter/slower pointers in this kind of code just for extra defense. I saw in [2] there is some sort of `raw_ptr<T>` type [3] that seems to intend to help, so maybe the crash in [2] was actually successfully defended against?
It's too bad that there's not a good way to have a broader way to switch between dialects within a project where in one place it's "this portion of the code is perf-critical and carefully reviewed" and in another "this portion of the code is perf-oblivious and has lots of async state that's easy to get wrong". I've wondered if it's almost worth mixing two separate languages (like a GCed one for the latter) just to make the distinction clear.
[Disclaimer: worked on this code many years ago, wouldn't be surprised if I caused >0 of these bugs...]
[1] https://bugs.chromium.org/p/chromium/issues/detail?id=120103... [2] https://bugs.chromium.org/p/chromium/issues/detail?id=132323... [3] https://source.chromium.org/chromium/chromium/src/+/main:bas...
Here is a demo with some Rust output: https://evmar.github.io/weave/?wasm/rust.wasm be sure to click on 'code' to dive into individual functions.
As far as languages go, Go is relatively well-funded. You can of course imagine hypotheticals where there was more money behind it, but on the spectrum of PLs it's got more support than most. And further, I think the core developers who work on it like rsc and bradfitz are objectively great implementers. Despite that, the majority of complaints about Go are a lack of language features; you even see some in this thread.
In other words, a language with an above-average amount of effort and ability and 14 years behind it is still perceived as feature poor. But imagine how much more time it would have taken if they had taken on a larger scope, like if they had gone through all the complex design process for generics up front.
For me it is a great lesson on how important reducing scope is for shipping, and also how a reduced scope enables you to more judiciously choose what is important. If they had decided that features X, Y, Z were table stakes for their launch they would never have had the time to do the other things that I think have made Go great.
I came up with what I think is a kind of neat solution, which is that the tokenizer is generic over some T and takes a function from byteslice to T and uses T in place of the strings. This way, when the caller has some more efficient representation available (like one that allocates less) it can provide one, but I can still unit test the tokenizer with the identity function for convenience.
In a sense this is like fusing the tokenizer with the parser at build time, but the generic allows layering the tokenizer such that it doesn't know about the parser's representation.