Opened 3 years ago

Closed 3 years ago

#32945 closed Cleanup/optimization (invalid)

Improve performance of HttpRequest._current_scheme_host()

Reported by: David Smith Owned by: nobody
Component: HTTP handling Version: 3.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

_current_scheme_host() currently uses .format. I propose that we change this to an f-string for a c.7.5% performance gain.

There's a few different ways this could be written, I think OptionTwo would be the one which would meet Django's f-string guidelines. I've shown a few different versions to show that moving to f-string is the big win here and adding a few extra variables (or not) as no impact.

One thing to note is that this function is decorated with cached_property but I've removed this to benchmark the underlying function.

.....................
Current: Mean +- std dev: 243 us +- 4 us
.....................
Option One: Mean +- std dev: 226 us +- 2 us
.....................
Option Two: Mean +- std dev: 225 us +- 3 us
.....................
Option Three: Mean +- std dev: 226 us +- 2 us
import pyperf
from django.conf import settings
from django.http import HttpRequest
import django


settings.configure(ALLOWED_HOSTS = ['localhost'])
django.setup()

class CurrentHttpRequest(HttpRequest):
    def _current_scheme_host(self):
        return '{}://{}'.format(self.scheme, self.get_host())

class OptionOneHttpRequest(HttpRequest):
    def _current_scheme_host(self):
        return f'{self.scheme}://{self.get_host()}'

class OptionTwoHttpRequest(HttpRequest):
    def _current_scheme_host(self):
        host = self.get_host()
        return f'{self.scheme}://{host}'

class OptionThreeHttpRequest(HttpRequest):
    def _current_scheme_host(self):
        scheme = self.scheme
        host = self.get_host()
        return f'{scheme}://{host}'

def current(loops):
    request = CurrentHttpRequest()
    request.META['SERVER_NAME'] = 'localhost'
    request.META['SERVER_PORT'] = '80'
    range_it = range(loops)
    t0 = pyperf.perf_counter()

    for loop in range_it:
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()

    return pyperf.perf_counter() - t0

def option_one(loops):
    request = OptionOneHttpRequest()
    request.META['SERVER_NAME'] = 'localhost'
    request.META['SERVER_PORT'] = '80'
    range_it = range(loops)
    t0 = pyperf.perf_counter()

    for loop in range_it:
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()

    return pyperf.perf_counter() - t0

def option_two(loops):
    request = OptionTwoHttpRequest()
    request.META['SERVER_NAME'] = 'localhost'
    request.META['SERVER_PORT'] = '80'
    range_it = range(loops)
    t0 = pyperf.perf_counter()

    for loop in range_it:
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()

    return pyperf.perf_counter() - t0

def option_three(loops):
    request = OptionThreeHttpRequest()
    request.META['SERVER_NAME'] = 'localhost'
    request.META['SERVER_PORT'] = '80'
    range_it = range(loops)
    t0 = pyperf.perf_counter()

    for loop in range_it:
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()
        request._current_scheme_host()

    return pyperf.perf_counter() - t0

runner = pyperf.Runner()
runner.bench_time_func('Current', current)
runner.bench_time_func('Option One', option_one)
runner.bench_time_func('Option Two', option_two)
runner.bench_time_func('Option Three', option_three)

Change History (1)

comment:1 by Mariusz Felisiak, 3 years ago

Component: UncategorizedHTTP handling
Resolution: invalid
Status: newclosed
Type: UncategorizedCleanup/optimization

Thanks for this proposition, however I don't think it's worth changing. I don't see a significant improvement in Python 3.8:

.....................
Current: Mean +- std dev: 29.4 us +- 1.9 us
.....................
Option One: Mean +- std dev: 28.9 us +- 0.7 us
.....................
Option Two: Mean +- std dev: 28.9 us +- 0.7 us
.....................
Option Three: Mean +- std dev: 29.2 us +- 1.5 us

and in Python 3.10 is even worse:

.....................
Current: Mean +- std dev: 34.4 us +- 1.2 us
.....................
Option One: Mean +- std dev: 34.2 us +- 0.9 us
.....................
Option Two: Mean +- std dev: 34.7 us +- 0.9 us
.....................
Option Three: Mean +- std dev: 34.8 us +- 0.9 us
Note: See TracTickets for help on using tickets.
Back to Top