Skip to content

Autoload IPAddr instead of requiring it eagerly - #1099

Open
tas50 wants to merge 1 commit into
ruby:masterfrom
tas50:perf/autoload-ipaddr
Open

Autoload IPAddr instead of requiring it eagerly#1099
tas50 wants to merge 1 commit into
ruby:masterfrom
tas50:perf/autoload-ipaddr

Conversation

@tas50

@tas50 tas50 commented Aug 28, 2026

Copy link
Copy Markdown

Problem

lib/openssl/ssl.rb requires ipaddr at the top of the file. The only use is in OpenSSL::SSL.verify_certificate_identity, and only for certificates carrying an iPAddress SAN:

when 7 # iPAddress in GeneralName (RFC5280)
  should_verify_common_name = false
  if san.value.size == 4 || san.value.size == 16
    begin
      return true if san.value == IPAddr.new(hostname).hton
    rescue IPAddr::InvalidAddressError
    end
  end

Fix

-require "ipaddr"
+autoload :IPAddr, "ipaddr"

autoload rather than an inline require, so IPAddr stays resolvable — anything downstream that relied on require "openssl" defining it is unaffected, and there is no per-verification cost.

The rescue clause still resolves correctly: IPAddr.new triggers the autoload before it can raise, so IPAddr::InvalidAddressError is defined by the time the rescue is evaluated.

Measurements — and what this does not save

To be clear about scope: this does not save the require "socket" on the very next line, which is unconditional and unrelated. The saving is ipaddr.rb itself (857 lines).

Measured with socket already loaded, which is openssl's actual situation, Ruby 4.0.6 (arm64-darwin), 15 runs:

marginal cost of require "ipaddr" min median files
2.47 ms 2.95 ms 1

End-to-end require "openssl" moves from a min of 40.68 → 35.12 ms in one sample and 26.42 → 24.88 ms in another; that figure carries a lot of variance from loading openssl.so, so the marginal number above is the honest one.

Small in absolute terms, but openssl is loaded in a large share of Ruby processes, the change is one line, and there is no behavior difference.

Tests

bundle exec rake test: 630 tests, 0 failures, unchanged (633 with the additions).

The existing suite already exercises the IPAddr path directly — test_ssl.rb asserts verify_certificate_identity against a certificate with an IP:127.0.0.1 SAN, in both the matching and non-matching directions.

The three added tests cover that requiring openssl does not load ipaddr, that IPAddr still resolves afterwards, and that referencing it pulls the library in. The first fails against the previous code.

Related

ruby/net-http#337 makes the same change for Resolv in net/http, which uses it for two regexps on one line.

openssl/ssl.rb requires ipaddr at the top of the file. The only use is
in OpenSSL::SSL.verify_certificate_identity, and only for certificates
carrying an iPAddress SAN:

    return true if san.value == IPAddr.new(hostname).hton
  rescue IPAddr::InvalidAddressError

Autoload matches the shape of the change net/http is making for the
same constant and keeps IPAddr resolvable, so anything downstream that
relied on require "openssl" defining it is unaffected.

Note this does not save the socket require on the next line, which is
unconditional and unrelated. The saving is ipaddr.rb itself, 857 lines.
Measured with socket already loaded, which is openssl's actual
situation, on Ruby 4.0.6 (arm64-darwin), 15 runs:

  marginal cost of require "ipaddr"   min 2.47 ms / median 2.95 ms, 1 file

End-to-end require "openssl" moves from a min of 26.42 ms to 24.88 ms,
but that figure carries a lot of variance from loading openssl.so, so
the marginal number above is the honest one.

Small in absolute terms, but openssl is loaded in a large share of Ruby
processes, and the change is one line with no behavior difference.

The rescue clause still resolves correctly: IPAddr.new triggers the
autoload before it can raise, so IPAddr::InvalidAddressError is defined
by the time the rescue is evaluated.

Test suite: 630 tests, 0 failures, unchanged. The existing coverage
already exercises the IPAddr path directly, asserting
verify_certificate_identity against a cert with an IP:127.0.0.1 SAN.

The three added tests cover that requiring openssl does not load
ipaddr, that IPAddr still resolves afterwards, and that referencing it
pulls the library in. The first fails against the previous code.

Signed-off-by: Tim Smith <tsmith84@proton.me>
@rhenium

rhenium commented Aug 28, 2026

Copy link
Copy Markdown
Member

I think it's generally better to avoid registering an autoload for another library.

ipaddr is a small stdlib consisting of a single source file. Is lazy loading it actually a meaningful improvement?

Also, wouldn't most users reach OpenSSL::SSL.verify_certificate_identity through OpenSSL::SSL::SSLSocket anyway?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants