Skip to content

Remove _get_[both]sep[s]() calls in os.path #117634

Closed as not planned
Closed as not planned

Description

@nineteendo

Feature or enhancement

Proposal:

Python function calls are always expensive. We can replace all _get_sep() & _get_bothseps() calls in os.path with ternary operators, merging with existing isinstance() checks where applicable. e.g:

-seps = _get_bothseps(p)
+seps = b'\\/' if isinstance(p, bytes) else '\\/'

Also, in posixpath.expanduser() we can replace root with the already assigned sep from earlier:

if isinstance(path, bytes):
    userhome = os.fsencode(userhome)
-    root = b'/'
-else:
-    root = '/'
-userhome = userhome.rstrip(root)
-return (userhome + path[i:]) or root
+userhome = userhome.rstrip(sep)
+return (userhome + path[i:]) or sep

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

Linked PRs

Activity

  1. erlend-aasland commented on Apr 8, 2024

    @erlend-aasland
    Contributor

    Python function calls are always expensive.

    This is being worked on:

  2. nineteendo commented on Apr 8, 2024

    @nineteendo
    ContributorAuthor

    OK, but calling a function will always have some overhead, we can never fully eliminate that (unless we add macro's to python).
    And the code will still fit on a single line, making the intention much clearer:

    -seps = _get_bothseps(p)
    +seps = b'\\/' if isinstance(p, bytes) else '\\/'
  3. added
    performancePerformance or resource usage
    stdlibStandard Library Python modules in the Lib/ directory
    on Apr 8, 2024
  4. terryjreedy commented on Apr 8, 2024

    @terryjreedy
    Member

    Why do you think that the isinstance call faster than the _get_bothseps call? Removing a redundant call is another matter.

  5. eryksun commented on Apr 8, 2024

    @eryksun
    Contributor

    @terryjreedy, _get_bothseps() calls isinstance(). The suggestion is to manually inline the code in the source at each call site since Python's compiler doesn't support inline function calls. It could be considered unnecessary optimization, however. Call overhead of a few nanoseconds is a drop in the bucket compared to work that requires a few microseconds.

  6. nineteendo commented on Apr 9, 2024

    @nineteendo
    ContributorAuthor

    It could be considered unnecessary optimization, however. Call overhead of a few nanoseconds is a drop in the bucket compared to work that requires a few microseconds.

    In my benchmarks, the new code is 4-12% faster: #117635 (comment). If you want benchmarks for longer input, let me know.

    Also, ntpath.expanduser() calls this function in every iteration of a loop, without saving the result:

    cpython/Lib/ntpath.py

    Lines 377 to 378 in e338e1a

    while i < n and path[i] not in _get_bothseps(path):
    i += 1

    Doing that makes it 61% faster for long user names (>100 characters).

  7. erlend-aasland commented on Apr 9, 2024

    @erlend-aasland
    Contributor

    I'm not sure the performance win outweights the increase in code duplication.

  8. nineteendo commented on Apr 9, 2024

    @nineteendo
    ContributorAuthor

    Do I need to make a separate issue for os.path.expanduser() (on posix we're doing a redundant isinstance() check) or can I also change the other functions?

  9. erlend-aasland commented on Apr 9, 2024

    @erlend-aasland
    Contributor

    As I've said earlier, micro-optimisations are generally hard to get support for; we mostly prefer code clarity, readability and maintainability over performance.

    I would instead encourage you to direct your enthusiasm towards the existing issues in the bug-tracker. There's close to 7000 issues in the tracker. What we really need help with, is triaging this heap of issues, fixing those who can be fixed, closing those who are invalid, etc. I think you could be of real help (and you would experience more support from the core team) if you directed your skills towards the bug-tracker.

  10. nineteendo commented on Apr 9, 2024

    @nineteendo
    ContributorAuthor

    I've made a separate issue for ntpath.expanduser(): #117686 We really shouldn't call _get_bothseps() in every iteration.
    I've also created 5 pull requests for existing issues in the issue tracker: https://github.com/python/cpython/pulls/nineteendo.
    So if you want, you can review them.

  11. erlend-aasland commented on Apr 9, 2024

    @erlend-aasland
    Contributor

    I've made a separate issue for ntpath.expanduser(): #117686 We really shouldn't call _get_bothseps() in every iteration.

    That's seems a fair point. I suggest to close this issue, though; IMO the performance gains do not outweigh the added code duplication nor the churn.

    I've also created 5 pull requests for existing issues in the issue tracker: https://github.com/python/cpython/pulls/nineteendo. So if you want, you can review them.

    That's great; thanks for helping reducing our backlog!

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

    performancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions