5 ms·
The "image" interface is one of my favorite parts of Go's standard library, and I think it's one of the best showcases of Go's "interface" feature. Want to sav
by yalue 7y ago
The "image" interface is one of my favorite parts of Go's standard library, and I think it's one of the best showcases of Go's "interface" feature. Want to save something as an image? Just implement three functions, two of which are trivial boilerplate and the third of which is just "return the color at this coordinate". You don't even need to manage the buffer of image data yourself this way. For example, if you want an image full of noise, just make your "get color" function return a random value every time it's called. I've used this myself for things like simple fractals.
And,on top of all that, once you've written code satisfying the image interface, the standard library even includes functions for saving your image as one of several possible image formats. And, due to the fact that the interface itself is specified by the standard library, virtually all third-party image encoding or decoding libraries for Go use it, too. So, every image reader or writer I've seen for Go, even third-party ones, can be drop-in replacements for one another.
Anyway, it's not Go's standard use case, but as someone who loves fractals and fiddles with images all the time it's one of my favorite parts of the language.
- benhoyt 7y agoI like the simplicity of this (very Go-like). However, what are the performance implications of only being able to get one pixel value at a time? Wouldn't it be much less efficient than say "get this line of pixels" or "get this rectangle of pixels"?
- bmurphy1976 7y agoDoes it matter? Rendering a game at 60fps, sure. Saving a JPEG though? This is a great example where the simple approach is probably better. If you need speed don't use the standard library, use something specialized. This is a great example I didn't even know about that reinforces why I like Go. We don't have to over engineer everything.
- gameswithgo 7y agoimages tend to be very large now. 4k monitors are normal, 16k monitors are planned in the future. So yeah I think it matters. Not always but often!
- lalaithion 7y ago16K monitors? What size of monitor do you expect to use? If you sit 2 feet away from your monitor, you'd need a 57 inch monitor to resolve the pixels.
- bitwize 7y agoThere is basically no upper bound to the display resolution people want, even if their eyes can't physically resolve it. Graphic designers and gamers will still swear there's a difference. It's like audiophilia for the visual system.
- whatshisface 7y agoAt a certain point people will prefer large and more monitors over increased resolution. If I could buy two 16k monitors instead of one 32k monitor I'd do it, and that puts a soft upper limit on the resolution.
- deleted 7y ago[deleted]
- Crinus 7y ago4K monitors are very rare, in regular desktop stats they are so insignificant statistically that they do not even show up: http://gs.statcounter.com/screen-resolution-stats/desktop/worldwide/2019 http://gs.statcounter.com/screen-resolution-stats/desktop/wo... In gaming they are just at 1.61% right below 1280x1024 and the increase is so low (+0.01) that might as well be zero (compare with 1080p's +2.04% which is the one increasing the most): https://store.steampowered.com/hwsurvey/ https://store.steampowered.com/hwsurvey/ Tech minded people are a bubble, gamers are a tiny bubble among those and /r/pcmasterrace 4K-or-die boasters are a tiny bubble among gamers. 4K, or even 1440p, matters way less in practice than tech minded people think.
- enriquto 7y agoWith a single modern CPU you can do a lot of operations at each pixel of a high resolution screen and still get a framerate that your monitor cannot churn. Then, you typically have a few more cores around to do other stuff. Unless your language introduces an unreasonable overhead, a for loop over the pixels is perfectly appropriate and fast.
- nemo1618 7y agoThe problem in this case is not the looping over each pixel, but the overhead of invoking a dynamic method on each pixel. For example, if you're iterating over a []byte and setting each value to zero, the compiler can optimize that to a single memclr. Using an interface masks the underlying representation and consequently prevents any sort of inlining.
- enriquto 7y ago> Using an interface masks the underlying representation and consequently prevents any sort of inlining. This sounds like a limitation of a particular optimizing compiler/interpreter rather than a problem of the language itself. For example, the plain lua interpreter incurs quite a lot of overhead for this, but the luajit interpreter is oblivious. The standard python interpreter definitely adds a lot of overhead.
- rwj 7y agoThis is another way that Go's approach works well. In practice, you will see library code check for common image formats, and then dispatch to optimized code. The advantage here being that optimization is a library concern. Callers still maintain flexibility.
- jerf 7y agoYou'll get the overhead of a non-inlineable vtable-based method call for each pixel. How badly that hurts you depends on the ratio of expense of that call vs. how expensive the pixel is to generate. If you've already got all your pixel values manifested in memory and you're just indexing into an array, it's going to be fairly expensive. If you're generating noise with a random number generator, it's going to be noticeable but not necessarily catastrophic (since "generating a random number" and "making a method call" are somewhat comparable in size, varying based on the number generator in question). If you're generating a fractal the overhead will rapidly be lost in the noise. But I'd also point out that the Go standard library does not necessarily claim to the "last word" for any give task; it's generally more an 80/20 sort of thing. If you've got a case where that's an unacceptable performance loss, go get or write a specialized library. There's nothing "un-Go-ic" about that.
- diroussel 7y agoI would expect the stable based dispatch to be handled quite well with branch prediction. And surely the cache misses from those nested loops would have a much worse impact. Even if a few extra instructions have to run per pixel it's going to be quicker than a fetch from main memory. All the examples, in each language, could be rewritten as a cache oblivious algorithm to optimise cache usage. This would speed them all up. See https://en.wikipedia.org/wiki/Cache-oblivious_algorithm https://en.wikipedia.org/wiki/Cache-oblivious_algorithm
- jerf 7y agoIn general, I tend to agree there's a lot of people who have kind of picked up "vtables always bad and slow" and overestimate the actual overhead. But I have actually benchmarked this before, and it is possible to have a function body so small (like, for example, a single slice array index lookup and return of a register-sized value like a machine word) that the function call overhead can dominate even so. (Languages like Erlang and Go that emphasize concurrency have a constant low-level stream of posts on their forums from people who do an even more extreme version, when they try to "parallelize" the task of adding a list of integers together, and replace a highly-pipelineable int add operation that can actually come out to less than one cycle per add with spawning a new execution context, sending over the integers to add, adding them in the new context, and then synchronizing on sending them back. Then they wonder why Erlang/Go/threading in general sucks so much because this new program is literally hundreds of times slower than the original.) But it is true this amortizes away fairly quickly, because the overhead isn't that large. Even the larger random number generators like the Mersenne Twister will be a long ways towards dominating the function call overhead. I don't even begin to worry about function call overhead unless I can see I'm trying to do several million per second, because generally, you can't do several million per second on a single core because the function bodies themselves are too large and doing too much stuff, such that even if function call overhead was 0 it would still be impossible in the particular program to do it.
- tedunangst 7y agoCast to image.RGBA (or whatever) and bang away on .Pix []uint8.
- jchw 7y agoYou can just optimize for underlying concrete types! I believe even the standard library does this.
- hunterloftis 7y agoYou can certainly operate directly on pixel arrays as well. When the original example here is changed to operate on .Pix instead of using .At(), it runs about 2x faster: https://github.com/nicolashahn/diffimg-go/pull/2 https://github.com/nicolashahn/diffimg-go/pull/2
- sdegutis 7y agoIt sounds like a great use of interfaces, but what is Go-specific about it? Couldn't the same thing be done in Java? (And I imagine it probably already has.)
- yalue 7y agoThe specific thing, IMO, is the fact that the interface itself is specified in the Go standard library along with plenty of utilities for working with it. Java does have similar things, but, as far as I know, they're limited to the UI libraries and far more complicated (e.g. https://docs.oracle.com/javase/7/docs/api/java/awt/Image.html https://docs.oracle.com/javase/7/docs/api/java/awt/Image.htm...). Imagine if everyone who read or wrote image files in Java were guaranteed to conform to a dirt-simple "Image" interface with only three methods--with optimizations available but strictly optional. For someone who's just tinkering or just wants to dump some cool bitmaps, the ability to write an "At(...)" function in Go and be looking at a PNG within a few seconds is just great fun to have in the standard library.
- kodablah 7y agoAgreed. I was able to implement image.Image for a set of pixels from a Cgo library (written in Rust) and it works cleanly w/ the other Go image tools such as PNG saving[0]. 0 - https://godoc.org/github.com/cretz/go-scrap#FrameImage https://godoc.org/github.com/cretz/go-scrap#FrameImage