diff --git a/.requirements/bench.txt b/.requirements/bench.txt
index 30701ef..4649bc8 100644
--- a/.requirements/bench.txt
+++ b/.requirements/bench.txt
@@ -5,3 +5,4 @@ aiohttp==3.9.3
 aiosonic==0.18.0
 niquests==3.5.2
 pycurl==7.45.3
+matplotlib==3.8.3
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 111ae6e..8fa30d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index d786184..44b7115 100644
--- a/README.md
+++ b/README.md
@@ -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:
 
@@ -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
 
diff --git a/benchmarks/README.md b/benchmarks/README.md
index 5f3444d..a9b8afb 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
 
diff --git a/benchmarks/linear_x100_1k.png b/benchmarks/linear_x100_1k.png
new file mode 120000
index 0000000..81889ef
--- /dev/null
+++ b/benchmarks/linear_x100_1k.png
@@ -0,0 +1 @@
+../docs/linear_x100_1k.png
\ No newline at end of file
diff --git a/benchmarks/p4_x100_1k.png b/benchmarks/p4_x100_1k.png
new file mode 120000
index 0000000..11412c7
--- /dev/null
+++ b/benchmarks/p4_x100_1k.png
@@ -0,0 +1 @@
+../docs/p4_x100_1k.png
\ No newline at end of file
diff --git a/benchmarks/single_x100_1k.png b/benchmarks/single_x100_1k.png
new file mode 120000
index 0000000..8a6b2af
--- /dev/null
+++ b/benchmarks/single_x100_1k.png
@@ -0,0 +1 @@
+../docs/single_x100_1k.png
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 58796e7..78daa2e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -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:
 
@@ -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 
diff --git a/docs/linear_x100_1k.png b/docs/linear_x100_1k.png
new file mode 100644
index 0000000..7d45405
Binary files /dev/null and b/docs/linear_x100_1k.png differ
diff --git a/docs/p4_x100_1k.png b/docs/p4_x100_1k.png
new file mode 100644
index 0000000..3e3bdb1
Binary files /dev/null and b/docs/p4_x100_1k.png differ
diff --git a/docs/single_x100_1k.png b/docs/single_x100_1k.png
new file mode 100644
index 0000000..43ebdba
Binary files /dev/null and b/docs/single_x100_1k.png differ
diff --git a/tools/docs/update-bench-charts.py b/tools/docs/update-bench-charts.py
new file mode 100755
index 0000000..310485a
--- /dev/null
+++ b/tools/docs/update-bench-charts.py
@@ -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()