Skip to content

Commit

Permalink
Publish benchmarks results.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvolodin7 committed Mar 7, 2024
1 parent f70069a commit baa4cd5
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 3 deletions.
1 change: 1 addition & 0 deletions .requirements/bench.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ aiohttp==3.9.3
aiosonic==0.18.0
niquests==3.5.2
pycurl==7.45.3
matplotlib==3.8.3
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

To see unreleased changes, please see the [CHANGELOG on the main branch guide](https://github.com/gufolabs/gufo_http/blob/main/CHANGELOG.md).

## [Unreleased]

### Added

* Benchmark results and charts.

## 0.1.1 - 2024-03-05

### Added
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*Gufo HTTP* is a high-performance Python HTTP client library that handles both asynchronous and synchronous modes.
It wraps famous [Reqwest][Reqwest] HTTP client, written in
[Rust][Rust] language with [PyO3][PyO3] wrapper.
Our task is to reach maximal performance while maintaining clean and easy-to use API.

The getting of single URL is a simple task:

Expand Down Expand Up @@ -49,7 +50,22 @@ async with HttpClient(auth=BasicAuth("scott", "tiger")) as client:

## Performance

Gufo HTTP is proved to be one of the fastest Python HTTP client available
in the various scenarios. For example:

### Single HTTP/1.1 requests scenario

![Single requests](docs/single_x100_1k.png)

### 100 Linear HTTP/1.1 requests scenario

![Linear requests](docs/linear_x100_1k.png)

### 100 Parallel HTTP/1.1 requests scenario

![Parallel requests](docs/p4_x100_1k.png)

Refer to [benchmarks](benchmarks/README.md) for details.

## On Gufo Stack

Expand Down
11 changes: 8 additions & 3 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Run tests:
pytest benchmarks/test_single_x100_1k.py
```

Results:
### Results (lower is better)
```
================================================================= test session starts =================================================================
platform linux -- Python 3.11.2, pytest-7.4.3, pluggy-1.4.0
Expand Down Expand Up @@ -106,6 +106,8 @@ Legend:
================================================================= 10 passed in 9.12s ==================================================================
```

![Median chart](single_x100_1k.png)

## 100 Linear HTTP/1.1 Requests

Perform set of 100 linear http requests to read 1kb text file using single client session
Expand All @@ -120,7 +122,7 @@ Run tests:
pytest benchmarks/test_linear_x100_1k.py
```

Results:
### Results (lower is better)
```
================================================================= test session starts =================================================================
platform linux -- Python 3.11.2, pytest-7.4.3, pluggy-1.4.0
Expand Down Expand Up @@ -153,6 +155,8 @@ Legend:
================================================================= 10 passed in 14.29s =================================================================
```

![Median chart](linear_x100_1k.png)

## 100 Parallel HTTP/1.1 Requests

Perform 100 HTTP/1.1 requests to read 1kb text file with concurrency of 4 maintaininng
Expand All @@ -169,7 +173,7 @@ Run tests:
pytest benchmarks/test_p4_x100_1k.py
```

Results:
### Results (lower is better)
```
================================================================= test session starts =================================================================
platform linux -- Python 3.11.2, pytest-7.4.3, pluggy-1.4.0
Expand Down Expand Up @@ -201,6 +205,7 @@ Legend:
OPS: Operations Per Second, computed as 1 / Mean
================================================================= 10 passed in 14.76s =================================================================
```
![Median chart](p4_x100_1k.png)

## Feedback

Expand Down
1 change: 1 addition & 0 deletions benchmarks/linear_x100_1k.png
1 change: 1 addition & 0 deletions benchmarks/p4_x100_1k.png
1 change: 1 addition & 0 deletions benchmarks/single_x100_1k.png
20 changes: 20 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ hero:
*Gufo HTTP* is a high-performance Python HTTP client library that handles both asynchronous and synchronous modes.
It wraps famous [Reqwest][Reqwest] HTTP client, written in
[Rust][Rust] language with [PyO3][PyO3] wrapper.
Our task is to reach maximal performance while maintaining clean and easy-to use API.

The getting of single URL is a simple task:

Expand Down Expand Up @@ -40,6 +41,25 @@ async with HttpClient(auth=BasicAuth("scott", "tiger")) as client:
...
```

## Performance

Gufo HTTP is proved to be one of the fastest Python HTTP client available
in the various scenarios. For example:

### Single HTTP/1.1 requests scenario

![Single requests](single_x100_1k.png)

### 100 Linear HTTP/1.1 requests scenario

![Linear requests](linear_x100_1k.png)

### 100 Parallel HTTP/1.1 requests scenario

![Parallel requests](p4_x100_1k.png)

Refer to [benchmarks](benchmarks.md) for details.

## On Gufo Stack

This product is a part of [Gufo Stack][Gufo Stack] - the collaborative effort
Expand Down
Binary file added docs/linear_x100_1k.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/p4_x100_1k.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/single_x100_1k.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
187 changes: 187 additions & 0 deletions tools/docs/update-bench-charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# ---------------------------------------------------------------------
# Gufo HTTP: Generate benchmark charts
# ---------------------------------------------------------------------
# Copyright (C) 2024, Gufo Labs
# See LICENSE.md for details
# ---------------------------------------------------------------------
"""Parse bechmark results and generate charts."""

# Python modules
import enum
import re
from dataclasses import dataclass
from typing import Iterable, List, Tuple

# Third-party modules
import matplotlib.pyplot as plt
from matplotlib import ticker

rx_name = re.compile(r"^Name \(time in (\S+)\)")


@dataclass
class Benchmark(object):
"""
Benchmark descriptor.
Attributes:
path: Output chart path.
title: Chart title.
"""

path: str
title: str


BENCHMARKS = [
Benchmark(
title="Single HTTP/1.1 Requests (Median)",
path="docs/single_x100_1k.png",
),
Benchmark(
title="100 Linear HTTP/1.1 Requests (Median)",
path="docs/linear_x100_1k.png",
),
Benchmark(
title="100 Parallel HTTP/1.1 Requests (Median)",
path="docs/p4_x100_1k.png",
),
]

NAME_MAP = {"gufo_http": "Gufo HTTP", "pycurl": "PycURL"}


def normalize_name(s: str) -> str:
"""
Normalize test name.
Args:
s: Test name.
Returns:
Normalized name.
"""
if s.startswith("test_"):
s = s[5:]
mode = ""
if s.endswith("_sync"):
s = s[:-5]
mode = " (Sync)"
elif s.endswith("_async"):
s = s[:-6]
mode = " (Async)"
s = NAME_MAP.get(s, s)
return f"{s}{mode}"


def build_barchart(
bench: Benchmark, data: List[Tuple[str, float]], scale: str
) -> None:
"""
Build bar chart into SVG file.
Args:
bench: Benchmark description.
data: List of (name, value).
scale: Time scale label.
"""

def is_gufo_http(s: str) -> bool:
return "Gufo HTTP" in s

# Extracting test names and measured values from the data
tests, values = zip(*data)

# Creating the bar chart
plt.figure(figsize=(10, 6))
plt.barh(
tests,
values,
color=[
"#2c3e50" if is_gufo_http(test) else "#34495e" for test in tests
],
)
plt.xlabel(f"Time ({scale})")
plt.title(bench.title)
# Adding thousands separator to y-axis labels
plt.gca().xaxis.set_major_formatter(ticker.StrMethodFormatter("{x:,.0f}"))
# Adding text annotations for ratio between each bar and smallest one
min_value = min(values)
for test, value in zip(tests, values):
ratio = value / min_value
fontweight = "bold" if is_gufo_http(test) else "normal"
plt.text(
value, test, f" x{ratio:.2f}", va="center", fontweight=fontweight
)
# Make y-axis labels bold for test names containing "gufo_http"
for tick_label in plt.gca().get_yticklabels():
if is_gufo_http(tick_label.get_text()):
tick_label.set_weight("bold")
# Adjusting right padding to shift border to the right
plt.subplots_adjust(right=1.3)
# Saving the plot as an SVG file
print(f"Writing {bench.path}")
plt.savefig(bench.path, format="png", bbox_inches="tight")
plt.close()


class Mode(enum.Enum):
"""
Parser mode.
Attributes:
WAITING: Waiting for table of results.
SKIP_LINE: Table detected, need to skip next line.
PARSING: Parsing table.
"""

WAITING = 0
SKIP_LINE = 1
PARSING = 2


def iter_results(path: str) -> Iterable[Tuple[str, List[Tuple[str, float]]]]:
"""
Read benchmarks docs and extract values for charts.
Args:
path: README.md path
Returns:
Yields tuple of (scale, data block)
"""
r = []
mode = Mode.WAITING
scale = None
with open(path) as fp:
for line in fp:
ln = line.strip()
if mode == Mode.WAITING:
m = rx_name.search(ln)
if m:
scale = m.group(1)
mode = Mode.SKIP_LINE
elif mode == Mode.SKIP_LINE:
mode = Mode.PARSING
elif mode == Mode.PARSING:
if ln.startswith("---"):
mode = Mode.WAITING
yield scale, r
r = []
else:
parts = ln.split()
name = normalize_name(parts[0])
value = float(parts[9].replace(",", ""))
r.append((name, value))


def main() -> None:
"""Main function."""
for bench, (scale, data) in zip(
BENCHMARKS, iter_results("benchmarks/README.md")
):
build_barchart(bench, data, scale)


if __name__ == "__main__":
main()

0 comments on commit baa4cd5

Please sign in to comment.