Skip to content

MacOS Big Sur import ldap slow #487

Open

Description

@variable

Issue description:
Takes about 5 seconds to run import ldap

import time
print(1111111111111111)
start = time.time()
import ldap
print(2222222222222222)
print(time.time() - start)

which calls site-packages/ldap/__init__.py

...
  _trace_level = 0
  _trace_file = sys.stderr
  _trace_stack_limit = None
import time
s = time.time()
print('aaaaaaaaaaaaaaaaaa')
import _ldap
print('bbbbbbbbbbbbbbbbb')
print(time.time() - s)
assert _ldap.__version__==__version__, \
       ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!')
from _ldap import *
...

output

1111111111111111
aaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbb
5.0554280281066895
2222222222222222
5.1632020473480225

Steps to reproduce:

Operating system:
MacOS Big Sur

Python version:
3.7.13

python-ldap version:
3.4.2

Activity

  1. variable commented on Aug 23, 2022

    @variable
    Author

    I did a more simple python file just to import _ldap and profiled it and got this

    image

  2. mistotebe commented on Aug 28, 2026

    @mistotebe
    Contributor

    Hi, is this still happening for you? If so, can you provide strace output as well?

  3. spaceone commented on Sep 1, 2026

    @spaceone
    Contributor

    btw: the regular import on Debian is also not as fast as wished. It takes 300ms on some test virtual machines.

  4. mistotebe commented on Sep 2, 2026

    @mistotebe
    Contributor

    Same, strace might show where the time went (-tt and/or -T to include timing info)

  5. spaceone commented on Sep 10, 2026

    @spaceone
    Contributor

    strace reveals:

    Phase Time in trace Approx. cost
    Python startup + site initialization 10.296 → 10.376 ~80 ms
    ldap starts, stdlib helpers 10.376 → 10.402 ~26 ms
    _ldap.so + native LDAP/SASL/TLS/Kerberos stack 10.402 → ~10.480 ~78 ms
    ldap.functions, ldapobject, schema + huge stdlib dependency tree ~10.480 → 10.621 ~141 ms
    LDAP controls + pyasn1 10.621 → ~10.694 ~73 ms
    remaining extop initialization + shutdown ~10.694 → 10.731 ~37 ms

    We have some SASL client modules which are loaded:

    17:35:10.437356 ... libgnutls.so.30
    17:35:10.446612 ... libgssapi_krb5.so.2
    17:35:10.449088 ... libkrb5.so.3
    17:35:10.450751 ... libkrb5support.so.0
    17:35:10.461888 ... sasl2/libsasldb.so
    17:35:10.465109 ... libgssapi.so.3
    17:35:10.466214 ... libkrb5.so.26
    17:35:10.474966 ... sasl2/libgssapiv2.so
    

    python3 -X importtime -c 'import ldap' is also helpful for the pure python import timings:

    Import Self time Cumulative time Share of total
    ldap.functions 0.67 ms 149.96 ms ~71%
    ldap.ldapobject 1.01 ms 124.42 ms ~59%
    ldap.schema 0.40 ms 68.06 ms ~32%
    ldap.schema.subentry 1.02 ms 67.67 ms ~32%
    urllib.request 4.46 ms 60.22 ms ~29%
    _ldap 36.72 ms 36.72 ms ~17%
    ldap.controls 0.65 ms 28.23 ms ~13%
    ldap.extop 0.47 ms 26.71 ms ~13%
    ldap.extop.dds 1.10 ms 25.71 ms ~12%
    pyasn1_modules.rfc2251 21.05 ms 21.43 ms ~10%
    traceback 1.56 ms 18.77 ms ~9%
    pprint 0.74 ms 17.53 ms ~8%

    ldap.schema.subentry imports urllib.request, which is a particularly expensive dependency tree:

    base64
    email
    hashlib
    http.client
    email.parser
    email.feedparser
    email.header
    random
    socket
    selectors
    urllib.parse
    ipaddress
    ssl
    tempfile
    shutil
    bz2
    lzma
    ...
    

    Maybe this can be deferred a little bit into the case where it's actually needed.
    Python 3.14 has lazy import - that might be nice to use in the future ;-)

    pyasn1 adds another significant chunk because LDAP controls and extended operations (also DDS) are imported - result4() requires the control decoding methods.

    pprint is only in if __debug__: calls and could be lazy-imported.

    But I don't want to hijack the original problem. We rewrote one component in rust due to this.

  6. mistotebe commented on Sep 24, 2026

    @mistotebe
    Contributor

    Yes, reorganising the imports might make sense. Still 5s sounds like an issue somewhere (not quite waiting for a timeout though) rather than something like ~300ms which is more along the lines I was thinking being slow? We can move to that but I'd like @variable to check that's also what they're seeing and not e.g. some poll/select calls dominating the trace.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions