Skip to content

Commit 78fb5ac

Browse files
committed
README: add benchmark
1 parent 1b328af commit 78fb5ac

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ unique_ints: stream[int] = (
544544
assert list(unique_ints) == [0, 1]
545545
```
546546

547-
## vs `builtins.map/filter`
547+
## performances
548548

549549
There is zero overhead during iteration compared to `builtins.map` and `builtins.filter`:
550550

@@ -558,6 +558,30 @@ odd_int_chars = stream(range(N)).filter(lambda n: n % 2).map(str)
558558
map(str, filter(lambda n: n % 2, range(N)))
559559
```
560560

561+
Let's get an idea of the throughput of operations compared to `builtins.map`:
562+
563+
|operation|times slower than `builtins.map(lambda _: _, range(N))`|
564+
|--|--|
565+
|`stream(range(N)).map(lambda _: _)`|1.0x|
566+
|`stream(range(N)).filter(lambda _: _)`|1.0x|
567+
|`stream((i,) for i in range(N)).flatten()`|1.8x|
568+
|`stream(range(N)).skip(N)`|1.8x|
569+
|`stream(range(N)).do(lambda _: _)`|1.9x|
570+
|`stream(range(N)).catch(ValueError)`|2.0x|
571+
|`stream(range(N)).group(5)`|2.3x|
572+
|`stream(range(N)).take(N)`|2.9x|
573+
|`stream(range(N)).observe('ints', do=bool)`|4.1x|
574+
|`stream(range(N)).observe('ints', do=bool, every=timedelta(...))`|5.4x|
575+
|`stream(range(N)).throttle(N, per=timedelta(...))`|5.7x|
576+
|`stream(range(N)).group(by=bool, up_to=5)`|7.2x|
577+
|`stream(range(N)).buffer(N)`|50x|
578+
|`stream(range(N)).map(lambda _: _, concurrency=2)`|320x|
579+
|`stream(range(N)).group(within=timedelta(...))`|340x|
580+
|`stream(range(N)).map(lambda _: _, concurrency=2, as_completed=True)`|340x|
581+
|`stream((i,) for i in range(N)).flatten(concurrency=2)`|840x|
582+
583+
(source: `pytest -s tests/benchmark.py`)
584+
561585
## e.g. ETL via [`dlt`](https://github.com/dlt-hub/dlt)
562586

563587
A `stream` is an expressive way to declare a `dlt.resource`:

tests/benchmark.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from collections.abc import Iterable
2+
from datetime import timedelta
3+
import timeit
4+
from streamable import stream
5+
6+
N = 1_000_000
7+
ints = stream(range(N))
8+
9+
def consume(s: Iterable):
10+
for _ in s:
11+
pass
12+
13+
baseline = timeit.timeit(lambda: consume(map(lambda _: _, ints)), number=10) / 10
14+
15+
for times, s in (
16+
(10, ints.map(lambda _: _)),
17+
(10, ints.filter(lambda _: _)),
18+
(10, stream((i,) for i in range(N)).flatten()),
19+
(10, ints.skip(N)),
20+
(10, ints.do(lambda _: _)),
21+
(10, ints.catch(ValueError)),
22+
(10, ints.group(5)),
23+
(10, ints.take(N)),
24+
(10, ints.observe("ints", do=bool)),
25+
(10, ints.observe("ints", do=bool, every=N)),
26+
(10, ints.observe("ints", do=bool, every=timedelta(seconds=1))),
27+
(10, ints.throttle(N, per=timedelta(seconds=1))),
28+
(10, ints.group(5, by=bool)),
29+
30+
(1, ints.buffer(N)),
31+
(1, ints.map(lambda _: _, concurrency=2)),
32+
(1, ints.group(within=timedelta(seconds=1))),
33+
(1, ints.map(lambda _: _, concurrency=2, as_completed=True)),
34+
(1, stream((i,) for i in range(N)).flatten(concurrency=2)),
35+
):
36+
duration = timeit.timeit(lambda: consume(s), number=times) / times
37+
print(s)
38+
print(f"is {duration/baseline:.1f}x slower than builtins.map")

0 commit comments

Comments
 (0)