BitwiseIp.Blocks.optimize

You're seeing just the function optimize, go back to BitwiseIp.Blocks module for more information.

Specs

optimize(t()) :: t()

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:

  1. The size of the list matters, since a smaller list requires fewer individual checks.

  2. 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"]