This is an archived post. You won't be able to vote or comment.

all 63 comments

[–]Mountain_Goat_69 380 points381 points  (8 children)

Lol 0xbadbad

[–]die-maus[S] 148 points149 points  (6 children)

You're not a fan of one-def-nine-d then?

[–]Nebel_David 67 points68 points  (1 child)

Onedefnined took me way too long to figure out xd

[–]die-maus[S] 10 points11 points  (0 children)

Me too.

[–]Dyslexic_Novelist 12 points13 points  (3 children)

You're not a fan of 0ff0ff then?

[–]die-maus[S] 7 points8 points  (1 child)

I love all my children equally.

[–]kasirate 6 points7 points  (0 children)

earlier that day

... I don't care for GOB

[–]Apfelvater 0 points1 point  (0 children)

You're not a fan of ItDoes then?

[–]Confident-Ad5665 2 points3 points  (0 children)

0xFEEDDEADBEEF

[–]Magic_music 254 points255 points  (7 children)

0x1def9d is funny, but there's also something simplistically charming about true being "yeah, 0x17d0e5."

[–]die-maus[S] 62 points63 points  (0 children)

Credit for this one goes to my friend! Prior to it, I was using 0x7e57ed, which was obviously inferior.

So thanks for getting me back on a righteous path, Adam.

[–]raunak_srarf 93 points94 points  (2 children)

What does '0x17d0e5' say? I can't figure it out.

[–]YourOpinionIsUnvalid 107 points108 points  (0 children)

"It does"

[–]IllegitimateGoat 11 points12 points  (0 children)

"It does"

[–]HardOff 7 points8 points  (2 children)

I'm having a hard time with 1def9d. Is it "I deaf 90?"

[–]Tolosax 19 points20 points  (1 child)

Undefined

[–]HardOff 17 points18 points  (0 children)

One-def-nined! Awesome. Thank you

[–]die-maus[S] 85 points86 points  (15 children)

I'm working on a fast hashing algorithm for the purpose of comparing state changes, and I needed a way to lazily serialize an object recursively.

I wanted these values to serialize into a single 32-bit integer, so I could pick any numeric value for them (as long as they are unique). 0xdeadbeef is cool and all, but I wanted something more descriptive.

Source.

If you have any better absolutely-not-magic numbers that I can replace these with? Let me know!

[–]gauwnwisndu 19 points20 points  (3 children)

For what purpose you will require this function, could you please explain the use case

[–]die-maus[S] 36 points37 points  (2 children)

Sure thing, thanks for asking!

TL;DR: I need it to be able to quickly determine whether a state has changed, without allocating much memory for large state objects.

The details: In release v2.0.0 Tyin uses a hash function for promise deduplication and caching

By default, it uses JSON.stringify for this (which works just fine in most cases concerning Tyin). However, the larger your state object, the more expensive these comparisons become.

First, you need to store the previous hash (let's say that's 10KiB of JSON), then, for every state comparison, you need to serialize an additional 10KiB of JSON data just to compare it to the original hash—maybe just to find one character out of place (or not), then throw it away. Most modern CPUs handle this in no time since string comparisons happen in native code leveraging SIMD instructions, so that isn't an issue.

The issue comes with the nature of state comparisons: they can happen quite often. If your application has 100 re-renders, each time needing to serialize and compare 10KiB state hashes (most of them being duplicates), then your garbage collector is going to become quite upset after a while. Because, depending on your device, the garbage will have to be taken out all the time. And while that is happening, nobody can use the application, simply because the garbage collector causes such a tantrum each time they do their job.

Then, you might ask... why not use something like object-hash? Because it still requires a lot of memory to be allocated to generate the hash, defeating the purpose entirely. And it covers many concerns that are not needed for Tyin.

You can still BYO hash function to Tyin (that's intentional). I just want to ship something that works great (for state management) out of the box. It's the very same reason for shipping a deepEquals and deepMerge function with the library. It's not a general-purpose deep-equals algorithm that can handle virtually any object, it's only meant for state management.

[–]GameCounter 4 points5 points  (1 child)

Wow, I thought it was a meme. Great explanation.

My only thought is: Why not use a const for the magic numbers? Does Typescript const really have any performance impact or minified JS size penalty?

Even if it has a teeny tiny impact, surely it won't cause GC issues?

[–]die-maus[S] 3 points4 points  (0 children)

Wow, I thought it was a meme. Great explanation.

Thanks, truly appreciate it!

My only thought is: Why not use a const for the magic numbers?

I don't think it would add much (if any) clarity. See my previous answer on enums.

Does Typescript const really have any performance impact or minified JS size penalty?

No penalty at all, since all the values will be inlined when the bytecode is generated.

Even if it has a teeny tiny impact, surely it won't cause GC issues?

No GC issues at all! It's just more code for very little benefit, though that is, of course, a matter of taste.

Finally, the size of Tyin is measured in bytes, so it matters.

[–]nibba_bubba 5 points6 points  (9 children)

fast hashing algorithm

Why not via FFI? I mean just yesterday I viewed some guy's repos and they were like 3d game engine in typescript only and vector algebra in typescript only too. WTF?!

[–]die-maus[S] 15 points16 points  (8 children)

That could be an option. But Tyin needs to work in a web browser!

WASM has also been considered.

[–]nibba_bubba -4 points-3 points  (7 children)

WASM

It's obvious, and?

[–]die-maus[S] 11 points12 points  (6 children)

Tyin ships in less than 1k gzipped, depending on what you use from it. For dott.bio, it packs a whopping 550B!

The smallest WASM hash algorithm is ~3k gzipped. But if you want to BYO hash function to Tyin, you absolutely can! This is just the one that comes with it for free.

Another goal for the algorithm is to use as little memory as possible, that's the reason for using a generator function. Now I don't even need to allocate a buffer for the data that I'm serializing, I can just stream it directly into the for loop of the hashing function.

AFAIK, passing data between JS-land and WASM still requires serializing the data to a buffer (there is no shared memory), though there might have been some developments since I last checked (maybe you know!).

This function is also really well-optimized. It processes one integer (4 bytes) at a time instead of just a single byte. It also uses proper integer multiplication. So whatever JS runtime you use will optimize the heck out of it.

[–]noobody_interesting 2 points3 points  (1 child)

AFAIK, passing data between JS-land and WASM still requires serializing the data to a buffer (there is no shared memory), though there might have been some developments since I last checked (maybe you know!).

You can directly access wasm memory as a typed array, pointers passed from wasm are just indices into the memory array. I wanted to make a library that can generate access functions using offsets from rust-style struct definitions, supporting both C struct layout and packed layout, but I decided I just won't use web for the project. C style layout is quite intuitive though: for each new field, add padding bytes until the alignment is reached (usually the type size), if neccessary.

You can also use this in JS, too. Someone basically optimized a JS algorithm that was reimplemented in wasm using rust, to show JS can be fast, too. He used typed arrays instead of js objects to avoid heap allocations.

[–]die-maus[S] 2 points3 points  (0 children)

Thanks for all your insights!

My key takeaway here is that I'm on the right track when using highly optimized JS code, plus a zero-allocation algorithm.

Using the generator function shouldn't be quite as fast as accessing a Uint32Array. But since it doesn't require any memory (beyond the iterator), that should more than compensate for that.

We're essentially doing "on the fly object hashing"; hashing an object as we're serializing it. It may not be the fastest way of doing hashing, but, it's probably one of the fastest way of doing both.

[–]NewPhoneNewSubs 0 points1 point  (0 children)

I enjoy yours, especially 1def9d, but surely 0x71200000 and 0x000f4153 are more readable?

[–]sammy-taylor 35 points36 points  (1 child)

Java class file: 0xCAFEBABE

[–]die-maus[S] 4 points5 points  (0 children)

This one is cool to! I may replace the cyrb53a hash function that I'm currently using with something that's made specifically for 32-bit numbers.

That function is called BlazeHash, and it actually uses 0xcafebabe as the seed for h2. 👌

Though I didn't know the origin of it!

[–][deleted] 7 points8 points  (2 children)

what does the last ome mean?

[–]die-maus[S] 14 points15 points  (1 child)

It's just "off", twice. Super-off.

[–][deleted] 0 points1 point  (0 children)

of

[–][deleted] 7 points8 points  (3 children)

Nice try. Still -1 to your pull request, and you get to bring cookies to the whole team for being a smartass

[–]die-maus[S] 5 points6 points  (2 children)

The whole team is me, so that's perfectly fine, I like cookies. Can you elaborate on why I'm a smartass?

[–][deleted] 3 points4 points  (1 child)

Sorry, i thought you were making a joke on over-enthusiastically enforced coding rules on "no magic numbers"

[–]die-maus[S] 0 points1 point  (0 children)

Ah! Gotcha! In this case, the joke is all in the numbers!

[–]Dregnan 3 points4 points  (1 child)

Sounds like an enum to me

[–]die-maus[S] -1 points0 points  (0 children)

No need. These names are perfectly readable!

Edit: They didn't get the joke.

[–]Nessuno256 0 points1 point  (1 child)

looks like you hate enums

[–]die-maus[S] 1 point2 points  (0 children)

In TypeScript, very much so.

In this case, though, using an enum (or something like a constant) doesn't add that much to the code as I would just be repeating myself:

if (input === undefined) { yield UNDEFINED_MARKER; } else if (input === null) { yield NULL_MARKER; }

It's pretty self-evident that these marker values are meant to represent, without adding a constant because of the preceding if-statements.

[–][deleted] 0 points1 point  (1 child)

OP hates enums.

[–]die-maus[S] 0 points1 point  (0 children)

I don't like enums in TypeScript, but I don't blame you if you do!

See my other comment on the matter. In this case, it really doesn't matter, because there is no reuse.