Ask HN: What is new in algorithms and data structures these days?
Algs/DS were my first love in CS. Nowadays, all we hear about is AI/ML. There must be hardware/software improvements coming from or necessitating fundamental Algs/DS research.
Care to share any of the favorite recent results?
Are there big fields still making gains behind all this computing surge?
215 comments
[ 4.0 ms ] story [ 212 ms ] thread[1] https://www.siam.org/Portals/0/Conferences/About_SIAM_Confer...
I'm currently re-studying them just bc I'm interview prepping. I wonder if whiteboarding for DS/Algo is gonna be a thing of the past too though.
I would imagine there's certainly new DS/Algos in progress to aide ML training efficiency
Obligatory Quanta link: https://www.quantamagazine.org/finally-a-fast-algorithm-for-...
Papers and references (page maintained by central academic in the world of CRDTs): https://crdt.tech
Group doing research into how they can be used to build interesting collaborative (and async) applications: https://www.inkandswitch.com
A few of the major open source implementations - mostly for rich text editing or JSON like data structures:
- Yjs: https://github.com/yjs/yjs
- Automerge: https://github.com/automerge/automerge
- Peritext: https://www.inkandswitch.com/peritext/
- Dimond types: https://github.com/josephg/diamond-types
People building eventually consistent database syncing with them:
- https://electric-sql.com (Postgres <-> SQLite)
- https://vlcn.io (SQLite <-> SQLite)
Open source colaborative servers (coordination, persistance, presence):
- https://github.com/ueberdosis/hocuspocus
- https://github.com/partykit/partykit
- https://github.com/firesync-org/firesync
And there’s another hard problem I’m stuck on: so, CRDTs like mine and Yjs use (random agent id, sequence number) as unique IDs for operations. But a troublesome user might reuse an operation ID, sending different operations to different peers and giving them the same (agent, seq) pair. This results in really awful, weird bugs. Martin Kleppman wrote a paper about BFT in CRDTs. His suggestion is we do what git does and use SHA hashes for IDs instead.
But there’s two problems:
1. We don’t want to store a hash for every keystroke a user makes. That would get big, fast. We want a DAG of hashes like git, except a hash is generated with each keystroke. How do we build an efficient system that can query by hash, without storing all of the hashes?
2. I want users to be able to permanently delete inserted text. If I accidentally paste an AWS key into a document, I don’t want that key in the document history forever. How do I enable that while still getting all the benefits of the hashing system above? (Note if your hash includes the typed character, even if you delete the character itself from disk, you can trivially brute force the hash to recover any typed characters).
Each of these problems in isolation has a solution. But the real prize is how do you solve both at the same time? I think this problem has a solution but I don’t think anyone has solved it yet. Want to name an algorithm and get my eternal respect? Here’s your chance.
Yes, if you are a Byzantine general trying to coordinate an attack, and you aren't sure of your peers' true allegiance.
It seems to me that for the majority of use cases, collaborative editing in a work sense, you would normally have trust that a user isn't actively hostile. And so I'm not convinced that BFT needs to be a high priority goal in most of these implementations.
I think I higher priority issue is with how to aid developers with the use generic CRDT structures within a system where all the rules are not encoded into the CRDTs themselves. So for example when using Yjs with Prosemirror, Prosemirror has its own schema that defines how a document can be structured. However not all those rules are encoded into the underlying Yjs structure, it's possible to have two conflicting edits that results in a Yjs document that doesn't comply with the Prosemirror scheme. The current system throws away any none conforming data when the document is loaded or updated. Because of this you need to load the document into a server side instance of Prosemirror in order to apply that schema when doing a server side merge, or even if you are just rendering the static content after "blindly" merging the documents.
My point really is that CRDTs are an encoding of user intent, and when these generic CRTD are merged you have a structure representing merged user intent, not a final conforming state. The solution is either domain specific CRDT that are tailored exactly to you application, or a schema system for these generic types that apply rules when merging. Prosemirror/Yjs does (mostly) exactly this but I think it needs to be solved in a more generic way that can be run on any layer of the system.
As for the prosemirror problem, I assume you’re talking about weird merges from users putting markdown in a text crdt? You’re totally right - this is a problem. Text CRDTs treat documents as a simple sequence of characters. And that confuses a lot of structured formats. For example, if two users concurrently bold the same word, the system should see that users agree that it should be bolded. But if that “bold” intent is translated into “insert double asterisks here and here”, you end up with 4 asterisks before and after the text, and that will confuse markdown parsers. The problem is that a text crdt doesn’t understand markdown. It only understands inserting items, and bolding something shouldn’t be treated as an insert.
JSON editing has similar problems. I’ve heard of plenty of people over the years putting json text into a text crdt, only to find that when concurrent edits happen, the json grows parse errors. Eg if two users concurrently insert “a” and “b” into an empty list. The result is [“a””b”] which can’t be parsed.
The answer to both of these problems is to use CRDTs which understand the shape of your data structure. Eg, use a json OT/crdt system for json data (like sharedb or automerge). Likewise, if the user is editing rich text in prosemirror then you want a rich text crdt like peritext. Rich text CRDTs add the concept of annotations - so if two users bold overlapping regions of text, the crdt understands that the result should be that the entire region is bolded. And that can be translated back to markdown if you want.
Luckily, in over a decade of work in the collaborative editing space, I haven’t found any other examples of this problem other than rich text and json. I think if a collaborative editing system supports json data, rich text and plain text then it’ll be enough to add collaborative editing support to 99% of applications.
The ink & switch people did a great write up of how rich text CRDTs work here: https://www.inkandswitch.com/peritext/
Agreed, BFT is clearly needed for multiplayer CRDT backed video games.
> I assume you’re talking about weird merges from users putting markdown in a text crdt?
Nope, although that is an issue. In that case the document shouldn't be markdown, it should be a rich text CRDT that's converted to markdown as output.
On the conflicts I mentioned, an example, say you are building a to do list app. First let's do it with Prosemirror and Yjs, but for some reason we have decided to limit the length of a to do list to 10 items. Prosemirror will let you do that when defining a schema, have a maximum number of child nodes of the parent node type. With the current Yjs/Prosemirror system, if you have 9 items in the list and two people concurrently add a 10th, one of them will be dropped by prosemirror (deterministically). The document schema enforced that rule outside of the CRDT implementation. Yjs xmlFragments do not have the concept of these sort of rules.
Now say you want to do this with the json like Map and Array types. Again the array type does not have the concept of a length limit, it will merge the two concurrent edits and create a document with 11 entries. In this case your application needs to manage the no longer complying document to correct it.
The issue comes if you are naively merging the documents on the server, and dumping the json, it will not take into account your applications own conflict resolution. My suggestion is that a CRDT schema could do this, it would be a bit like a JSON Schema, but with rules about how to correct misshapen structures.
So yes, I agree these generic rich text plus JSON types cover what 99% of applications need, but they also need to enforce a shape to the data structure that isn't built into the generic types. Having a way to do that as part of the merging layer, rather than application layer, would help to ensure correctness.
My best answer at the moment is to tell people to rethink validation rules like that. I can't think of a lot of good use cases for collaborative editing where a "length <= 10" rule is something you want.
Unfortunately, validation rules are really important for referential integrity. If you add a reference to some item in a data set and I concurrently delete that item, what should happen? Does the delete get undone? Does the reference get removed? Is the reference just invalid now? Should references only be to an item at some point in time? (Eg like a git SHA rather than a path)? Maybe optimistic systems just can't have referential integrity? Its an uncomfortable problem.
This is a hard problem and something that I think has to be intrinsic to the serialization method of your CRDT. Unique IDs of any kind are really risky in CRDTs because they basically break the mathematical links between potential partitioned duplicates. Lists make this problem even harder because you have to contend with if you should incorporate the ordinal of each element into the hash value.
I’m working on a version control system for visual programs. You wrote a response to someone on structured edits a few months back about how the UNIX fs not being schema/structure aware is the core problem with collaborative edits and destroys open interoperability. I think you hit the nail on the head. I’ve been working on something for nearly a year now to try to address that problem. Let me know if you want to compare notes.
Disclaimer: layman of the highest degree
Love to. Hit me up if you like - my email address is in my profile.
I'm sometimes bad at responding to email because I'm hyperfocusing or something and falling behind on email. Sorry in advance if that happens.
Years back, in 2016 I had a prototype visual programming system where I built a partial Git client in JavaScript to automatically save edits to the program to GitHub. It worked pretty well even with it committing every edit (though there were some bugs).
Email is in my profile if you're interested to share.
1. Encrypt the data with an ephemeral key.
2. Upon delete, just wipe the key. The data becomes unrecoverable.
I'm not sure about problem 1. A fast seekable stream cipher like ChaCha might help here (don't use e.g. RC4 for this), but a faster hash like BLAKE3 might be more appropriate. I'm happy to noodle over this some more. These are just my unfiltered thoughts.
If you combined the two: Each entry in the graph could have a public salt used for key derivation for the actual data. This salt would not be part of the DAG, but stored outside of it. To delete an entry, zero-out the salt and the underlying key becomes unrecoverable, which makes the data unreadable, without needing to change the history of the DAG.
Salts per character + cryptographic erasure would solve problem 2. But how do we do it without a massive increase in storage, memory use and network bandwidth? I don’t want to generate and store a cryptographically unique salt per keystroke. (Even though it might run fine on modern hardware).
Writing a novel is a couple million keystrokes over a year, so even storing a kilobyte per keystroke is trivial.
2. Godbolt => "This rendered text came from these portions of the DAG/tree, press CTRL-Del to irrecoverably remove them from the DAG and/or replace them with 'REDACTED'".
#1 is "simply" related to efficiency. You can lean on probabilistic data structures to see "yeah, probably this was HERE in the history tree", and then perhaps replay the "N" seconds checkpoints with the original input data to validate or match exactly where the hash is/was.
#2 is the same problem GIT has, where you need to rewrite / filter / "detached head" in order to obliterate something from history, and is somewhat a local/remote UI issue. "A request to obliterate history is being built in parallel over here => click here to adopt it, and diff/transfer only your new work to it"
Related to #2 I've had a thought of a social network protocol, and how to "delete malicious nudes" or "oops, I accidentally published my SSN and Tax Filing *.pdf to my friends". On the "real" open internet, google or malicious actors would/could store that info forever. However, in a semi-closed group (eg: 100-1000 "friends of friends") that are "all" running participating/conforming clients, a "request-to-delete" or "request-to-disavow" is particularly valuable.
My imagined protocol is: "post001.txt" => "$GOOD_SHA", "nudes.jpg" => "$WHOOPS_SHA", "taxes.pdf" => "$YIKES_SHA", "bait.txt" => "$BAIT_SHA".
"Of course" everything is content-addressable, ie: anyone can fetch "post001.txt" from anyone as "$GOOD_SHA", however, "it would be nice" if I as a participant in the network could query "how_many( $GOOD_SHA );" and see distribution across the network participants. "disavow( $WHOOPS_SHA, $YIKES_SHA ) && how_many( $WHOOPS_SHA, $YIKES_SHA )" => ie: in a "good" network, after a "disavow(...)" then nodes should not respond with that availability. Occasionally throw in "$BAIT_SHA w/ disavow(...)" and make sure that no one is propagating it.
Similar to "key revocation lists", you could eliminate/reject any clients which do not respect requests of non-propagation for $BAIT_SHA or anything that is "disavow(...)'d". Same story with anyone hosting https://en.wikipedia.org/wiki/Celebrity_sex_tape ... I don't really want them in my network, so query for particular content, and if they respond, I can nudge/boot/disengage from users that are hosting them (same with eg: "I love my AK-47 and assault rifle" political memes, disengage / dislike / spread apart people sharing/hosting/propagating content that I don't want).
While you're still left with malicious / non-conforming nodes potentially archiving everything for all eternity, there is a relatively immediate ability to fracture / exclude (shun) nodes that are detected as non-conforming, so "your stuff" is still out there, it's just relatively inaccessible, and you have to be "careful" to have your client be up-to-date w.r.t. revocation actions otherwise you'll be excluded from networks.
Meh. Kindof complicated, but those seem to be in the range of characteristics that you're talking about as well.
2. String merging should also allow null merges, e.g. identity the same closed set of history inserts that refer to only the inside of the original inserts, and so permanently deleting history of characters in the document could be replaced with a null insert that can still be referred to by other inserts/merges.
I think it might mess with fine-grained undo, also?
(idk much about CRDTs)
"Reject re-used sequence numbers" might be simpler?
But it sounds like a V5 Guid [0] might meet some of the needs for your "operation Ids". The existing "random agent id", and "sequence number" could be used as part of the namespace / salt.
[0] https://www.sohamkamani.com/uuid-versions-explained/#v5-non-...
I agree this could be done. But diamond types (my library) considers every keystroke to be a new change. If we store a UUID for every keystroke, that’s very inefficient.
So, why does it have to be every keystroke? We can batch them locally. An approximate rule of thumb to use to guide the batching is typing speed. Using 200 keystrokes/minute, we get an average of 3.33 keystrokes/second. If we use 3 seconds latency for the collaborative editing to resolve concurrent edits, we get 10 keystrokes per batch (3 secs). You can also have an override where smaller batches are replicated to nodes if sufficient time has elapsed since last local edit (eg: 10 secs have elapsed and we have a partial batch of 3 keystrokes only).
> 2. If I accidentally paste an AWS key into a document, I don’t want that key in the document history forever. How do I enable that while still getting all the benefits of the hashing system above?
In order for this to work, we should assume that every replica's current state is always obtained by starting with the empty document and then playing the operations log. We should also permit the author of an operation to rollback the operation. So, I suppose if the collaborative editor shows an interface of all operations that originated locally and the author can select the operations that inserted the AWS key into the replica, they can issue a rollback operations op and have it propagated to all replicas.
Since the point-in-time state of the replicas are regenerated by replaying the operations log, we would have deleted the accidentally inserted key.
In order to address the issue of the AWS key still being in the operations log, we can define the semantics of the rollback op to also include secure erasure of the previous operation from the operations log (i.e., the rollback op would update the original operation into a no-op.
So, when all replicas apply the rollback op initiated by the author, eventually all replicas will converge to having the accidentally inserted AWS key removed from the replica and the operations log.
> This repository contains a high performance rust CRDT for text editing. This is a special data type which supports concurrent editing of lists or strings (text documents) by multiple users in a P2P network without needing a centralized server.
> This version of diamond types only supports plain text editing. Work is underway to add support for other JSON-style data types. See the more_types branch for details.
In any case, I agree with the metaphor and batching granular operations can always be done.
[1]: https://github.com/josephg/diamond-types
If you have these two operations:
... They get immediately merged internally to become: This reduces the number of items we need to process by an order of magnitude. In practice, the metadata (positions, IDs, etc) ends up taking up about 0.1 bytes on disk per inserted character, which is awesome.This works great for (agent, seq) style IDs because the IDs can also be run-length encoded. But we can't run-length encode hashes. And I don't want my file size to grow by 32x because we store a hash after every keystroke.
(I didn't invent this idea. Martin Kleppman first used it for storing CRDTs on disk, and Kevin Jahns made in-memory structures use the same tricks in yjs)
This approach would absolutely work. Thanks for bringing it up! The problem is that it has a cost I'd rather not pay.
The problem is that batching only works if you don't send anything until the end of the batch. And that gives you an awful tradeoff - the more "live" your editing system is, the bigger the overhead. Its really nice seeing someone type live. Seeing their changes "pop in" after 5 seconds doesn't feel great. It doesn't feel current. And I think its too slow for pair programming over zoom and things like that. It also makes the DAG of changes much more complicated, because there's a much higher chance that changes are concurrent when there's that much latency. And that in turn also makes other aspects of the system do a lot more work to merge.
With hashing alone, you don't need to batch changes like that. Instead, you can just store hashes on disk for (for example) every 100 changes, and regenerate the changes in the middle when needed. (Though you need some other tricks so you can know where to look for a given hash).
Respect where its due, that would 100% solve it. I'm just looking for a way we can delete data without needing to give up the liveness.
> We should also permit the author of an operation to rollback the operation.
This doesn't completely solve the problem of hashes and guessability. Say in the 5 second batch I only type 1 character. We store some metadata, the character and the hash of the metadata & character. Then in the next 5 seconds I issue a rollback operation to delete that character. If I delete the character from durable storage, I can still figure out what the character was by brute forcing things and checking the hash for that batch.
I think thats fixable by adding junk data to every batch. I just bet there's ways to avoid doing that.
We don't need to encode positions separately as it is a function of the state of the replica and the sign of the hash.
Between the sync points, each message only contains part of the hmac, proportional to the state size, and not h_(N-i). Ie if sending a single keystroke then a single byte from the hmac, if sending a few bytes then two bytes of hmac etc.
If the sequence is continuous you can still verify as you can regenerate h_(N-i) from the previous sync point. A deletion can then still be done by issuing a redact operation which deletes everything from the start of the secret up to the next sync point, and adding an operation which inserts back the data between the end of the secret to the next sync point.
My thought was that while finding a collision for a single keystroke message might not be hard, doing it for two messages in a row should be quite difficult. However it would "just" roughly double the size of the messages on average, rather than add dozens of bytes to each.
I was also thinking that h_0 should be based on data includes something that's not up to the individual actor, to make preimage attacks harder.
Anyway, way past bedtime and not my area at all so this might all be drivel, but interesting problem to ponder...
i have a blog post to shill if you haven’t heard of them: https://joelgustafson.com/posts/2023-05-04/merklizing-the-ke...
I imagine in an application like this there would be a "settled" set for which all agents agree, and a "pending" set which is agreed by some but not all agents. The hash could be used solely for the pending set to get agreement on order of operations, but after that it could simply be dropped altogether. The sequence number could even be generated on the receiving agent's side.
Also, even for the "pending" set you wouldn't even need to store the hash in your primary data structure. You could simply add a dictionary from (agent id, seq) to hash, right?
The second issue seems reasonable easy to solve with this: include a salt in the hash. You're only storing the "head" hash of the "settled" set, so when the salts aren't stored there is no way to reconstruct this hash from the settled set itself.
If I'm understanding the problem right a malicious user, uses the same source id to send action a to user 1 and action b to user 2. This has started two independent (and currently indistinguishable branches in your DAG) When those branches come into contact at say user 3 weird bugs start arising.
However, user 3 is receiving a DAG of all the operations along with the operations. Can't it use that to validate the DAG chain? If it finds a conflict it could simply create a branch at the conflict point by modifying the IDs at the point of conflict. Say by appending a hash of just that operation to the id?
Are merkle trees relevant here?
If anyone has thoughts about this space, feature requests, would like a preview of what we are building, or anything else, please do reach out direcly to me at henry@firesync.live, I'm talking to as many people as possible at the moment.
Is there a 'data structure' or 'scheme/setup' which fits the below description?
Have a repo (with a specific git commit hash), which is specifically constructed to reproducibly construct some data (database, dataset, set of examples files, etc) (which can then be checked by a checksum), such that when a different person pulls and executes that code, a decision could be made to either run the code again, or to pull it from the decentralized group of people that have that repo database.
This is effectively "code as data" which is checked by a key value pairing: [git commit hash <==> data checksum hash]. So if you have a process that runs for 24 days on a good machine and produces a 30 MB file for people to use, they can check for reproducibilityif they want with the code, or just grab the file from everyone who pulled the repo to save time. Obviously having defaults for download or rerun could be determined by certain logic like execution time etc, but the base idea is something I've looked into for a while.
Is there a name for this type of setup?
So instead of using CPU cycles to do finance/currency things (or whatever is done in that arena), this would calculate whatever data you actually want directly.
In some cases it could be a ton of calculation (estimating electron density functional, or training NN weights) to receive a small file (DFT functionals are quite small). Or it could be some calculation for a large amount of data (ETL for big we scraped data).
One important aspect which is similar to the currency/Blockchain realm is that the commits would specify the data state, so the data would likely be mostly read-only for people, but if they change it, it would simply branch off to a different hash pair.
https://web.stanford.edu/class/cs168/index.html
I didn't have time to go through all of this, but the parts that I did read were very interesting to me.
It's not even cutting edge new stuff of 202x, probably most of them are new-ish results from ~2000 onwards.
- Eventual consistency [2]
- Refinement types [3], [4]
[1] https://www2.cs.uh.edu/~ceick/6340/Streams.pdf
[2] https://www.microsoft.com/en-us/research/wp-content/uploads/...
[3] https://ranjitjhala.github.io/static/trust_but_verify.pdf
[4] https://ranjitjhala.github.io/static/refinement_types_for_ty...
Indeed. To me, they look like a formalized subset of precondition contracts that can be checked statically. I don't think the idea is new, I seem to recall Ada and Eiffel having something like this, but I first learned about them via Racket[1]. I still don't know how they work or what's needed to implement them.
Interestingly, Raku has a similar concept called `subset`, eg.
but from what I know no static analysis is done on the refinement/"where" part, it's a runtime assertion only.[1] https://blog.racket-lang.org/2017/11/adding-refinement-types...
Why make it more complicated? :-)
The secret is that given values are smartmatched against the given object. And the values 1 through 10 will return True if smartmatched against the 1..10 Range object. The `of Int' ensures that only integer values are allowed, otherwise 3.14 would also match. Unless of course, that is what you intended. In which case you can omit the "of Int".
I believe it's because they're not exactly easy to implement and the resulting extensive type checking might also affect compiler performance.
By the way, another great example of refinement types (in JavaScript) is this one: https://kaleidawave.github.io/posts/introducing-ezno/
https://en.m.wikipedia.org/wiki/Combinatorial_optimization
Algos to solve those both exact methods and heuristics are super useful in real world applications, and very underappreciated by the HN crowd IMO.
Have you been doing it by hand or using a big program?
So, in a couple projects I was involved we had to resort to manually constructed heuristics such as Simulated Annealing (SA) and Large Neighborhood Search (LNS).
A very cool large scale approach we resorted in some problems (eg. airline scheduling) was Column Generation[1]. Not only it solved in minutes what took hours in the MIP implementation, unlike SA and LNS it's and exact method that provide valid bounds to global optimality.
[1]: https://en.wikipedia.org/wiki/Column_generation
Eg at Goldman Sachs I used mixed integer linear programming to reshuffle the assignments of particular departments' computing needs to specific data centres. Before I got my hands at this, people used to just do it manually and hadn't even realized this was something were you could optimize systematically and squeeze some efficiency out of.
If you have an eye for it, you can find similar opportunities in many areas of many companies. Assignment / scheduling is actually extremely common, if you can recognise it. It's also relatively easy to show an improvement that you can measure in dollars, which always goes over well with the business folks.
I've used SAT solvers recreationally to try solve some mathematical combinatoricsish-like problems but interesting more down-to-earth applications have eluded me. I would love to dig deeper into this stuff.
https://www.gurobi.com/case_studies/
however, it's mixed-integer programming rather than SAT solvers/constraint programming, but similar in spirit.
Some recent literature reviews:
https://link.springer.com/article/10.1007/s11750-021-00594-1
https://www.ijcai.org/proceedings/2021/608
There just aren't many situations where they're useful. Time scheduling, logistics, operations, there are only a handful of places where these problems are so important that they need to be solved optimally or nearly optimally.
>Time scheduling, logistics, operations...
Add marketing (pricing, assortment, ...) and finance (portfolio, captial budgeting, ...) to those.
And those are huge domains, moving trillions of USD globally each year. Many companies (think most of the Global 2000) benefit from a couple % improvement on their operations that lead to many $$ in savings.
If you are in the HN crowd, the 'opposite' direction is useful:
Know that effective generic solvers exist for these problems (but don't worry about how they do it), and re-formulate many of the trickier problems you encounter in the languages of these solvers, instead of eg writing your own bespoke algorithm to assign tents at the company picnic.
https://arxiv.org/pdf/2201.09331.pdf
(I wish to found a way to encode the equivalent of `struct Data{col1:Vec<..>, ...>})
I'm honestly not sure otherwise.
When the computing platform changes and you have to start paying attention to L-level caches and counting your bytes (so you can do you big compute in memory), and use n cores, and then arrays are adopted, that is not fashion; so not "hip", rather modern.
https://www.probabilistic-numerics.org/
0: https://egraphs-good.github.io/
1: https://github.com/cfallin/rfcs/blob/main/accepted/cranelift...
0: http://www.rntz.net/files/seminaive-datafun.pdf
1: https://www.mwillsey.com/papers/egglog
2: https://s-arash.github.io/ascent/cc22main-p95-seamless-deduc...
https://flix.dev/
https://doc.flix.dev/fixpoints.html
(I am one of the developers of Flix)
> Flix is strongly typed. Any attempt to use predicate symbol with terms of the wrong type (or with the wrong arity) is caught by the type checker.
*Statically typed
0: https://people.csail.mit.edu/mcarbin/papers/aplas05.pdf
https://github.com/egraphs-good/egglog
https://arxiv.org/pdf/2304.04332.pdf
Parsing with errors that are useful for the human is definitely an open research topic.
Capabilities have seen interesting work, in particular in relationships to effect handlers.
Hyperloglog and other datasketch keep seeing incremental progress. I have not had time to read it all but Omar Ertl has a massive reduction in memory use in UltraLogLog that i expect a paper on soon. The code is already up.
Or tdigest. For the prom use case, i usually prefer ddsketch. Easier to integrate. Tdigest is really complicated.
That said there are definitely interesting work that need to happen on compressing time series histograms for heatmaps. Iirc influx had some interesting work there.
They’ve released it as experimental recently, and we’ve found it to be a massive improvement over the original histograms. It’s still bucket-based at the end of the day, but the exponential bucket layout and sparse representation seems to work really well in practice.
How Branch Mispredictions don't affect Quicksort (2016) https://arxiv.org/abs/1604.06697
Quicksort is still the algorithm of choice for general purpose internal unstable sort. In modern hardwares, quicksort spends a lot of time recovering from branch misprediction, because quicksort's comparison branches are inherently unpredictable. The problem is severe to the extent that with branchful quicksort, uneven split with more recursion is faster than even split, because it makes branches more predictable! Exact penalty is hardware microarchitecture specific, but one report is 20:80 split being optimal.
So... you should use branchless quicksort! The technique was pioneered by an implementation called BlockQuicksort, but pdqsort (for pattern-defeating quicksort) https://arxiv.org/abs/2106.05123 is also popular. pdqsort incorporates branchless technique and also includes other enhancements.
Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step (2017) https://abseil.io/blog/20180927-swisstables
Hash table is the workhorse of data structures, and while improvements are possible for specific usage patterns, open addressing with linear probing is still the fastest implementation for general purpose. So called "Swiss Table" combines cache-friendly metadata layout with SIMD-enhanced probing. These two optimizations work well with each other and combination of two is particularly effective.
I'm sad that it's not "pretty darn quicksort"
* for some arbitrary definition of “constant”
Some people throw the word “constant” around quite freely.
Scientific and mathematical advances are notoriously hard to predict. “Constant” is the last word I would use.
One simple model would be spaced in time according to a memoryless stochastic process while having “impact” scores sampled from an exponential distribution.
Of course, one could include more realisticish dynamics to the model, such as network effects and clumping (e.g. from funding priorities and “ideas in the air”.)
I feel like some areas of computer science tend to correlate with deep statistical appreciation more than others.
Speaking of the topic of stochastic behavior in general, it feels to me like (a) randomized algorithms and (b) better statistical simulations have steadily been gaining traction in algorithms and data structures.
Here is my summary of the algorithm from another HN post: https://news.ycombinator.com/item?id=34479148#34481937
[1] https://github.com/terminusdb/terminusdb/blob/dev/docs/white...
Basically given a set of million (billion, trillion...) ~1000 valued vectors, and a query ~1000 valued vector, find the closest vector in the indexed set. This is "nearest neighbor" search and there have been increasingly more and more sophisticated approaches:
http://ann-benchmarks.com
https://haystackconf.com/us2022/talk-6/
And has spawned companies like Weaviate, Pinecone, etc etc (a half a dozen others)
That's 100,000 english wikipedias or 50 googles.
Not that this is a practical example because we technically cannot index all cells in each body. But even if such an algorithm being studied today might be useful one day when we do capability to collect such data
As far as I am aware Google doesn't publish any statistics about the size of its index, which no doubt varies.
If I was building it from my 5 minutes of googling, using 15TB nvme u2 drives, and easily available server chasis, I can get 24 drives per 2u of a rack. That's 360 TB + a couple server nodes. So ~6u per PB. A full height rack is 42u, so 6-7PB per rack once you take up some of the space with networking, etc. So dozens is doable in a short datacenter row.
Realistically you could fit a lot more storage per U, depending on how much compute you need per unit of data. The example above assumes all the disks are at the front of the server only, if you mount them internally also, you can fit a lot more. (see Backblaze's storage pods for how they did it with spinning disks).
Dozens of PB is not that much data in 2023.
Yes it is. Just transferring it at data center speeds will take days if not weeks.
Think of it like an accountant. Weights are all of their experience. Prompt is the form in front of them. A vector database makes it easier to find the appropriate tax law and have that open (in the prompt) as well.
This is useful for people as well, like literally this example. But the LLM + vector combination is looking really powerful because of the tight loops.
> What sort of dataset are you indexing that has trillion entries?
It doesn't say
> What sort of dataset has a trillion entries?
You can retrieve the exact information even faster. From an (x,y,t) coordinate, you can find the exact index in constant time
for each photo, extract and index SIFT vectors
https://en.m.wikipedia.org/wiki/Scale-invariant_feature_tran...
i used it as an example of a way a dataset might have a huge number of feature vectors.
curious if there are better ways to do this now.
I'm not in the we scraping/search area, so idk about the 100B website thing (other than it's Google order of size), but the encoding would take some mega amount of time depending on how it's done, hence the suggested sum of GloVe chunks (potentially doable with decent hardware in months) rather than throwing LLM on there (would take literal centuries to process).
It is a cacheline sized 'container' (CLC) of machine-word length records, with one record used to store the record order and remaining bits for metadata. So you can project different kinds of container semantics, such as FIFO or LRU -- any ordered set semantics -- on this meta record. Using arrays of CLCs you can create e.g. a segmented LRU, where the overhead per item is substantially less than a conventional pointer-based datastructure, and, is naturally suited for concurrent operations (for example by assigning a range to distinct worker threads), and ops require a few or couple of lines to be touched. The LRU (or whatever) semantics in aggregate will be probabilistic, as the LRU order is deterministic per unit container only. It is very fast :)
https://github.com/alphazero/libclc/blob/master/include/libc...
https://github.com/alphazero/libclc/blob/master/include/libc...
As for the non-deterministic aspects: Since container semantics e.g. LRU order is only applied at unit level, the overall cache is ~LRU. We can strictly quantify the 'ordering error' by observing the age of items in FIFO mode as they are removed: for a deterministic container the age of the item is equal to the total capacity of the queue, for a segmented (array) composed of FIFO queues, the age will have a effectively gaussian distribution around the capacity (number of units x unit capacity). But since these containers can use as few as 9 bits per entry vs 24 or more bytes for pointer based solutions (which use linked-lists), for the same allocation of memory, the capacity of the array of CLCs will be much greater, so, the distribution tail of 'short-lived' items will actually be longer lived than items in a strict queue for the same exact memory. Additional techniques, such as n-array hashing, and low order 'clock' bits at container level, can tighten this distribution significantly (i.e. ~LRU -> LRU) via better loading factors.
2022 Paper https://arxiv.org/abs/2203.00671
For example, the constraints may be:
1) The graph is read left to right. This informs the layout such that those spiral and circular graph layout algorithms that blow out the graph do not work.
2) Try to keep certain types of nodes vertical aligned, so that they lie on the same horizontal line.
3) Minimize edge crosses.
4) Have the edges be not lines but paths made up of horizontal and vertical lines. This probably requires some path finding as well.
5) Be able to live update in the sense that a new node or edge entry into the graph does not drastically re-layout the graph for reasonable inserts.
Then it's relatively easy to formulate your (5) as an additional constraint in the same language:
Given a previous solution x_0, find a new solution x_1 to slightly different constraints f_1 by minimizing af_1(x_1) + bdistance(x_1-x0)
You'd need to decide on the weights a and b and on the distance function. (Instead combining distance from old solution and constraints linearly, you could also use other ways to combine them. Eg you could also say:
Keep f_1(x_1) <= epsilon and minimize distance(x_1-x_0). Or you could do minimize the sum of squares:
af_1(x_1)^2 + bdistance(x_1-x0)^2
Specifics depends on what works well for your problem, and what your solver can handle.
See https://developers.google.com/optimization for a free library of solvers.
And it may be the case that (5) is actually the most implrtant constraint. Haha.
We worked on this problem, too, and Gordon Woodhull created a working system for dynamic hierarchical layout. I think he also incorporated an improved node positioning and edge routing algorithm of Ulrik Brandes. The code is still available here, I think: https://www.dynagraph.org
It was great work. We meant well, but were overextended for the resources we had. Gordon is a brilliant programmer.
To meet all these criteria would indeed be amazing. For 5) you may get semi-reliable results by doing the layout deterministically and optimize for things like short edge length, so adding new nodes would just push around the rest a little bit. But good luck avoiding crossings at the same time.
[0] https://cs.brown.edu/people/rtamassi/papers/gd-tutorial/gd-c...