Skip to content

Commit

Permalink
Add scripts to benchmark parsers.
Browse files Browse the repository at this point in the history
  • Loading branch information
aaugustin committed Apr 11, 2023
1 parent a6e1497 commit bb17be2
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 0 deletions.
101 changes: 101 additions & 0 deletions experiments/optimization/parse_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Benchark parsing WebSocket frames."""

import subprocess
import sys
import timeit

from websockets.extensions.permessage_deflate import PerMessageDeflate
from websockets.frames import Frame, Opcode
from websockets.streams import StreamReader


# 256kB of text, compressible by about 70%.
text = subprocess.check_output(["git", "log", "8dd8e410"], text=True)


def get_frame(size):
repeat, remainder = divmod(size, 256 * 1024)
payload = repeat * text + text[:remainder]
return Frame(Opcode.TEXT, payload.encode(), True)


def parse_frame(data, count, mask, extensions):
reader = StreamReader()
for _ in range(count):
reader.feed_data(data)
parser = Frame.parse(
reader.read_exact,
mask=mask,
extensions=extensions,
)
try:
next(parser)
except StopIteration:
pass
else:
assert False, "parser should return frame"
reader.feed_eof()
assert reader.at_eof(), "parser should consume all data"


def run_benchmark(size, count, compression=False, number=100):
if compression:
extensions = [PerMessageDeflate(True, True, 12, 12, {"memLevel": 5})]
else:
extensions = []
globals = {
"get_frame": get_frame,
"parse_frame": parse_frame,
"extensions": extensions,
}
sppf = (
min(
timeit.repeat(
f"parse_frame(data, {count}, mask=True, extensions=extensions)",
f"data = get_frame({size})"
f".serialize(mask=True, extensions=extensions)",
number=number,
globals=globals,
)
)
/ number
/ count
* 1_000_000
)
cppf = (
min(
timeit.repeat(
f"parse_frame(data, {count}, mask=False, extensions=extensions)",
f"data = get_frame({size})"
f".serialize(mask=False, extensions=extensions)",
number=number,
globals=globals,
)
)
/ number
/ count
* 1_000_000
)
print(f"{size}\t{compression}\t{sppf:.2f}\t{cppf:.2f}")


if __name__ == "__main__":
print("Sizes are in bytes. Times are in µs per frame.", file=sys.stderr)
print("Run `tabs -16` for clean output. Pipe stdout to TSV for saving.")
print(file=sys.stderr)

print("size\tcompression\tserver\tclient")
run_benchmark(size=8, count=1000, compression=False)
run_benchmark(size=60, count=1000, compression=False)
run_benchmark(size=500, count=1000, compression=False)
run_benchmark(size=4_000, count=1000, compression=False)
run_benchmark(size=30_000, count=200, compression=False)
run_benchmark(size=250_000, count=100, compression=False)
run_benchmark(size=2_000_000, count=20, compression=False)

run_benchmark(size=8, count=1000, compression=True)
run_benchmark(size=60, count=1000, compression=True)
run_benchmark(size=500, count=200, compression=True)
run_benchmark(size=4_000, count=100, compression=True)
run_benchmark(size=30_000, count=20, compression=True)
run_benchmark(size=250_000, count=10, compression=True)
102 changes: 102 additions & 0 deletions experiments/optimization/parse_handshake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Benchark parsing WebSocket handshake requests."""

# The parser for responses is designed similarly and should perform similarly.

import sys
import timeit

from websockets.http11 import Request
from websockets.streams import StreamReader


CHROME_HANDSHAKE = (
b"GET / HTTP/1.1\r\n"
b"Host: localhost:5678\r\n"
b"Connection: Upgrade\r\n"
b"Pragma: no-cache\r\n"
b"Cache-Control: no-cache\r\n"
b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
b"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36\r\n"
b"Upgrade: websocket\r\n"
b"Origin: null\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Accept-Encoding: gzip, deflate, br\r\n"
b"Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,fr;q=0.7\r\n"
b"Sec-WebSocket-Key: ebkySAl+8+e6l5pRKTMkyQ==\r\n"
b"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
b"\r\n"
)

FIREFOX_HANDSHAKE = (
b"GET / HTTP/1.1\r\n"
b"Host: localhost:5678\r\n"
b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) "
b"Gecko/20100101 Firefox/111.0\r\n"
b"Accept: */*\r\n"
b"Accept-Language: en-US,en;q=0.7,fr-FR;q=0.3\r\n"
b"Accept-Encoding: gzip, deflate, br\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Origin: null\r\n"
b"Sec-WebSocket-Extensions: permessage-deflate\r\n"
b"Sec-WebSocket-Key: 1PuS+hnb+0AXsL7z2hNAhw==\r\n"
b"Connection: keep-alive, Upgrade\r\n"
b"Sec-Fetch-Dest: websocket\r\n"
b"Sec-Fetch-Mode: websocket\r\n"
b"Sec-Fetch-Site: cross-site\r\n"
b"Pragma: no-cache\r\n"
b"Cache-Control: no-cache\r\n"
b"Upgrade: websocket\r\n"
b"\r\n"
)

WEBSOCKETS_HANDSHAKE = (
b"GET / HTTP/1.1\r\n"
b"Host: localhost:8765\r\n"
b"Upgrade: websocket\r\n"
b"Connection: Upgrade\r\n"
b"Sec-WebSocket-Key: 9c55e0/siQ6tJPCs/QR8ZA==\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
b"User-Agent: Python/3.11 websockets/11.0\r\n"
b"\r\n"
)


def parse_handshake(handshake):
reader = StreamReader()
reader.feed_data(handshake)
parser = Request.parse(reader.read_line)
try:
next(parser)
except StopIteration:
pass
else:
assert False, "parser should return request"
reader.feed_eof()
assert reader.at_eof(), "parser should consume all data"


def run_benchmark(name, handshake, number=10000):
ph = (
min(
timeit.repeat(
"parse_handshake(handshake)",
number=number,
globals={"parse_handshake": parse_handshake, "handshake": handshake},
)
)
/ number
* 1_000_000
)
print(f"{name}\t{len(handshake)}\t{ph:.1f}")


if __name__ == "__main__":
print("Sizes are in bytes. Times are in µs per frame.", file=sys.stderr)
print("Run `tabs -16` for clean output. Pipe stdout to TSV for saving.")
print(file=sys.stderr)

print("client\tsize\ttime")
run_benchmark("Chrome", CHROME_HANDSHAKE)
run_benchmark("Firefox", FIREFOX_HANDSHAKE)
run_benchmark("websockets", WEBSOCKETS_HANDSHAKE)

0 comments on commit bb17be2

Please sign in to comment.