Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: golang.org/x/image/math/fixed: a place for fixed-point types. #11906

Closed
nigeltao opened this issue Jul 28, 2015 · 13 comments
Closed

proposal: golang.org/x/image/math/fixed: a place for fixed-point types. #11906

nigeltao opened this issue Jul 28, 2015 · 13 comments

Comments

@nigeltao
Copy link
Contributor

I propose to create a new golang.org/x/image/math/fixed package for common fixed-point types to be used by Go graphics libraries, whether on golang.org/x, github.com, or anywhere else.

This would be analogous to how golang.org/x/image/math/f64 holds common vector and matrix types for Go graphics libraries.

Specifically, the types would be:


package fixed

// Int26 is a 26.6 fixed point number.
type Int26 int32

// Int52 is a 52.12 fixed point number.
type Int52 int64


The fixed.Int26 type can be used to represent a sub-pixel position, such as by the Truetype hinting VM, and by the WebKit and Blink browser layout engines (https://trac.webkit.org/wiki/LayoutUnit and https://chromium.googlesource.com/chromium/blink.git/+/master/Source/platform/LayoutUnit.h).

I used 24.8 in the first version of my Freetype port (https://code.google.com/p/freetype-go/source/browse/freetype/raster/geom.go), but in hindsight, the difference between 6 and 8 sub-pixel bits isn't noticable visually, and while the relative difference between 24 and 26 is small, the range of the fixed point type becomes more noticable when e.g. you're trying to fit a kerning cache entry into 32 bits, and only half or less of those bits are usable for the (fixed point) adjustment. The upstream C version of Freetype uses 26.6.

@crawshaw
Copy link
Member

What is the argument for fixed-point types over float32 on modern hardware?

@ianlancetaylor
Copy link
Contributor

Apologies for being pedantic, but could you glance over http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1169.pdf and compare your proposal to that one? I want to make sure we avoid confusion when using a phrase like "fixed-point type".

@nigeltao
Copy link
Contributor Author

@crawshaw the argument for fixed point types is that floating point is simply more complicated: NaNs, negative zeros, adding non-zero values can be no-ops, adding then subtracting x can be lossy, etc.

In comparison, integer arithmetic is integer arithmetic. The only difference here is that the semantics is that the atomic unit is 1/64 of a pixel, not a pixel. Also, rounding to the nearest pixel is a simple bit mask.

I repeat that freetype-go already has this concept, since https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#performing_arithmetic says that "arithmetic is done on 26.6 fixed point numbers".

I also repeat that WebKit and Blink use 26.6 as their fundamental unit, not float32.

@crawshaw
Copy link
Member

So the arguments are: 1. it is simpler to understand in isolation, and 2. it is used by other projects.

That's a start, but I think it's worth exploring the interactions a bit to see how well those arguments hold up. It is simpler in isolation, yes, on the assumption that algorithms on pixels don't exhibit the same properties general numerics do that justify all those floating point bells and whistles. (And I believe that is true.) But it won't be isolated, it has to interact with geom.Pt, or we have to redefine it. It has to interact with darwin, which is all float32/float64. Is that going to be easy enough for it still to be on the whole simpler?

As to justifying it by other projects using it, why is it widely absent in OS X, except for WebKit, which they inherited from KHTML? Is this a historical mistake made by NeXT, or was it uninteresting on their hardware? Why did WebKit introduce it, would they do so again today?

I realize your code has widely used fixed-point before, and I'm not opposed to it. But as this is a fresh proposal, I think it's a good opportunity to make a clear justification for fixed point arithmetic in a world where micro-controllers come with an FPU.

@nigeltao
Copy link
Contributor Author

@ianlancetaylor I skimmed that document, and their _Accum concept looks broadly similar, although the position of the actual fixed point differs. Section A.3 suggests that 8.8, 16.16 or 8.24 might be the closest thing to this proposal's 26.6, but note that, like C's "int" meaning different things to different compilers, their "_Accum" can mean a different number of fractional bits to different compilers. For this proposal, fixed.Int26 means exactly 26.6, regardless of your Go compiler.

@nigeltao
Copy link
Contributor Author

@crawshaw BTW, this isn't really about x/mobile and its geom.Pt. The immediate motivation is to start thinking about an abstraction of font rendering that isn't necessarily tied to Freetype (and its idiosyncratic license). A draw.Image that you draw glyphs on always works in pixels and sub-pixels, not inches, so geom.Pt simply doesn't apply there.

Also, the footnote at the bottom of https://trac.webkit.org/wiki/LayoutUnit suggests that WebKit discussed this in 2012 and stuck with fixed point (changing the atom from 1/60 to 1/64) and didn't take the opportunity to move to floats, despite having to interop with floats and Darwin.

@nigeltao
Copy link
Contributor Author

Another reason for fixed over floating point is that I want to cache the kerning pairs: layout adjustments between two glyphs like how "AV" has overlap between the "A" and the "V".

One simple cache table design is to key by the low N bits of the glyph indices of the two glyphs involved. Kerns involve two glyphs, so the table size is potentially quadratic in (1<<N), so you don't want N to be too large. On the other hand, I'd like to fit each cach table entry inside a uint32, so if a glyph index is a uint16, as truetype glyph indices are, then you'd store the 2 * (16 - N) high bits in each cache table entry, which leaves 2 * N bits for the actual kern distance, with maybe a sentinel value to indicate overflow.

Truncating a 32-bit 26.6 fixed point value down to 2*N bits is easy. Again, it's a simple bit mask. Storing some subset of float32 values in less than 32 bits is not as trivial.

@klauspost
Copy link
Contributor

@crawshaw There is also the performance difference. Float has become faster on most platforms, but there is still a factor 2-4 in performance when dealing with images.

Also float<->int conversions (which are costly on i386 for instance) are completely avoided, and certain mod operations, as well as remainder can be calculated with simple & operations, which will always involve a division or float->int->float conversion.

The only thing I don't really know is that the actual size isn't obvious. What about this slightly longer form?

// Int26x6 is a 26.6 fixed point number.
type Int26x6 int32

// Int52x12 is a 52.12 fixed point number.
type Int52x12 int64

@crawshaw
Copy link
Member

@klauspost can you point me to recent information on fixed-point vs. floating point performance? The most recent numbers I found are 8 years old, and I have heard at least two contrary anecdotes about modern Intel chips. I've also found nothing about arm64, or where ARM chips are expected to be in the next couple of years.

Transistor constraints meant it used to make sense to run the FPUs at a different clock speed to the integer unit, and to have fewer FPUs than ALUs per core, so there were limited instruction dispatch advantages for float32. But those explanations were waning a decade ago. SSE is full of floating point ops now.

At this point I think any claim to performance advantages needs to be supported with recent data.

@klauspost
Copy link
Contributor

In general float point have much longer latency for operations on x64, which is measurable. Lets take a very simple example, where we don't give integers an unfair advantage. A simple average of two values:

f := (a+b) * 0.5
i := (a+b+1) >>1

The integer needs another instruction to maintain proper rounding.

The assembly would probably look like this on x64, here Intel(R) Core(TM) i7-5820K:

ADD r64, r64                L:  1.0c  T: 0.34c
ADD r64, imm8             L:  1.0c  T: 0.34c
SAR r64, imm8              L: 1.0c  T: 0.50c
Latency: 3 cycles, max thoughput: 1.18c
ADDSS xmm, xmm        L: 3.0c  T:  1.00c
MULSS xmm, xmm        L: 5.0c  T:  0.50c
Latency: 8 cycles, max throughput: 1.5 cycles

So with integer we have the result ready after 3 cycles, where it takes 8 cycles with float. Of course you can get closer to the throughput value if you 'hide' other instructions, but the base performance of integer operations are still better than float.

Integer multiply is 3 cycles compared to the 5 of a float point, notwithstanding the cases where you can shift instead. Also consider the 8 cycles going from integer -> float -> integer. x mod 1.0 is 11 cycles in float, 1 cycle with integer. Division is faster in float, but you rarely have to do them.

The only real advantage for float over fixed point is that SIMD is a lot easier with float. It is still slower, but easier to write.

@nigeltao
Copy link
Contributor Author

As for name bikeshedding, "x" doesn't feel quite right. Perhaps it could be
type Int26_6 int32

@robpike
Copy link
Contributor

robpike commented Jul 30, 2015

LGTM

@gopherbot
Copy link

CL https://golang.org/cl/12863 mentions this issue.

@golang golang locked and limited conversation to collaborators Aug 5, 2016
mrhyperbit23z0d added a commit to mrhyperbit23z0d/bhegde8 that referenced this issue Jun 6, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
GalaxyForcew added a commit to GalaxyForcew/A1bisshy that referenced this issue Jun 6, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
yi-ge3 added a commit to yi-ge3/wislie that referenced this issue Jun 6, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
balloontmz6 added a commit to balloontmz6/Likewise42l that referenced this issue Jun 6, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
snapbakkhfbav added a commit to snapbakkhfbav/SayedBaladohr that referenced this issue Oct 6, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
MiderWong5ddop added a commit to MiderWong5ddop/sidie88f that referenced this issue Oct 7, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
rorypeckwnt4v added a commit to rorypeckwnt4v/LearnByBhanuPrataph that referenced this issue Oct 7, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
egorovcharenko9 added a commit to egorovcharenko9/RiceBIOC470z that referenced this issue Oct 7, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
RafayGhafoorf added a commit to RafayGhafoorf/dustinsand8 that referenced this issue Oct 7, 2022
Fixes golang/go#11906

Change-Id: I2b43311ff145e8453cd255f085c82add6da7de5b
Reviewed-on: https://go-review.googlesource.com/12863
Reviewed-by: Rob Pike <r@golang.org>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants