GeezerVison is showing github.com — gh-91539: Small performance improvement of urrlib.request.getproxies_environment() by RaphaelMarinier · Pull Request #108771 · python/cpython
Conversation
RaphaelMarinier
commented
Sep 1, 2023
•
edited by bedevere-bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
This is a small (10 - 20%) and trivial performance improvement of urrlib.request.getproxies_environment() , typically useful when there are many environment variables to go over.
This function is typically called each time one does a request using the requests package, and can become a bottleneck when there are 1000s of (unrelated) environment variables, so any performance improvement in this function is welcome.
This is an additional performance improvement to what was already done as part of #91539.
Performance details
With 100 unrelated env vars:
python getproxies_environment_benchmark.py 100
Walltime of each invocation of getproxies_environment() 0.057759761810302734 ms
Walltime of each invocation of getproxies_environment_faster() 0.04382586479187012 ms
Speedup: 24.12388240830176 %
And with 5000:
Walltime of each invocation of getproxies_environment() 1.234044075012207 ms
Walltime of each invocation of getproxies_environment_faster() 1.0999860763549805 ms
Speedup: 10.863307184218716 %
With the code below:
import os
import sys
import time
def fill_env_dummy_values(n_proxies=4, n_non_proxies=10):
for i in range(n_non_proxies):
os.environ[f'dummy_environ_var_{i}'] = '0' * 50
for i in range(n_proxies):
os.environ[f'scheme_{i}_proxy'] = '0' * 20
# https://github.com/python/cpython/blob/f8be2e262c5c2fdbc9721210ae1cb46edc16db82/Lib/urllib/request.py#L2479
def getproxies_environment():
"""Return a dictionary of scheme -> proxy server URL mappings.
Scan the environment for variables named <scheme>_proxy;
this seems to be the standard convention. If you need a
different way, you can pass a proxies dictionary to the
[Fancy]URLopener constructor.
"""
# in order to prefer lowercase variables, process environment in
# two passes: first matches any, second pass matches lowercase only
# select only environment variables which end in (after making lowercase) _proxy
proxies = {}
environment = []
for name in os.environ.keys():
# fast screen underscore position before more expensive case-folding
if len(name) > 5 and name[-6] == "_" and name[-5:].lower() == "proxy":
value = os.environ[name]
proxy_name = name[:-6].lower()
environment.append((name, value, proxy_name))
if value:
proxies[proxy_name] = value
# CVE-2016-1000110 - If we are running as CGI script, forget HTTP_PROXY
# (non-all-lowercase) as it may be set from the web server by a "Proxy:"
# header from the client
# If "proxy" is lowercase, it will still be used thanks to the next block
if 'REQUEST_METHOD' in os.environ:
proxies.pop('http', None)
for name, value, proxy_name in environment:
# not case-folded, checking here for lower-case env vars only
if name[-6:] == '_proxy':
if value:
proxies[proxy_name] = value
else:
proxies.pop(proxy_name, None)
return proxies
# Speedup of
# https://github.com/python/cpython/blob/f8be2e262c5c2fdbc9721210ae1cb46edc16db82/Lib/urllib/request.py#L2479
def getproxies_environment_faster():
"""Return a dictionary of scheme -> proxy server URL mappings.
Scan the environment for variables named <scheme>_proxy;
this seems to be the standard convention. If you need a
different way, you can pass a proxies dictionary to the
[Fancy]URLopener constructor.
"""
# in order to prefer lowercase variables, process environment in
# two passes: first matches any, second pass matches lowercase only
# select only environment variables which end in (after making lowercase) _proxy
proxies = {}
environment = []
for name in os.environ: # No .keys() here.
# fast screen underscore position before more expensive case-folding
if len(name) > 5 and name[-6] == "_" and name[-5:].lower() == "proxy":
value = os.environ[name]
proxy_name = name[:-6].lower()
environment.append((name, value, proxy_name))
if value:
proxies[proxy_name] = value
# CVE-2016-1000110 - If we are running as CGI script, forget HTTP_PROXY
# (non-all-lowercase) as it may be set from the web server by a "Proxy:"
# header from the client
# If "proxy" is lowercase, it will still be used thanks to the next block
if 'REQUEST_METHOD' in os.environ:
proxies.pop('http', None)
for name, value, proxy_name in environment:
# not case-folded, checking here for lower-case env vars only
if name[-6:] == '_proxy':
if value:
proxies[proxy_name] = value
else:
proxies.pop(proxy_name, None)
return proxies
if __name__ == '__main__':
fill_env_dummy_values(n_proxies=4, n_non_proxies=int(sys.argv[1]))
# Make sure there is no pre-warming effect.
getproxies_environment()
getproxies_environment_faster()
num_repeats = 1000
t0 = time.time()
for _ in range(num_repeats):
getproxies_environment()
getproxies_environment_time_per_call = (time.time() - t0) / num_repeats
print('Walltime of each invocation of getproxies_environment()',
getproxies_environment_time_per_call * 1000, 'ms')
t0 = time.time()
for _ in range(num_repeats):
getproxies_environment_faster()
getproxies_environment_faster_time_per_call = (time.time() - t0) / num_repeats
print('Walltime of each invocation of getproxies_environment_faster()',
getproxies_environment_faster_time_per_call * 1000, 'ms')
print('Speedup:', (getproxies_environment_time_per_call - getproxies_environment_faster_time_per_call) / getproxies_environment_time_per_call * 100, '%')
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…are many environment variables. In a benchmark with 5k environment variables not related to proxies, and 5 specifying proxies, we get a 10% walltime improvement.
ghost
commented
Sep 1, 2023
•
edited by ghost
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
All commit authors signed the Contributor License Agreement. 
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
|
Small benchmark:
import os
import urllib.request
for ii in range(20):
os.environ[f'x{ii}_proxy']='a'
%timeit urllib.request.getproxies_environment()
results in
main: 70.2 µs ± 185 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
PR: 66.3 µs ± 2.41 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
So there is indeed a small performance improvement.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Thank you!
@hauntsaninja is there anything we still need to do for this pr?
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
kulikjak
pushed a commit
to kulikjak/cpython
that referenced
this pull request
Jan 22, 2024
…oxies_environment() (python#108771)
Small performance improvement of getproxies_environment() when there are many environment variables. In a benchmark with 5k environment variables not related to proxies, and 5 specifying proxies, we get a 10% walltime improvement.
aisk
pushed a commit
to aisk/cpython
that referenced
this pull request
Feb 11, 2024
…oxies_environment() (python#108771)
Small performance improvement of getproxies_environment() when there are many environment variables. In a benchmark with 5k environment variables not related to proxies, and 5 specifying proxies, we get a 10% walltime improvement.
Glyphack
pushed a commit
to Glyphack/cpython
that referenced
this pull request
Sep 2, 2024
…oxies_environment() (python#108771)
Small performance improvement of getproxies_environment() when there are many environment variables. In a benchmark with 5k environment variables not related to proxies, and 5 specifying proxies, we get a 10% walltime improvement.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|