Skip to content

Commit 5ce7fd3

Browse files
committed
started writing docs + generation of benchmarks
1 parent 6ca4f6b commit 5ce7fd3

23 files changed

Lines changed: 1683 additions & 301 deletions

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ help:
460460
@echo " make build - build wheel"
461461
@echo " make clean - clean everything"
462462
@echo ""
463+
@echo "── Lint ────────────────────────────────────────"
464+
@echo " make lint - check code with linter and formatter.
465+
@echo ""
463466
@echo "── Examples ────────────────────────────────────────"
464467
@echo " make run-examples - run all docs/examples/*.py under ASan"
465468
@echo ""

README.md

Lines changed: 90 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,130 @@
11
# Puring
2+
TODO - PyPi link
23

3-
Experimental async runtime for Python built on Linux io_uring.
4+
Puring allows true async file i/o natively for Python by bringing Event Loop based on io_uring. Implemented in CPython.
45

5-
~2× faster file I/O than asyncio thread pools in early benchmarks.
6+
⚠️ Currently in active development phase, so ABI and internals may change and will be expanded.
67

7-
Puring enables true async file I/O in Python without relying on thread pools,
8-
using Linux io_uring and a CPython C-extension runtime.
9-
10-
⚠️
11-
12-
Experimental project \
13-
APIs and internals may change \
14-
Used for experimenting with async I/O performance in Python \
15-
Looking for contributors and feedback \
16-
⚠️
17-
18-
## Why Puring?
19-
* **True Async File I/O:** Unlike epoll-based `asyncio` and `uvloop`, `puring` is based on io_uring, which provides real async I/O without thread pools for files \
20-
<small> For full explanation, go [here](docs/uring/URING.md) </small>
21-
* **Seamless Integration:** Designed to work as a plug-in for the standard `asyncio` event loop.
22-
* **Low Overhead:** C-implemented request registry with $O(1)$ lookup.
23-
* **Simple Architecture:** Simple layered architecture that makes it easy to understand what is happening internally.
24-
* **Full io_uring support** The goal is to progressively implement all io_uring features.
25-
* **C-Python API** Implemented using CPython C-API for minimal overhead and full control over memory and GIL behavior.
268

279
## Quick Examples:
2810
### Files:
2911
```python
3012
async def main():
31-
file = await puring.open_file(path='testfile.txt')
32-
33-
data = b'Hello, puring!\n'
34-
await file.write(data=data)
35-
await file.read()
36-
37-
await file.close()
13+
async with puring.open_file(path='testfile.txt') as file:
14+
data = b'Hello, puring!\n'
15+
await file.write(data=data)
16+
data = await file.read()
3817

3918
asyncio.run(main(), loop_factory=puring.PuringLoop)
4019
```
4120

4221
### Sockets:
4322
```python
44-
HOST = "127.0.0.1"
45-
PORT = 9000
46-
PAYLOAD = b"hello"
47-
4823
async def main():
49-
sock = await puring.prep_socket()
50-
51-
await sock.connect(HOST, PORT)
52-
await sock.send(PAYLOAD)
53-
data = await sock.recv()
54-
print("received:", data)
55-
await sock.close()
24+
async with await puring.prep_socket() as socket:
25+
await socket.connect('127.0.0.1', 9000)
26+
await socket.send(b'hello')
27+
data = await socket.recv()
5628

5729
asyncio.run(main(), loop_factory=puring.PuringLoop)
5830
```
5931

60-
## Comparison
61-
| Feature | asyncio | uvloop | puring |
62-
| ------------------- | ------------ | ------------ | -------- |
63-
| Async files | ❌ threadpool | ❌ threadpool | ✅ native |
64-
| Syscalls | many | many | minimal |
65-
| Kernel batching ||||
66-
| Zero-copy potential ||||
32+
### One of io_uring optimization features - Fixed buffers:
33+
```python
34+
async def main():
35+
loop = asyncio.get_running_loop()
36+
buf = bytearray(4096)
37+
with loop.buffer_mode(mode=puring.BUFFER_MODE.FIXED, buffers=[buf]):
38+
await simple_socket_example()
39+
40+
asyncio.run(main(), loop_factory=puring.PuringLoop)
41+
```
6742

43+
### See the whole [user guide](docs/USER_GUIDE.md)
44+
### See ABI in [documentation page]() and locally [here]().
45+
### Also you can watch examples inside `docs/examples` and run them under ASAN with
46+
> make run-examples
6847
69-
## Quick Install
48+
## Installation
7049
#### Warning! Linux only
71-
> git clone git@github.com:AivazianArtur/puring.git \
72-
> cd puring \
50+
Puring requires linux kernel version 6.11 and Python 3.12 or greater.
51+
Library is available on PyPI, so use pip to install it:
52+
> pip install puring
53+
54+
## Build and use
55+
To build and install use
7356
> make install
7457
75-
## Architecture
76-
### Why Python needs it
77-
It brings proactor pattern to Python in Linux, that:
78-
* Allows implementation of async file I/O operations.
79-
* Enables designs compatible with upcoming no-GIL Python efforts.
80-
81-
### Current State
82-
* Core C-engine for Ring management.
83-
* Registry-based request tracking to connect futures with their result from CQE.
84-
* Python C-API bridge for `asyncio.Future` resolution.
85-
* Basic file usage. Brings true Async I/O.
86-
* Basic socket usage.
87-
88-
### Goal
89-
* Progressive coverage of io_uring features.
90-
### How it works
91-
To read about implementation details, go to [architecture page](docs/ARCHITECTURE.md)
58+
You can only build by using
59+
> make build
9260
61+
Run tests:
62+
> make test-all
9363
94-
## Benchmarks
95-
On simple file benchmarks, `Puring` is showing that even in pre-alpha mode and with many features to come, it is already provide truly async file ops 2x-faster than other Python solutions. \
96-
For ping-pong benchmark of sockets, puring now shows results close or event better than `uvloop`. It is proof of concept.
64+
Run tests under ASAN:
65+
> make test-all-asan
9766
98-
### File Results:
67+
While working with code, dont forget to
68+
> make lint
9969
100-
![file benchmark result](docs/assets/benchmark_results/files_benchmark.png)
70+
#### Watch more commands inside `Makefile`. Currently tested only on Fedora 43 with 7.1.5 kernel version.
10171

102-
### Sockets Results:
72+
## Architecture
73+
### io_uring
74+
Puring is written natively in CPython and brings the new event loop, based on io_uring. \
75+
What is io_uring and how it works, explained for Python developers - [here](docs/IO_URING.md)
76+
77+
### Presenting new objects
78+
- Main:
79+
- PuringLoop
80+
- File
81+
- Socket
82+
- Helpers:
83+
- BufferModeCtx
84+
- StreamStrategyCtx
85+
- TransferModeCtx
86+
- ExecutionContextCtx
87+
- Enums:
88+
- BUFFER_MODE
89+
- STREAM_STRATEGY
90+
- TRANSFER_MODE
91+
- PAYLOAD_TYPE
92+
- Resolve Flags
93+
- Statx Flags
94+
- StatxMask
95+
96+
Whole documentatation about their purposes and how they work is [here](docs/ARCHITECTURE.md)
97+
98+
### Structure
99+
Structure of this project is trying to be simple - we have pure c-layer and pure python-layer and layer in between.
100+
- C-layer - Functionality written entirely in C and basically wrappers aroung liburing ABI
101+
- Python-layer - By analogy it is layer, written with usage of CPython API and CPython objects.
102+
- Layer in between - for now here is really only one thing - registry. It is container to hold objects in between. I wish i could say that in between C-layer and Python-layer, but the meaning is not so. We are working with async nature and giving control of the operations to kernel, so we can do our things. To map the result of kernel with what was intended we need some sort of storage - this is registry.
103+
104+
### Domains
105+
- Ring
106+
- Event Loop
107+
- Reader
108+
- OPS (подтипы)
109+
- Buffers(внутри написать про фиксед буфер и про открытие/закрытие буфер модов)
110+
- Execution Context(внутри написать про другие)
111+
- Registry
112+
- Timer. То что ниже убрать на отдельную страницу
103113

104-
![socket benchmark result](docs/assets/benchmark_results/sockets/avg_latency.png)
114+
To read about implementation details, go to [architecture page](docs/ARCHITECTURE.md)
105115

106-
To learn more, go to [benchmarks documentation](docs/BENCHMARK.md)
107116

117+
## Benchmarks
108118

109119
## Contributing
110-
To start contribute, go to our [contributing guideline](docs/guidelines/CONTRIBUTING.md)
111-
112120
We are looking for help with:
113-
1. Testing on different Linux Distros/Kernels.
114-
2. Sharing experience in memory management, libraries architecture and many other things
115-
3. Write tests and benchmarks \
116-
And many more, see our [roadmap](docs/ROADMAP.md)
117-
118-
## Using
119-
### Developer
120-
To install, go to [installation page](docs/guidelines/INSTALLATION.md) \
121-
To start contribute, go to [developer guideline](docs/guidelines/DEVELOPING.md) and [contribution guideline](docs/guidelines/CONTRIBUTING.md)
122-
123-
### User
124-
See how to use here - [usage guide](docs/USAGE.md)
121+
1. Write new functionality. See our [roadmap](docs/ROADMAP.md) - you can add new checks yourself, but create an issue first.
122+
2. Testing on different Linux Distros/Kernels. If you'll find some issues - create some on GitHub.
123+
3. Sharing experience in memory management, libraries architecture, cpython, io_uring, epoll and many other things.
124+
4. Write tests and benchmarks \
125125

126+
* Whole contribution culture should be vaccinated to this project, so if you are experienced in this things - welcome, please.
127+
* Beware of [developer guideline](docs/guidelines/DEVELOPING.md)
126128

127129
## Roadmap
128130
See [here](docs/ROADMAP.md)

docs/ARCHITECTURE.md

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,72 @@
11
## Principles:
2-
* Strong layers between Python interface and C-functionality
3-
* Trying to stick to DDD principles
4-
5-
## Domains could be present at:
6-
* Only in `Python` layer
7-
* Only in `C` layer
8-
* In both layers
9-
10-
## Main Domains:
11-
### Python-layer only domains
12-
* Initialization
13-
* Loop
14-
* Signals
15-
* Future
16-
* Reader
17-
* Macroses
18-
* GIL Releaser
19-
* Loop Thread Sentinel
20-
### C-layer only domains
21-
* Rings (now ring only)
22-
* Buffers (now only implicit toy buffers)
23-
* Registry
24-
### Common domains
25-
* Ops
26-
* Files
27-
* Sockets
28-
29-
## Fluidity design remark:
30-
DDD means iterative architecture designing: while you are building system, you are learning more about it. So for this moment in code some domains are not as explicit as they could be.
31-
32-
#TODO: Describe Domains
2+
- Strong layers between Python interface and C-functionality
3+
- Trying to stick to DDD principles
4+
5+
### Structure
6+
Structure of this project is trying to be simple - we have pure c-layer and pure python-layer and layer in between.
7+
- C-layer - Functionality written entirely in C and basically wrappers aroung liburing ABI
8+
- Python-layer - By analogy it is layer, written with usage of CPython API and CPython objects.
9+
- Layer in between - for now here is really only one thing - registry. It is container to hold objects in between. I wish i could say that in between C-layer and Python-layer, but the meaning is not so. We are working with async nature and giving control of the operations to kernel, so we can do our things. To map the result of kernel with what was intended we need some sort of storage - this is registry.
10+
11+
## Introduction
12+
### io_uring
13+
Puring is written natively in CPython and brings the new event loop, based on io_uring.
14+
io_uring is an alternate for
15+
16+
What is io_uring and how it works, explained for Python developers - [here](TODO)
17+
18+
### Domains
19+
20+
1. Ring
21+
2. Event loop
22+
3. Reader
23+
4. OPS
24+
5. Buffer
25+
6. ExecutionContext
26+
7. Registry
27+
8. Timer
28+
29+
Main challenge was to connect Python event loop and io_uring.
30+
31+
1. From io_uring side main thing is `ring` itself. Ring contains two rings actually - `Submission Queue` with `SQE` and `Completion Queue` with `CQE`(`E` is from `event`). It really important to understand this concepts, but it all really gives us only one domain - `ring`.
32+
33+
2. From python side there is main object too - `Event Loop`. Loop is complicated, and its complicity is shown in code: by `loop` domain with `PuringLoop` object and by `reader` domain.
34+
35+
3. `Reader` is part of loop that reads result of I/O multiplexing mechanism(the output part). In our case it reads directly result of operations, but when you work with `epoll` its different.
36+
37+
4. Next domain is `OPS`, which contains `File` and `Socket` objects. This domain is mirrorly presented in both c-layer and python-layer. Its purpose is to be an API of system operations.
38+
39+
5. To read and write from/to Files and Sockets, we need buffers. And there is separate `Buffer` domain for this purpose.
40+
41+
6. But also io_uring gives us some workarounds for buffers, for example `PROVIDED` buffer mode, where you allocating buffers for io_uring and than io_uring controlls them, not you. There are more `Buffer Modes`, and there is also two another dimensions of modding ops - `Stream Strategy` and `Transfer Mode`. All this are parts of `ExecutionContext` Domain.
42+
43+
7. `Registry` is storage of operations, to map them in `Reader`.
44+
45+
8. `Timer` - as we are waiting for kernel to done the operation, we can set timeouts. That is purpose of this layer.
46+
47+
48+
### New python objects
49+
- Main:
50+
- PuringLoop
51+
- File
52+
- Socket
53+
- Helpers:
54+
- BufferModeCtx
55+
- StreamStrategyCtx
56+
- TransferModeCtx
57+
- ExecutionContextCtx
58+
- Enums:
59+
- BUFFER_MODE
60+
- STREAM_STRATEGY
61+
- TRANSFER_MODE
62+
- PAYLOAD_TYPE
63+
- Resolve Flags
64+
- Statx Flags
65+
- StatxMask
66+
67+
68+
## Domains
69+
70+
## Python objects
71+
72+
#NOTE While PuringLoop is child of BaseEventLoop, PuringSocket and PuringFile are build from scratch.

0 commit comments

Comments
 (0)