4 ms·
I should send this to rsc, but it's fairly easy to find examples where the lack of generics caused an opportunity cost. (1) I started porting our high-perform
by dgacmu 9y ago
I should send this to rsc, but it's fairly easy to find examples where the lack of generics caused an opportunity cost.
(1) I started porting our high-performance, concurrent cuckoo hashing code to Go about 4 years ago. I quit. You can probably guess why from the comments at the top of the file about boxing things with interface{}. It just got slow and gross, to the point where libcuckoo-go was slower and more bloated than the integrated map type, just because of all the boxing: https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go
(my research group created libcuckoo.)
Go 1.9 offers a native concurrent map type, four years after we looked at getting libcuckoo on go -- because fundamental containers like this really benefit from being type-safe and fast.
(2) I chose to very tightly restrict the initial set of operations we initially accepted into the TensorFlow Go API because there was no non-gross way that I could see to manipulate Tensor types without adding the syntactic equivalent of the bigint library, where everything was Tensor.This(a, b), and Tensor.That(z, q). https://github.com/tensorflow/tensorflow/pull/1237 https://github.com/tensorflow/tensorflow/pull/1237
and https://github.com/tensorflow/tensorflow/pull/1771 https://github.com/tensorflow/tensorflow/pull/1771
I love go, but the lack of generics simply causes me to look elsewhere for certain large classes of development and research. We need them.
- aaronblohowiak 9y agoplease do send that to rsc
- bascule 9y ago> fundamental containers like this really benefit from being type-safe Note that, at least in its current form, the native concurrent map type uses interface{} for all keys and values, and therefore offers no type safety: https://github.com/golang/go/blob/master/src/sync/map.go https://github.com/golang/go/blob/master/src/sync/map.go See also: https://github.com/golang/go/issues/18177 https://github.com/golang/go/issues/18177 All of Go's built-in pseudo-generic types (e.g. maps) require special support from the parser. I'm not sure if they plan on doing that for sync.Map as well, but this is clearly an area that could benefit from generics.
- rsc 9y agoThanks for this. Will followup on email.
- kevhito 9y agoFor (2), are you looking for overloading arithmetic operators (+, -, etc.)? Do people normally consider that under the umbrella of "generics"? For (1), for curiosity sake, I tried benchmarking the cuckoo.go file you posted. I'm curious if these numbers are in line with what you found. The first test I did was a Rand test, Putting 10,000 random string values under random string keys, then Getting the same 10,000 keys, then Getting 5000 more random keys. The first line below uses default code you posted, which uses type keytype string type valuetype string The second line specializes the code to just using the string type directly for keys and values. The third line uses interface{} instead for values, which is the code style HN doesn't like. BenchmarkRandInsert-8 100 13474408 ns/op BenchmarkStringStringRandInsert-8 100 13585071 ns/op BenchmarkStringVoidStringRandInsert-8 100 14126666 ns/op That third line shows the cost of casting to/from interface{}. The difference in the code was about as you might expect, with the "void" example needing a cast on each put and a cast on each get. If I use uint32 for value instead, I get: BenchmarkStringIntRandInsert-8 200 9431584 ns/op BenchmarkStringVoidIntRandInsert-8 100 10485785 ns/op The 9.4ms result is using a version of your code hand-specialized for string key and uint32 values. The 10.4ms result uses interface{} for values. I ran a sequential-insert test as well, with keys and values generated sequentially instead of randomly. The results are much the same, though the cost of casting to/from interface{} seems to get mostly lost in the noise. BenchmarkSequentialInsert-8 100 12185272 ns/op BenchmarkStringStringSequentialInsert-8 100 12233946 ns/op BenchmarkStringVoidStringSequentialInsert-8 100 12543980 ns/op BenchmarkStringIntSequentialInsert-8 200 8823747 ns/op BenchmarkStringVoidIntSequentialInsert-8 200 8709242 ns/op I also tried some tests using uint32 as the key type. This int key specialized version is 3x faster than any of the string key versions, but it's not really a fair comparison... a small part of the code is specific to string keys (getinthash), so it's not as obvious what the generic equivalent of that function would be. The keys need to be Hashable or some such, not just interface{}. I'm also allocating and formatting random strings in one case, vs just picking random numbers in the other case. I didn't find the interface{} casting in any of the above code too horribly "gross", just a bit ugly, but that's entirely subjective. Yes, all of the cuckoo versions seemed about 30% slower than the corresponding builtin map type, but my benchmark numbers don't show if that is the price of boxing, of supporting concurrency, or maybe function call overhead, or something else. I'm guessing you did more detailed benchmarks that point to the source of the slowdown. One last comment: keytype and valuetype really need to be exported, don't they? I wasn't able to use the library without changing them to exported symbols.
- e3b0c 9y agoSounds like https://github.com/golang/go/issues/19335 https://github.com/golang/go/issues/19335 I guess the performance issue has something to do with some missing optimizations. Hopefully it will get better. Edit: Also https://github.com/golang/go/issues/19361 https://github.com/golang/go/issues/19361