Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Make IPPrefix smaller? #167

Open
bradfitz opened this issue May 16, 2021 · 9 comments
Open

Make IPPrefix smaller? #167

bradfitz opened this issue May 16, 2021 · 9 comments

Comments

@bradfitz
Copy link
Contributor

(Re-filing #154, which was closed when half of it was done)

An IPPrefix is currently an IP + a uint16, 32 bytes.

Now that IPPrefix is opaque, we can make it smaller:

We could instead do:

type IPPrefix struct {
     addr uint128
     isv6 bool
     bits uint8
}

And we'd be at 24 bytes after padding: https://play.golang.org/p/opWXptWj6jQ

This all assumes an IPPrefix of an IPv6 zone doesn't make sense. Is that correct?

To which @danderson said:

I'm not sure if a v6+zone prefix makes sense. My mind immediately jumped to v6 link-local prefixes, which would implicitly be per-interface things, but I don't know if they conventionally use the addr zone to represent that scoping.

@mdlayher
Copy link
Member

AFAIK Dave is correct: IPv6 zone + prefix doesn't really make sense.

@josharian
Copy link
Collaborator

josharian commented May 16, 2021

What should ParseIPPrefix and IPPrefixFrom do when a zone is present? Panic? Strip the zone?

I think we should instead intern the (zone, bits) pair, thereby not losing any information, even if misguided.

type IPPrefix struct {
     addr uint128
     zb *intern.Value // of uint8 or struct { string, uint8 }
}

uint8s fit in an interface without allocation, so package intern shouldn't need new API for cheap operation when no zone is present. (I figure it's ok to allocate when used with zone, although it does contradict the docs that say that IPPrefixFrom doesn't allocate. We can change those docs.)

@bradfitz
Copy link
Contributor Author

so package intern shouldn't need new API for cheap operation when no zone is present

The intern package's global mutex is only tolerable because nobody really uses IPv6 zones. But IPPrefixes are used (a lot), so that global mutex would almost certainly suck. I'd want to make sure that IPPrefixFrom never hits a mutex. Which means we'd probably want 129 *intern.Values pre-allocated:

package netaddr

// &internBits[n] is the *intern.Value for an IPPrefix with /n bits.
var internBits [129]intern.Value

It takes 1KB; probably fine. Alternatively, that could be a fast path in the intern package.

@josharian
Copy link
Collaborator

josharian commented May 17, 2021

We can also store bits in addr if zb == z4.

@bradfitz
Copy link
Contributor Author

We can also store bits in addr if zb == z4.

I worry about conditional representations complicating a lot of stuff (since we'd need a separate way for IPv6 then), and perhaps making otherwise-inlinable things not.

josharian added a commit that referenced this issue May 20, 2021
An experiment for #167. I'd probably a failed experiment.

name                                old time/op    new time/op    delta
IPSetFuzz-8                           7.54µs ± 1%    7.61µs ± 1%     +0.87%  (p=0.004 n=10+10)
BinaryMarshalRoundTrip/ipv4-8         13.8ns ± 1%    14.0ns ± 1%     +1.12%  (p=0.000 n=10+10)
BinaryMarshalRoundTrip/ipv6-8         22.9ns ± 2%    23.1ns ± 1%       ~     (p=0.060 n=10+10)
BinaryMarshalRoundTrip/ipv6+zone-8     106ns ± 4%     106ns ± 0%       ~     (p=0.306 n=9+9)
StdIPv4-8                             91.6ns ± 2%    91.8ns ± 1%       ~     (p=0.182 n=9+10)
IPv4-8                                85.0ns ± 1%    83.9ns ± 0%     -1.35%  (p=0.000 n=8+10)
IPv4_inline-8                         88.7ns ± 2%    88.4ns ± 2%       ~     (p=0.497 n=10+9)
StdIPv6-8                              117ns ± 1%     117ns ± 0%       ~     (p=0.304 n=10+10)
IPv6-8                                 108ns ± 1%     108ns ± 1%       ~     (p=0.223 n=10+10)
IPv4Contains-8                        3.15ns ± 1%   11.95ns ± 1%   +279.39%  (p=0.000 n=10+10)
IPv6Contains-8                        3.82ns ± 3%   13.03ns ± 2%   +241.22%  (p=0.000 n=9+10)
ParseIP/v4-8                          14.0ns ± 1%    13.9ns ± 1%     -0.64%  (p=0.036 n=10+10)
ParseIP/v6-8                          69.1ns ± 3%    67.9ns ± 0%     -1.75%  (p=0.000 n=9+10)
ParseIP/v6_ellipsis-8                 43.7ns ± 2%    43.6ns ± 2%       ~     (p=0.796 n=10+10)
ParseIP/v6_v4-8                       43.5ns ± 2%    43.4ns ± 2%       ~     (p=0.549 n=9+10)
ParseIP/v6_zone-8                     85.3ns ± 3%    87.5ns ± 4%     +2.59%  (p=0.033 n=10+9)
StdParseIP/v4-8                       35.8ns ± 2%    35.8ns ± 1%       ~     (p=0.715 n=9+9)
StdParseIP/v6-8                       82.2ns ± 0%    83.1ns ± 0%     +1.02%  (p=0.000 n=8+9)
StdParseIP/v6_ellipsis-8              53.2ns ± 2%    53.9ns ± 1%     +1.29%  (p=0.001 n=10+10)
StdParseIP/v6_v4-8                    75.6ns ± 1%    76.1ns ± 0%     +0.73%  (p=0.000 n=9+10)
StdParseIP/v6_zone-8                  64.4ns ± 1%    65.3ns ± 1%     +1.43%  (p=0.000 n=10+10)
IPString/v4-8                         22.6ns ± 1%    22.6ns ± 1%       ~     (p=0.482 n=9+10)
IPString/v6-8                         62.2ns ± 0%    62.8ns ± 1%     +0.95%  (p=0.001 n=10+10)
IPString/v6_ellipsis-8                57.1ns ± 0%    57.6ns ± 1%     +0.85%  (p=0.000 n=9+10)
IPString/v6_v4-8                      58.7ns ± 1%    59.3ns ± 0%     +1.07%  (p=0.000 n=10+9)
IPString/v6_zone-8                    58.5ns ± 0%    59.0ns ± 1%     +0.90%  (p=0.000 n=10+10)
IPMarshalText-8                       21.3ns ± 1%    21.5ns ± 1%     +0.79%  (p=0.000 n=10+10)
IPPortString/v4-8                      159ns ± 0%     160ns ± 1%     +1.00%  (p=0.000 n=9+10)
IPPortString/v6-8                      123ns ± 1%     124ns ± 1%       ~     (p=0.147 n=9+10)
IPPortString/v6_ellipsis-8             119ns ± 1%     120ns ± 0%     +0.70%  (p=0.004 n=9+10)
IPPortString/v6_v4-8                   119ns ± 1%     120ns ± 1%     +1.07%  (p=0.000 n=10+9)
IPPortString/v6_zone-8                 121ns ± 1%     122ns ± 1%     +0.61%  (p=0.024 n=10+10)
IPPortMarshalText/v4-8                36.7ns ± 1%    36.8ns ± 1%       ~     (p=0.484 n=10+9)
IPPortMarshalText/v6-8                74.2ns ± 1%    74.9ns ± 1%     +0.92%  (p=0.003 n=9+10)
IPPortMarshalText/v6_ellipsis-8       71.7ns ± 1%    72.3ns ± 1%     +0.89%  (p=0.000 n=9+10)
IPPortMarshalText/v6_v4-8             73.9ns ± 1%    74.6ns ± 1%     +0.91%  (p=0.000 n=10+10)
IPPortMarshalText/v6_zone-8           73.1ns ± 1%    73.7ns ± 0%     +0.78%  (p=0.001 n=10+9)
IPPrefixMasking/IPv4_/32-8            4.01ns ± 5%    6.25ns ± 1%    +55.99%  (p=0.000 n=10+8)
IPPrefixMasking/IPv4_/17-8            3.94ns ± 0%    6.23ns ± 0%    +58.03%  (p=0.000 n=10+10)
IPPrefixMasking/IPv4_/0-8             3.95ns ± 0%    6.23ns ± 0%    +57.81%  (p=0.000 n=8+10)
IPPrefixMasking/IPv6_/128-8           3.94ns ± 1%    6.25ns ± 0%    +58.45%  (p=0.000 n=9+10)
IPPrefixMasking/IPv6_/65-8            4.01ns ± 4%    6.23ns ± 0%    +55.53%  (p=0.000 n=10+9)
IPPrefixMasking/IPv6_/0-8             3.95ns ± 1%    6.36ns ± 4%    +61.09%  (p=0.000 n=8+10)
IPPrefixMasking/IPv6_zone_/128-8      3.94ns ± 0%  107.53ns ± 2%  +2626.16%  (p=0.000 n=8+10)
IPPrefixMasking/IPv6_zone_/65-8       3.95ns ± 0%  106.50ns ± 0%  +2596.05%  (p=0.000 n=9+10)
IPPrefixMasking/IPv6_zone_/0-8        3.95ns ± 0%  107.22ns ± 0%  +2617.08%  (p=0.000 n=9+8)
IPPrefixMarshalText-8                 28.8ns ± 1%    39.4ns ± 1%    +36.75%  (p=0.000 n=10+10)
ParseIPPort/v4-8                      27.0ns ± 1%    27.1ns ± 1%     +0.53%  (p=0.041 n=10+9)
ParseIPPort/v6-8                      81.7ns ± 1%    81.1ns ± 1%     -0.73%  (p=0.037 n=10+10)
ParseIPPort/v6_ellipsis-8             56.9ns ± 2%    55.8ns ± 1%     -1.80%  (p=0.000 n=10+9)
ParseIPPort/v6_v4-8                   55.8ns ± 1%    56.0ns ± 1%       ~     (p=0.204 n=10+9)
ParseIPPort/v6_zone-8                  101ns ± 0%     102ns ± 0%     +1.68%  (p=0.000 n=8+9)
IPRangePrefixes-8                      563ns ± 1%     738ns ± 1%    +31.05%  (p=0.000 n=9+9)
IPRangePrefix-8                       13.1ns ± 0%    17.1ns ± 1%    +30.13%  (p=0.000 n=8+10)
IPNextPrior-8                          135ns ± 1%     135ns ± 1%       ~     (p=0.439 n=10+10)

name                                old alloc/op   new alloc/op   delta
IPSetFuzz-8                           1.90kB ± 1%    1.90kB ± 1%       ~     (p=0.956 n=10+10)
StdIPv4-8                              16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPv4-8                                 0.00B          0.00B            ~     (all equal)
IPv4_inline-8                          0.00B          0.00B            ~     (all equal)
StdIPv6-8                              16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPv6-8                                 0.00B          0.00B            ~     (all equal)
IPv4Contains-8                         0.00B          0.00B            ~     (all equal)
IPv6Contains-8                         0.00B          0.00B            ~     (all equal)
ParseIP/v4-8                           0.00B          0.00B            ~     (all equal)
ParseIP/v6-8                           0.00B          0.00B            ~     (all equal)
ParseIP/v6_ellipsis-8                  0.00B          0.00B            ~     (all equal)
ParseIP/v6_v4-8                        0.00B          0.00B            ~     (all equal)
ParseIP/v6_zone-8                      0.00B          0.00B            ~     (all equal)
StdParseIP/v4-8                        16.0B ± 0%     16.0B ± 0%       ~     (all equal)
StdParseIP/v6-8                        16.0B ± 0%     16.0B ± 0%       ~     (all equal)
StdParseIP/v6_ellipsis-8               16.0B ± 0%     16.0B ± 0%       ~     (all equal)
StdParseIP/v6_v4-8                     32.0B ± 0%     32.0B ± 0%       ~     (all equal)
StdParseIP/v6_zone-8                   16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPString/v4-8                          16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPString/v6-8                          48.0B ± 0%     48.0B ± 0%       ~     (all equal)
IPString/v6_ellipsis-8                 24.0B ± 0%     24.0B ± 0%       ~     (all equal)
IPString/v6_v4-8                       16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPString/v6_zone-8                     24.0B ± 0%     24.0B ± 0%       ~     (all equal)
IPMarshalText-8                        16.0B ± 0%     16.0B ± 0%       ~     (all equal)
IPPortString/v4-8                      26.0B ± 0%     26.0B ± 0%       ~     (all equal)
IPPortString/v6-8                       101B ± 0%      101B ± 0%       ~     (all equal)
IPPortString/v6_ellipsis-8             61.0B ± 0%     61.0B ± 0%       ~     (all equal)
IPPortString/v6_v4-8                   45.0B ± 0%     45.0B ± 0%       ~     (all equal)
IPPortString/v6_zone-8                 61.0B ± 0%     61.0B ± 0%       ~     (all equal)
IPPortMarshalText/v4-8                 24.0B ± 0%     24.0B ± 0%       ~     (all equal)
IPPortMarshalText/v6-8                 64.0B ± 0%     64.0B ± 0%       ~     (all equal)
IPPortMarshalText/v6_ellipsis-8        64.0B ± 0%     64.0B ± 0%       ~     (all equal)
IPPortMarshalText/v6_v4-8              64.0B ± 0%     64.0B ± 0%       ~     (all equal)
IPPortMarshalText/v6_zone-8            64.0B ± 0%     64.0B ± 0%       ~     (all equal)
IPPrefixMasking/IPv4_/32-8             0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv4_/17-8             0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv4_/0-8              0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv6_/128-8            0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv6_/65-8             0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv6_/0-8              0.00B          0.00B            ~     (all equal)
IPPrefixMasking/IPv6_zone_/128-8       0.00B         24.00B ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMasking/IPv6_zone_/65-8        0.00B         24.00B ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMasking/IPv6_zone_/0-8         0.00B         24.00B ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMarshalText-8                  24.0B ± 0%     24.0B ± 0%       ~     (all equal)
ParseIPPort/v4-8                       0.00B          0.00B            ~     (all equal)
ParseIPPort/v6-8                       0.00B          0.00B            ~     (all equal)
ParseIPPort/v6_ellipsis-8              0.00B          0.00B            ~     (all equal)
ParseIPPort/v6_v4-8                    0.00B          0.00B            ~     (all equal)
ParseIPPort/v6_zone-8                  0.00B          0.00B            ~     (all equal)
IPRangePrefixes-8                      0.00B          0.00B            ~     (all equal)
IPRangePrefix-8                        0.00B          0.00B            ~     (all equal)

name                                old allocs/op  new allocs/op  delta
IPSetFuzz-8                             30.0 ± 0%      30.0 ± 0%       ~     (all equal)
StdIPv4-8                               1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPv4-8                                  0.00           0.00            ~     (all equal)
IPv4_inline-8                           0.00           0.00            ~     (all equal)
StdIPv6-8                               1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPv6-8                                  0.00           0.00            ~     (all equal)
IPv4Contains-8                          0.00           0.00            ~     (all equal)
IPv6Contains-8                          0.00           0.00            ~     (all equal)
ParseIP/v4-8                            0.00           0.00            ~     (all equal)
ParseIP/v6-8                            0.00           0.00            ~     (all equal)
ParseIP/v6_ellipsis-8                   0.00           0.00            ~     (all equal)
ParseIP/v6_v4-8                         0.00           0.00            ~     (all equal)
ParseIP/v6_zone-8                       0.00           0.00            ~     (all equal)
StdParseIP/v4-8                         1.00 ± 0%      1.00 ± 0%       ~     (all equal)
StdParseIP/v6-8                         1.00 ± 0%      1.00 ± 0%       ~     (all equal)
StdParseIP/v6_ellipsis-8                1.00 ± 0%      1.00 ± 0%       ~     (all equal)
StdParseIP/v6_v4-8                      2.00 ± 0%      2.00 ± 0%       ~     (all equal)
StdParseIP/v6_zone-8                    1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPString/v4-8                           1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPString/v6-8                           1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPString/v6_ellipsis-8                  1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPString/v6_v4-8                        1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPString/v6_zone-8                      1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPMarshalText-8                         1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPortString/v4-8                       2.00 ± 0%      2.00 ± 0%       ~     (all equal)
IPPortString/v6-8                       3.00 ± 0%      3.00 ± 0%       ~     (all equal)
IPPortString/v6_ellipsis-8              3.00 ± 0%      3.00 ± 0%       ~     (all equal)
IPPortString/v6_v4-8                    3.00 ± 0%      3.00 ± 0%       ~     (all equal)
IPPortString/v6_zone-8                  3.00 ± 0%      3.00 ± 0%       ~     (all equal)
IPPortMarshalText/v4-8                  1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPortMarshalText/v6-8                  1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPortMarshalText/v6_ellipsis-8         1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPortMarshalText/v6_v4-8               1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPortMarshalText/v6_zone-8             1.00 ± 0%      1.00 ± 0%       ~     (all equal)
IPPrefixMasking/IPv4_/32-8              0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv4_/17-8              0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv4_/0-8               0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv6_/128-8             0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv6_/65-8              0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv6_/0-8               0.00           0.00            ~     (all equal)
IPPrefixMasking/IPv6_zone_/128-8        0.00           1.00 ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMasking/IPv6_zone_/65-8         0.00           1.00 ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMasking/IPv6_zone_/0-8          0.00           1.00 ± 0%      +Inf%  (p=0.000 n=10+10)
IPPrefixMarshalText-8                   1.00 ± 0%      1.00 ± 0%       ~     (all equal)
ParseIPPort/v4-8                        0.00           0.00            ~     (all equal)
ParseIPPort/v6-8                        0.00           0.00            ~     (all equal)
ParseIPPort/v6_ellipsis-8               0.00           0.00            ~     (all equal)
ParseIPPort/v6_v4-8                     0.00           0.00            ~     (all equal)
ParseIPPort/v6_zone-8                   0.00           0.00            ~     (all equal)
IPRangePrefixes-8                       0.00           0.00            ~     (all equal)
IPRangePrefix-8                         0.00           0.00            ~     (all equal)

Signed-off-by: Josh Bleecher Snyder <[email protected]>
@josharian
Copy link
Collaborator

I tried out combining bits and zone in #175. It...got ugly. Among other things we still need a way to distinguish IPv4 from IPv6, so we ended up needing two internBits arrays. And we ended up with a bunch of type switches and goo, making otherwise-inlinable things not.

So I guess we're back to the API question: What should ParseIPPrefix and IPPrefixFrom do when a zone is present? Panic? Strip the zone?

And then we should run an experiment to see how the code actually looks. There's something delightfully lightweight about having two fields each of which is itself already in the desired form. I'm leaning towards not doing anything here.

@danderson
Copy link
Member

So, it seems our prime question is whether zones ever make sense in a prefix, and we still don't know. The closest piece of software that might care is CoreRAD, but since @mdlayher hasn't screamed yet I assume it doesn't need prefix+zone. I can't think of another piece of software that would care about scoped addrs at all, let alone scoped prefixes.

Given that, the question I have would be: how hard would it be to re-add zone support later if it turns out we were wrong? If it's not an API change (aside from documenting that ParseIPPrefix now accepts zones), then we can cut out zones for now, make everything more optimized, and still have an out if it turns out prefix+zone is in fact important.

The main thing to answer there is what our current error-less functions (i.e. IPPrefixFrom, pfx.WithIP if we ever implement that) do when handed a zoned IP? We can:

  • Silently strip the zone. That would lead to a semantic change if we allow zoned prefixes down the road, which might break existing software. Or we have to add a new API (e.g. IPPrefixFromZoned).
  • Add an error return to those methods, so we can error gracefully when handed a zoned IP. Makes those calls very heavyweight compared to where they are now, too much so imo.
  • Panic when handed a zoned IP. Can be gracefully upgraded to "don't panic" if we need to support zoned prefixes down the road, but runtime panics are rude.

Given the options, I lean slightly towards silently (but documentedly) stripping the zone when constructing prefixes. We can add an additional API to allow zoned prefixes down the road (or accept a very small semantic change for programs that passed zoned IPs into prefix construction), and it avoids both rude panics and verbose error paths.

@bradfitz
Copy link
Contributor Author

SGTM (slightly drop zone, documented, and we can always add more API later if a need arises)

@josharian
Copy link
Collaborator

Do we want UnmarshalText and ParseIPPrefix to drop zone or return an error? An error seems nicer, but it makes the behavior slightly inconsistent. (ParseIP + Atoi + IPPrefixFrom != ParseIPPrefix)

danderson added a commit that referenced this issue May 31, 2021
Enabler for #167.

Signed-off-by: David Anderson <[email protected]>
bradfitz pushed a commit that referenced this issue Jun 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants