[HN Gopher] Otter, Fastest Go in-memory cache based on S3-FIFO a... ___________________________________________________________________ Otter, Fastest Go in-memory cache based on S3-FIFO algorithm Author : rickette Score : 122 points Date : 2023-12-23 15:49 UTC (7 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | tedunangst wrote: | I was curious what function was used for hashing (how do you | write a generic hash function in go?) and it's pretty disgusting. | | https://github.com/dolthub/maphash/blob/main/runtime.go | lukevp wrote: | Could you provide more context on what's disgusting? I'm not a | go programmer but this looks like it's just a wrapper around a | library called 'maphash' and doesn't do any of the hashing | here? This is more of a service wrapper around that lib so that | it can have a consistent seed value etc.? | brabel wrote: | I suppose the use of unsafe pointers liberally is disgusting | to some. | sidlls wrote: | I wouldn't call it disgusting. It's just typical unsafe go | code. The named return parameter in `getRuntimeHasher` is | irritating, as all such parameters in go code are. If you're | going to return values, do so explicitly in the return | statement. In go code, seeing `return` at the end of a | function _does not_ mean that the function doesn 't return | any values, and that can lead to harder-to-read code. | insanitybit wrote: | > It's just typical unsafe go code. | | But... just to hash something? | sidlls wrote: | Go has a lot of lower level internal implementations of | things that unsafe code like this serves as a thin shim | for. It's an almost zero-cost abstraction to use such a | builtin. | | I'm not saying it's good: it just is what it is. It's | silly: but so is most of go. I've worked with this | language for several years now: I don't like it at all. | It presents a facade of simplicity, but has all sorts of | hidden issues that affect performance and behavior in | surprising ways. | insanitybit wrote: | I think we're all saying the same thing then. | tedunangst wrote: | Constructing a map (an opaque type) just so you cast it to a | carefully duplicated struct so you can steal the function | pointer is kinda nutso. | | Did you look at the linked code in maphash? | oooyay wrote: | Disgusting is a word, but I'm guessing you'd say the same about | most unsupported feature code (aka unsafe). You can make code | like this reliable in the ways it needs to be while still being | "unsafe". That's kind of table stakes when you start dabbling | in unsupported things. | tedunangst wrote: | And what does this code do to make itself reliable in the | event the go authors change the internal structure of a map? | rad_gruchalski wrote: | Nothing. That's the trade off. What is not clear about | "unsafe" in that context? | tedunangst wrote: | The part about making it reliable in the ways it needs to | be. | oooyay wrote: | It's pinned to a Go version and these: https://github.com | /dolthub/maphash/blob/main/hasher_test.go | falsandtru wrote: | https://news.ycombinator.com/item?id=36434358 | | Note that S3-FIFO has no loop resistance. Nevertheless, the | result that this library is functioning in a loop indicates that | some important changes have been made. Also, the result that | Ristretto, which has loop resistance, is not functioning in a | loop is clearly an anomaly and likely not being measured | correctly. Ristretto's benchmark results on S3, DS1, and OLTP | differ markedly from the official ones | (https://github.com/dgraph-io/ristretto). Otter's benchmark | results are quite suspicious. | NovaX wrote: | I think you may be right. The library author said he made many | changes because implementing the eviction policy following the | paper's design was pretty awful. | | > First, I tried to implement cache on hash table and lock free | queues, as the authors wrote. I was annoyed, s3-fifo was indeed | many times faster than lru cache implemented on hash table with | global mutex, but still lost to ristretto and theine, which use | bp-wrapper techniques. The profiler also showed that it was the | eviction policy that was spending the bulk of the time. And I | was only able to beat ristretto and theine after rewriting the | implementation using bp-wrapper. | | > In summary, I completely disagree about the scalability | advantage of S3-FIFO over the other policies | | > Though to be honest, what was causing the hit ratio to go | down in DS1 I still don't understand | | https://github.com/Yiling-J/theine-go/issues/29#issuecomment... | medler wrote: | Could you share some pointers where I can read more on "loop | resistance?" Does that just refer to whether it does well on a | looping access pattern? | senderista wrote: | The usual phrase is "scan resistance", which is especially | important for databases. A pure LRU policy does poorly on | scans; LFU does better but performs worse than LRU on other | workloads. The original ARC paper[0] has a good discussion of | the tradeoffs. | | [0] https://www.usenix.org/legacy/events/fast03/tech/full_pap | ers... | falsandtru wrote: | Scan resistance and loop resistance are completely | different. They are also distinguished in the papers. Note | that ARC has scan resistance but no loop resistance. | medler wrote: | None of the papers linked above or linked in the GitHub | repo mention "loop resistance." | senderista wrote: | Not _completely_ different; they 're both sequential | access patterns and equivalent whenever the loop size | exceeds cache size. | falsandtru wrote: | Not equivalent completely. Scan is a one-time access, so | it never hits in scanning. Loop is multi-time accesses, | so it can get many hits in looping if it has loop | resistance. No hits on scan resistance in looping. Over. | falsandtru wrote: | Sometimes referred to as tolerance. There is no established | terminology, so the only way to find out is to use scan or | loop as keywords. | someplaceguy wrote: | So why did you say S3-FIFO has no loop resistance (or loop | tolerance)? | falsandtru wrote: | There is an established benchmark for testing loop | resistance (GLI, Loop). S3-FIFO has not passed this test. | And I have already confirmed that S3-FIFO does not pass | the test. | someplaceguy wrote: | The design of S3-FIFO seems to imply it should handle all | loops less than 90% of the cache size pretty well, | perhaps even up to 100%. | | Did you figure out why it did not pass the test? Was | there a bug in the implementation, perhaps? | | Also, where can I find the benchmark you speak of? My web | search did not find anything. | falsandtru wrote: | My implementation is reviewed and linked from the | official S3-FIFO page. | | https://s3fifo.com | | A benchmark is as follows. | | https://github.com/ben-manes/caffeine/wiki/Efficiency | | I can't deal with you any longer. Over. | NovaX wrote: | /u/someplaceguy, | | Those LIRS traces, along with many others, available at | this page [1]. I did a cursory review using their traces | using Caffeine's and the author's simulators to avoid | bias or a mistaken implementation. In their target | workloads Caffeine was on par or better [2]. I have not | seen anything novel in this or their previous works and | find their claims to be easily disproven, so I have not | implement this policy in Caffeine simulator yet. | | [1]: https://github.com/ben-manes/caffeine/wiki/Simulator | | [2]: | https://github.com/1a1a11a/libCacheSim/discussions/20 | someplaceguy wrote: | S3-FIFO has scan resistance. What do you mean by "loop | resistance" and why do you say S3-FIFO doesn't have it? | almost_usual wrote: | Pretty much 2Q without the LRU. | jeffbee wrote: | I never really understood why anyone wants to maintain LRU | properties in caches. It doesn't make sense today and it didn't | make sense decades ago, either. You do need some rational basis | for eviction, but ranking your hot set on every access was | always just weird. I am glad this is being fixed in the | literature lately. | tomalaci wrote: | For a long time Go did not expose their super efficient internal | map hashing algorithm that, if possible, would use AES | instructions from the processor to hash even faster. That is, | they did not expose it as an official API. People usually did | some "unsafe" usage to link to that internal hashing function for | their own super-fast map implementations. | | That was changed some time ago. They released official maphash | package: https://pkg.go.dev/hash/maphash | | Otter author could probably look into replacing their 3rd party | hashing dependency (https://github.com/dolthub/maphash) with the | official one and knock off an unneeded dependency :) | tedunangst wrote: | hash/maphash isn't a very good replacement if you're trying to | hash simple structs, since they still require conversion to | byte slice by some means. | Thaxll wrote: | Hash functions all work on bytes sequence or string, hashing | a struct/class/object is a different use case. | michalmatczuk wrote: | The issue is Go stdlib does not have parallel hash map. | | We have https://github.com/puzpuzpuz/xsync#map a different Cache | line hashmap impl. | 1f60c wrote: | What about https://pkg.go.dev/sync#Map? | mvcalder wrote: | I wrote up this analysis of cache one-hits after reading the FIFO | is all you need paper. | | https://www.polyscale.ai/blog/one-hit-expectation | hn_throwaway_99 wrote: | I didn't know what the S3-FIFO algorithm was. https://s3fifo.com/ | gives a great overview with some good visuals. | 1f60c wrote: | This entire time, I thought it had something to do with Amazon | S3. | coxley wrote: | Sorry if I missed it, but does Otter do anything special to limit | GC-impact when caching lots of references? That's the main | selling point for bigcache and freecache. | neonsunset wrote: | You may want to vet this as a third-party dependency due to | supply chain attack risks. ___________________________________________________________________ (page generated 2023-12-23 23:00 UTC)