BitwiseIp.Blocks.optimize
optimize
, go back to BitwiseIp.Blocks module for more information.
Specs
Computes an equivalent list of blocks optimal for member?/2
.
While an individual BitwiseIp.Block.member?/2
call is already efficient,
the performance of member?/2
is sensitive to a couple of factors:
The size of the list matters, since a smaller list requires fewer individual checks.
The order of the elements in the list matters, since
member?/2
will exit early as soon as any individual check returns true.
To optimize for the size of the list, this function recursively merges any
two blocks where one is a subset of the other. This is tested using
BitwiseIp.Block.subnet?/2
. For example, 1.2.0.0/16
is a subset of
1.0.0.0/8
, so instead of calling BitwiseIp.Block.member?/2
on both of
them, we can simply check the larger range of the two - in this case,
1.0.0.0/8
.
The order can be optimized by placing larger blocks earlier in the list.
Assuming an even distribution of IP addresses, it's more likely for an
address to fall inside of a block that covers a wider range. Thus, we can
sort by the integer-encoded mask: a smaller mask means a shorter network
prefix, which means there are more addresses possible (see
BitwiseIp.Block.size/1
for more on computing the size of a block from its
mask).
This optimization is kind of a parlor trick cribbed from the cider library. Except in pathological cases, the run time cost of performing the optimization is likely larger than any performance gained by using the new list. As such, if you're going to use this function at all, it's only really appropriate to call at compile time, which means your original list of blocks has to be available statically.
Examples
iex> ["1.2.3.4", "1.2.3.0/24", "1.2.0.0/16", "1.0.0.0/8"]
...> |> BitwiseIp.Blocks.parse!()
...> |> BitwiseIp.Blocks.optimize()
...> |> Enum.map(&to_string/1)
["1.0.0.0/8"]
iex> ["1.2.0.0/16", "3.0.0.0/8"]
...> |> BitwiseIp.Blocks.parse!()
...> |> BitwiseIp.Blocks.optimize()
...> |> Enum.map(&to_string/1)
["3.0.0.0/8", "1.2.0.0/16"]
iex> ["1.2.0.0/16", "3.4.5.0/24", "1.0.0.0/8", "3.4.0.0/16"]
...> |> BitwiseIp.Blocks.parse!()
...> |> BitwiseIp.Blocks.optimize()
...> |> Enum.map(&to_string/1)
["1.0.0.0/8", "3.4.0.0/16"]