Open midnightmonster opened 2 years ago
This is so cool. Thanks for contributing a full implementation, and I love the name you chose! I'll try to take a look at it and get it merged this holiday break.
Regarding the "silly indexes", I pushed up a commit adding an explanation of why they are what they are. Hopefully you don't find it "silly" anymore, ha, and if you can think of a better way to do it, by all means let me know. The implementation here doesn't have to match what I did in the other languages, if it supports the data model I'm using, though as a first pass, I'm glad you did!
@losvedir thanks for creating this project and putting it out there! I've been mostly doing Ruby on Rails for the last decade, and I often wonder what the other universes are like, so I appreciate the opportunity to compare several languages-I'm-not-using handling concurrency and building large-ish responses. I've been playing around with Elixir and Crystal and (to a lesser extent) Deno and wondering whether I want to seriously invest in any of them or triple-down (I think I'm past doubling) on my Ruby investment.
I was pleased to find that Crystal (very Ruby-like and pleasant for me to read/write) is broadly competitive with all-but-Rust. Next I want to try this in Ruby itself—I expect it to be slower and require more resources, but I don't really know by how much. If it's only e.g. 3x slower than Crystal, as someone who likes to fly solo or work on very small teams, perhaps I should stick with the more mature ecosystem that I already know deeply.
Re: "silly" indexes, bottom line is that instead of having records : Array(MyRecord)
and one or more key_index : Hash(String,Array(Int32))
(with the Int32
s being indexes in records
), you can skip the indirection and just have one or more records_by_key : Hash(String,Array(MyRecord))
. In the languages I work in anyway, it's not wasteful to have several of these. The data is much too large to all live on the stack, so it's all going in the heap anyway, so having the same MyRecord
appear in multiple arrays doesn't mean MyRecord
is actually duplicated—there are just multiple references to the same memory location. Depending on the language this introduces maybe a bit of memory overhead, but not a whole lot, and nothing like actually duplicating the records. (I'm pretty sure this is true even in Elixir.) And skipping the indirection makes for simpler, more idiomatic code and faster lookups.
I wrote my first Crystal version without the indirection, and it was faster-serving (maybe 6% slower-loading, though) with less application code, since I could rely entirely on the standard library JSON serialization macros.
Gets performance generally comparable to C# or Go. Compared to those implementations, Tryst usually has lower P95 times but noteably higher max times: 300ms-450ms on all tests. I suppose it's GC pauses. Memory usage tops out at ~460MB, CPU usage reaches 650% in the large responses test.
On my machine (Also an M1 Pro), Go takes over 2s to load the file initially, so tryst is faster-loading for me, but my Go server results are pretty comparable to your published ones.