Skip to content

Commit b8d0323

Browse files
committed
Improve performance of include? by 5-10x
Rails uses IPAddr#include? to evaluate what it should use as the client's remote ip by filtering potential ips against a trusted list of internal ips. In a _very_ minimal app, #include? was showing up in a profile as ~1% of request time. The issue is that #include? was converting itself and the other value passed in to ranges of IPAddr. This mean as a worst case (where other is a non-IPAddr, like a String) then there would be 5 IPAddr instances created (other -> IPAddr, and two each for the conversions to ranges). However, wrapping the begin and end values as IPAddr is not needed because they are necessarily fixed addresses already. This patch extracts the logic for getting the begin_addr and end_addr from the #to_range method so that they can be used in #include? without having to instantiate so many IPAddr. Benchmark: ```ruby net1 = IPAddr.new("192.168.2.0/24") net2 = IPAddr.new("192.168.2.100") net3 = IPAddr.new("192.168.3.0") net4 = IPAddr.new("192.168.2.0/16") Benchmark.ips do |x| x.report("/24 includes address") { net1.include? net2 } x.report("/24 not includes address") { net1.include? net3 } x.report("/16 includes /24") { net4.include? net1 } x.report("/24 not includes /16") { net1.include? net4 } x.compare! end ``` Before: ``` Comparison: /24 not includes /16: 175041.3 i/s /24 not includes address: 164933.2 i/s - 1.06x (± 0.00) slower /16 includes /24: 163881.9 i/s - 1.07x (± 0.00) slower /24 includes address: 163558.4 i/s - 1.07x (± 0.00) slower ``` After: ``` Comparison: /24 not includes /16: 2588364.9 i/s /24 not includes address: 1474650.7 i/s - 1.76x (± 0.00) slower /16 includes /24: 1461351.0 i/s - 1.77x (± 0.00) slower /24 includes address: 1425463.5 i/s - 1.82x (± 0.00) slower ```
1 parent 33d2d14 commit b8d0323

File tree

1 file changed

+16
-14
lines changed

1 file changed

+16
-14
lines changed

lib/ipaddr.rb

+16-14
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,7 @@ def mask(prefixlen)
176176
def include?(other)
177177
other = coerce_other(other)
178178
return false unless other.family == family
179-
range = to_range
180-
other = other.to_range
181-
range.begin <= other.begin && range.end >= other.end
179+
begin_addr <= other.begin_addr && end_addr >= other.end_addr
182180
end
183181
alias === include?
184182

@@ -400,17 +398,6 @@ def hash
400398

401399
# Creates a Range object for the network address.
402400
def to_range
403-
begin_addr = (@addr & @mask_addr)
404-
405-
case @family
406-
when Socket::AF_INET
407-
end_addr = (@addr | (IN4MASK ^ @mask_addr))
408-
when Socket::AF_INET6
409-
end_addr = (@addr | (IN6MASK ^ @mask_addr))
410-
else
411-
raise AddressFamilyError, "unsupported address family"
412-
end
413-
414401
self.class.new(begin_addr, @family)..self.class.new(end_addr, @family)
415402
end
416403

@@ -491,6 +478,21 @@ def zone_id=(zid)
491478

492479
protected
493480

481+
def begin_addr
482+
@addr & @mask_addr
483+
end
484+
485+
def end_addr
486+
case @family
487+
when Socket::AF_INET
488+
@addr | (IN4MASK ^ @mask_addr)
489+
when Socket::AF_INET6
490+
@addr | (IN6MASK ^ @mask_addr)
491+
else
492+
raise AddressFamilyError, "unsupported address family"
493+
end
494+
end
495+
494496
# Set +@addr+, the internal stored ip address, to given +addr+. The
495497
# parameter +addr+ is validated using the first +family+ member,
496498
# which is +Socket::AF_INET+ or +Socket::AF_INET6+.

0 commit comments

Comments
 (0)