Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speedup: lazy imports and remove import #276

Merged
merged 14 commits into from
Feb 2, 2024

Conversation

hugovk
Copy link
Member

@hugovk hugovk commented Feb 1, 2024

Command-line interfaces value speed, and unused imports can slow things down a lot.

We can use python -X importtime and tuna to identify bottlenecks: https://medium.com/alan/how-we-improved-our-python-backend-start-up-time-2c33cd4873c8

For example:

python3.13 -X importtime -c 'import prettytable' 2> import.log
tuna import.log

We're using Python 3.13.0a3 here, because it already includes some optimisations of its own.

Baseline

Starting on main, prettytable: 0.027 s (87.8%)

image

And let's begin by zooming into prettytable.prettytable: 0.010s (32.8%)

image

Lazy import json

json is a good first target. Taking 0.001 s (2.7%), it's only used by one function to load from JSON, and another to save. If doing plain text tables (or HTML etc), you won't use them.
Let's move the import into those two functions where it's actually needed:

image

Result: prettytable.prettytable: 0.009 s (30.3%)

Lazy import random

random is only used by a function to set random styles, and is mostly for fun. Lazy import it to save 0.001 s (1.8%):

image

Result: prettytable.prettytable 0.009 s (28.5%)

Lazy import csv

Like json, csv is only used for loading or saving CSV and takes 0.003 s (10.8%).

image

Result: prettytable.prettytable 0.008 s (27.1%)

Lazy import copy

Used in three places: a function to return a copy of a table. Is this used much? And also in other functions, themselves used when getting a number of string, HTML, CSV, JSON, Latex versions of a table. That's quite a few, so will likely end up being imported anyway, but it's a simple change and we may save 0.001 s (2.7%) in a few places.

image

Result: prettytable.prettytable 0.008 s (25.9%)

Lazy import textwrap

Only used in one function when getting a string version of a table. I think a common operation, but again, a quick saving for those cases when not.

image

Result: prettytable.prettytable 0.007 s (24.6%)

Lazy import wcwidth

Called by one function, but itself called several times, mostly for getting string tables? Well, saving of 0.001 s (2.2%) if not needed.

Result: prettytable.prettytable 0.007 s (23.0%)

Lazy import html.escape

Only used when getting HTML versions of tables: 0.001 s (2.3%)

image

Result: prettytable.prettytable 0.007 s (22.5%)

Replace math.floor with int

For positive inputs math.floor and int give the same results, we can avoid importing math to save a (rounded) 0.000 s (0.9%). We were calling int on the result of math.floor anyway!

image

Result: prettytable.prettytable 0.006 s (21.6%)

Lazy import importlib.metadata

Finally, let's step out back to prettytable:

image

importlib.metadata takes a big chunk of the original: 0.015 s (50.9%)!

We only use this to set __version__ on the off-chance someone might access it:

import importlib.metadata

__version__ = importlib.metadata.version(__name__)

We can instead only import when __version__ is accessed:

def __getattr__(name: str) -> Any:
    if name == "__version__":
        import importlib.metadata

        return importlib.metadata.version(__name__)

    msg = f"module '{__name__}' has no attribute '{name}'"
    raise AttributeError(msg)
image

Result: prettytable 0.007 s (65.1%).


Compare with our original prettytable: 0.027 s (87.8%), that's a big saving!

This is an idealised result and in reality a few of these will be imported, based on the use case. Still, those that do apply will help CLIs.

A quick look at the remaining "big" ones:

  • typing 0.003 s (24.3%) - not easy to remove without removing type hints entirely. The stdlib has already optimised typing for 3.13, so already better than before.
  • re 0.002 s (18.5%) - used to compile a regex to save time later. Used before calculating widths. Could potentially be replaced with some str.replace calls. Something to consider another time.
  • html.parser.HTMLParser 0.002 s (17.0%) - used to subclass. Could potentially be moved and only imported/created when needing to parse those things, but could technically change the public API. Something to consider another time.

But we've achieved enough here for now!

@hugovk hugovk added the changelog: Changed For changes in existing functionality label Feb 1, 2024
Copy link

codecov bot commented Feb 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (80da6be) 94.21% compared to head (6279c94) 94.38%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #276      +/-   ##
==========================================
+ Coverage   94.21%   94.38%   +0.16%     
==========================================
  Files           5        5              
  Lines        2387     2405      +18     
==========================================
+ Hits         2249     2270      +21     
+ Misses        138      135       -3     
Flag Coverage Δ
macos-latest 94.34% <100.00%> (+0.16%) ⬆️
ubuntu-latest 94.34% <100.00%> (+0.16%) ⬆️
windows-latest 94.30% <100.00%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hugovk hugovk changed the title Speedup: lazy import and replace imports Speedup: lazy imports and remove import Feb 2, 2024
@hugovk hugovk merged commit 666ccf3 into jazzband:main Feb 2, 2024
24 checks passed
@hugovk hugovk added the enhancement New feature or request label Feb 2, 2024
@hugovk hugovk deleted the lazy-load-imports branch February 2, 2024 16:48
@hugovk hugovk removed the enhancement New feature or request label Feb 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog: Changed For changes in existing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant