That's kind of exactly what floats are. You store the log2 of the number, a bit for its sign, and in what remaining bits you have left some fixed-point scaling between adjacent powers.
No, that is not what floats are. A logarithmic number system literally just stores the logarithm of a number (and a sign bit) and manipulates it as a logarithm. The significand is 1, always. So multiplication & division are simply addition & subtraction, respectively. But this simplification for multiplication, division, roots, and powers is counterbalanced by more complex addition and subtraction.[1]
As a former Skia contrib, this is cool as heck to read. It's exactly the sort of optimization work we had in mind when we wrote that SkRecord system, and I'm pleased that you were able to make use of nanobench. Back in those days we had just a few small optimizations that we could apply, mostly trying to eliminate unnecessary saveLayer() calls. Very cool to see it done in a modern way with Lean.
Let me also add that nanobench was a huge help, not just because it was a good benchmarking tool but also because it gave us some confidence that we're measuring the right thing. It's easy to make _something_ faster but hard to know if it's the right thing. Having that come pre-packaged from the project answers a lot of tricky questions that would otherwise be easy to get wrong.
That's how we felt answering performance questions of our own... we needed to know if our work was important, and the tools help keep that focus locked in. I'm no longer on the Skia team, but like, welcome to the Skia team; if you use nanobench, you're legit. :)
I'm the same user name @gmail.com if you ever have any questions. It's been a while but I'd be happy to try to page things back in for a good cause.
Hey, I am the first author of the paper, and thank you so much for the kind words. I am very grateful to you and all the other Skia contributors who've made the Skia codebase so easy to build and develop. I learnt so much about performance benchmarking by reading the nanobench source code too!
My rule of thumb is that integer ops cost 1 cycle except divides, floats 3 but maybe divide is a bit more, then integer divides are like infinity at 20+ cycles that cannot be amortized by vectorization.
When you code simd it's best to assume the integer divide instruction does not exist. Just an impossibility, if you need to divide ints, rethink your whole program.
Float divides are still pretty expensive; 8-10 cycles of latency on modern hardware, integer divides being 8-20 cycles. (on Apple M1 both are 8-10 cycles; int div is much worse on older x86 hw)
Integer multiply is also pretty universally 3 cycles of latency, i.e. basically the same as float multiply (or even add!).
What float div definitely has over int div is throughput, as float div comes in vectorized versions on x86 & ARM, and it usually is actually parallelized.
On top of generally fp div generally having higher throughput (M1 gets down to 1 instr/cycle! though int div isn't bad either at 0.5 instrs/cycle; x86 numbers are messy but even 32-bit int div is never better than f64 div, though they're close; also an annoying aspect is that x86 division instrs actually always take a 128-bit divisor, though hopefully a sign-/zero-extended 64-bit value skips the extra work)
They're typically like a 3-cycle op, right? Obviously that's not zero, but as far as floating point ops get it's a cheap as it gets, like an add, mul, fma, that sorta thing.
I am probably dating myself here, but in C the default rounding mode is round-to-nearest and the cost of switching the x86 FPU to truncate (and back) in order to perform the truncation is extremely expensive. Perhaps other instruction sets have truncate-to-integer instructions that ignore the active rounding mode.
I don't think there is necessarily one ideal middle ground here. It still feels to me like what's best is a function that depends on who and when.
I see it as something like a personal gradient descent. You're working on a problem, there are solutions down there somewhere, and you can kind of feel the gradient of the tools-and-techniques ground around you. Any way you walk means you're investing time improving some skill or another. So you should go the way that personally feels to you will best get you moving in the direction that you want to go.
For some people it's obvious LLMs are competent coders, getting better, sticking around... and those people should lean into that gradient. For some people what's obvious is nearly the exact opposites of all that, and I'd encourage those people to also follow their gradient/heart/nose down the path of sharpening their personal traditional coding skills. Some people are in a relatively flat area where nothing is obvious, and need to explore and maybe just keep doing their best to hedge with a bit of both.
reply