Skip to content

Instantly share code, notes, and snippets.

@amcgregor
Last active June 15, 2020 16:35
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save amcgregor/405354 to your computer and use it in GitHub Desktop.
Save amcgregor/405354 to your computer and use it in GitHub Desktop.
Python timeit benchmarking for realistic optimizations.
# All units in usecs (µsec) comparing Python 2.7 | 3.7.
# Last updated: 2019-02-11
# MacBook Pro (15-inch, 2016)
# macOS 10.14.3
# 2.7 GHz Intel Core i7
# 16 GB 2133 MHz LPDDR3
python -m timeit "200 <= 250 < 300" # 0.0354 | 0.059
python2.7 -m timeit "250 in xrange(200, 300)" # 1.25
python3.7 -m timeit "250 in range(200, 300)" # 0.324 -- yay for __contains__!
python -m timeit 's="Content-Type: text/html\r\n"' 'a,b = s.split(":", 1)' # 0.305 | 0.249
python -m timeit 's="Content-Type: text/html\r\n"' 'a,b = s.split(":")' # 0.287 | 0.285
python -m timeit 's="Content-Type: text/html\r\n"' 'a,c = s.partition(":")[::2]' # 0.22 | 0.275
python -m timeit 's="Content-Type: text/html\r\n"' 'a,b,c = s.partition(":")' # 0.13 | 0.189 !
# 3.8.2
python3.8 -m timeit 's="Content-Type: text/html\r\n"' 'a,b,c = s.partition(":")' # 281ns
python3.8 -m timeit 'import re; s="Content-Type: text/html\r\n"' 'a, b = re.split(":", s)' # 2µs (2000ns)
# Partition is faster, despite the extra variable assignment.
python -m timeit 's="Content-Type: text/html\r\n"' 's.upper()' # 0.348 | 0.121
python -m timeit 's="CONTENT-TYPE: text/html\r\n"' 's.upper()' # 0.307 | 0.106 !
python -m timeit 's="CONTENT-TYPE: TEXT/HTML\r\n"' 's.upper()' # 0.270 | 0.101 !
python -m timeit 's="Content-Type: text/html\r\n"' 's.title()' # 0.344 | 0.340
python -m timeit 's="Content-Type: text/html\r\n"' 's.lower()' # 0.274 | 0.102 !
# If you need to case-insensitively compare strings that are mostly lower-case, lower-case them.
python -m timeit 'a="foo";b="bar"' '", ".join((a, b))' # 0.136 | 0.158
python -m timeit 'a="foo";b="bar"' 'a + ", " + b' # 0.0668 | 0.135 !
# Join might be fancy for small lists or tuples, but it's a horrible waste of resources.
python -m timeit 'a="foo.bz"' 'a.endswith("bz")' # 0.164 | 0.157
python -m timeit 'a="foo.bz"' 'a[-2:] == "bz"' # 0.0676 | 0.132 !
# Slicing is fast, no, faster than that.
python -m timeit 'line="GET / HTTP/1.1"' 'a,b,c = line.split(" ", 2)' # 0.304 | 0.274
python -m timeit 'line="GET / HTTP/1.1"' 'a,_,_ = line.partition(" "); b,_,c = _.partition(" ")' # 0.226 | 0.307 !
python -m timeit 'line="GET / HTTP/1.1"' 'a,b,c = line.split(" ")' # 0.295 | 0.228 !
# Splitting is strange; how can limiting the split take -longer- than so much variable assignment?
python2.7 -m timeit 'from urllib import unquote; import re; qs = re.compile("(?i)%2F"); path = "/foo%2Fbar/bar/baz"' '[unquote(x) for x in qs.split(path)]' # 4.33
python3.7 -m timeit 'from urllib.parse import unquote; import re; qs = re.compile("(?i)%2F"); path = "/foo%2Fbar/bar/baz"' '[unquote(x) for x in qs.split(path)]' # 3.26
python2.7 -m timeit 'from urllib import unquote; import re; path = "/foo%2Fbar/bar/baz"' 'path.replace("%2F", "\0").replace("%2f", "\0"); path = unquote(path); path.replace("\0", "%2F")' # 3.69
python3.7 -m timeit 'from urllib.parse import unquote; import re; path = "/foo%2Fbar/bar/baz"' 'path.replace("%2F", "\0").replace("%2f", "\0"); path = unquote(path); path.replace("\0", "%2F")' # 5.79
# The decoding of URLs has some interesting requirements. Using regular expressions may be easy... but not fast.
python -m timeit 'line=""' 'line.split(",")' # 0.220 | 0.129
python -m timeit 'line=None' 'if line: line.split(",")' # 0.0192 | 0.0193 !
# It is better to test for a value before splitting, rather than splitting empty strings.
python -m timeit 'a="://"; b="http://www.example.com/"' 'a in b' # 0.0404 | 0.0484
python -m timeit 'a="://"; b="http://www.example.com/"' 'b.find(a)' # 0.186 | 0.167
python -m timeit 'a="://"; b="/foo/bar/baz"' 'a in b' # 0.0401 | 0.0505
python -m timeit 'a="://"; b="/foo/bar/baz"' 'b.find(a)' # 0.191 | 0.176
# In is faster, but doesn't return the offset.
python -m timeit 'uri="/foo/bar/baz"' 'uri.startswith("/")' # 0.174 | 0.174
python3.7 -m timeit 'uri="/foo/bar/baz"' 'uri[0] == "/"' # 0.0467 | 0.0555
python3.7 -m timeit 'uri="foo/bar/baz"' 'uri.startswith("/")' # 0.170 | 0.154
python3.7 -m timeit 'uri="foo/bar/baz"' 'uri[0] == "/"' # 0.0468 | 0.0597
# Array slicing from the beginning is much faster than str.startswith.
python -m timeit 'a=(1,2,3,4,5,6,7,8,9,0)' 'a[-5:][::-1]' # 0.146 | 0.192
python -m timeit 'a=(1,2,3,4,5,6,7,8,9,0)' 'a[::-1][:5]' # 0.167 | 0.185
python -m timeit 'a=(1,2,3,4,5,6,7,8,9,0)' 'a[:4:-1]' # 0.103 | 0.107 !
# Performing a single slice is faster than slicing broken apart, but...
# reversing a smaller slice is faster than reversing a large one. (Duh. ;)
python -m timeit 'then = 951196249, now = 1551196249' '(now - then) > 0.5' # 0.117
python -m timeit 'from datetime import datetime, timedelta; then = 951196249, now = 1551196249' \
'(datetime.fromtimestamp(now) - datetime.fromtimestamp(then)) > timedelta(seconds=0.5)' # 2.4 µs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment