turbopipe 0.0.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of turbopipe might be problematic. Click here for more details.
- turbopipe-1.0.0/.gitattributes +2 -0
- turbopipe-1.0.0/.github/workflows/release.yaml +83 -0
- turbopipe-1.0.0/.gitignore +4 -0
- turbopipe-1.0.0/License.md +21 -0
- turbopipe-1.0.0/PKG-INFO +34 -0
- turbopipe-1.0.0/examples/basic.py +80 -0
- turbopipe-1.0.0/meson.build +50 -0
- turbopipe-1.0.0/pyproject.toml +19 -0
- turbopipe-1.0.0/turbopipe/__init__.py +41 -0
- turbopipe-1.0.0/turbopipe/_turbopipe.cpp +258 -0
- turbopipe-1.0.0/turbopipe/include/gl_methods.hpp +2591 -0
- turbopipe-1.0.0/turbopipe/include/glcorearb.h +5991 -0
- turbopipe-1.0.0/turbopipe/include/khrplatform.h +311 -0
- turbopipe-1.0.0/turbopipe/version.py +3 -0
- turbopipe-0.0.0/PKG-INFO +0 -12
- turbopipe-0.0.0/pyproject.toml +0 -13
- /turbopipe-0.0.0/README.md → /turbopipe-1.0.0/Readme.md +0 -0
- /turbopipe-0.0.0/turbopipe/__init__.py → /turbopipe-1.0.0/examples/benchmark.py +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
paths:
|
|
7
|
+
- 'turbopipe/version.py'
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
sdist:
|
|
11
|
+
name: Package source
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
|
|
18
|
+
- name: deps
|
|
19
|
+
run: python -m pip install -U pip wheel build
|
|
20
|
+
|
|
21
|
+
- name: sdist
|
|
22
|
+
run: python -m build --sdist -o package
|
|
23
|
+
|
|
24
|
+
- name: upload
|
|
25
|
+
uses: actions/upload-artifact@v3
|
|
26
|
+
with:
|
|
27
|
+
name: package
|
|
28
|
+
path: package/*.tar.gz
|
|
29
|
+
|
|
30
|
+
wheels:
|
|
31
|
+
name: Build ${{matrix.pyver}} wheels on ${{matrix.os}}
|
|
32
|
+
runs-on: ${{matrix.os}}
|
|
33
|
+
strategy:
|
|
34
|
+
matrix:
|
|
35
|
+
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
|
|
36
|
+
pyver: [cp39, cp310, cp311, cp312]
|
|
37
|
+
|
|
38
|
+
env:
|
|
39
|
+
CIBW_BUILD: ${{matrix.pyver}}-*
|
|
40
|
+
CIBW_ARCHS_LINUX: auto
|
|
41
|
+
CIBW_ARCHS_MACOS: arm64
|
|
42
|
+
CIBW_ARCHS_WINDOWS: auto
|
|
43
|
+
CIBW_SKIP: '*musllinux* *i686* *-win32'
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- uses: actions/setup-python@v5
|
|
48
|
+
|
|
49
|
+
- name: deps
|
|
50
|
+
run: python -m pip install cibuildwheel==2.19.2
|
|
51
|
+
|
|
52
|
+
- name: wheels
|
|
53
|
+
run: python -m cibuildwheel --output-dir package
|
|
54
|
+
|
|
55
|
+
- name: upload
|
|
56
|
+
uses: actions/upload-artifact@v3
|
|
57
|
+
with:
|
|
58
|
+
name: package
|
|
59
|
+
path: package/*.whl
|
|
60
|
+
|
|
61
|
+
publish:
|
|
62
|
+
needs: [sdist, wheels]
|
|
63
|
+
name: Publish to PyPI
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- uses: actions/setup-python@v5
|
|
69
|
+
|
|
70
|
+
- name: Download artifacts
|
|
71
|
+
uses: actions/download-artifact@v3
|
|
72
|
+
with:
|
|
73
|
+
name: package
|
|
74
|
+
path: package
|
|
75
|
+
|
|
76
|
+
- name: deps
|
|
77
|
+
run: python -m pip install -U twine
|
|
78
|
+
|
|
79
|
+
- name: publish
|
|
80
|
+
env:
|
|
81
|
+
TWINE_USERNAME: __token__
|
|
82
|
+
TWINE_PASSWORD: ${{secrets.PYPI_TOKEN}}
|
|
83
|
+
run: twine upload package/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Gabriel Tremeschin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
turbopipe-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: turbopipe
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 🌀 Faster ModernGL Buffer inter process data transfers
|
|
5
|
+
Home-page: https://brokensrc.dev
|
|
6
|
+
Author-Email: Tremeschin <29046864+Tremeschin@users.noreply.github.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2024 Gabriel Tremeschin
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Project-URL: Issues, https://github.com/BrokenSource/TurboPipe/issues
|
|
29
|
+
Project-URL: Repository, https://github.com/BrokenSource/TurboPipe
|
|
30
|
+
Project-URL: Documentation, https://github.com/BrokenSource/TurboPipe
|
|
31
|
+
Project-URL: Homepage, https://brokensrc.dev
|
|
32
|
+
Requires-Python: >=3.7
|
|
33
|
+
Requires-Dist: moderngl
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import random
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Generator
|
|
5
|
+
|
|
6
|
+
import moderngl
|
|
7
|
+
import tqdm
|
|
8
|
+
import turbopipe
|
|
9
|
+
|
|
10
|
+
# User constants
|
|
11
|
+
WIDTH, HEIGHT = (1920, 1080)
|
|
12
|
+
FRAMERATE = 60
|
|
13
|
+
DURATION = 30
|
|
14
|
+
|
|
15
|
+
# Calculate constants
|
|
16
|
+
BYTES_PER_FRAME = (WIDTH * HEIGHT * 3)
|
|
17
|
+
TOTAL_FRAMES = (DURATION * FRAMERATE)
|
|
18
|
+
TOTAL_BYTES = (BYTES_PER_FRAME * TOTAL_FRAMES)
|
|
19
|
+
|
|
20
|
+
# Create ModernGL objects
|
|
21
|
+
ctx = moderngl.create_standalone_context()
|
|
22
|
+
buffer = ctx.buffer(reserve=BYTES_PER_FRAME)
|
|
23
|
+
|
|
24
|
+
# Let's play fair and avoid any OS/Python/Hardware optimizations
|
|
25
|
+
buffer.write(bytearray(random.getrandbits(8) for _ in range(BYTES_PER_FRAME)))
|
|
26
|
+
|
|
27
|
+
# -------------------------------------------------------------------------------------------------|
|
|
28
|
+
|
|
29
|
+
@contextlib.contextmanager
|
|
30
|
+
def FFmpeg() -> Generator[subprocess.Popen, None, None]:
|
|
31
|
+
try:
|
|
32
|
+
ffmpeg = subprocess.Popen([
|
|
33
|
+
"ffmpeg",
|
|
34
|
+
"-hide_banner",
|
|
35
|
+
"-loglevel", "error",
|
|
36
|
+
"-f", "rawvideo",
|
|
37
|
+
"-pix_fmt", "rgb24",
|
|
38
|
+
"-s", f"{WIDTH}x{HEIGHT}",
|
|
39
|
+
"-r", str(FRAMERATE),
|
|
40
|
+
"-i", "-",
|
|
41
|
+
"-f", "null",
|
|
42
|
+
"-", "-y"
|
|
43
|
+
], stdin=subprocess.PIPE)
|
|
44
|
+
|
|
45
|
+
yield ffmpeg
|
|
46
|
+
finally:
|
|
47
|
+
ffmpeg.stdin.close()
|
|
48
|
+
ffmpeg.wait()
|
|
49
|
+
|
|
50
|
+
# -------------------------------------------------------------------------------------------------|
|
|
51
|
+
|
|
52
|
+
@contextlib.contextmanager
|
|
53
|
+
def Progress():
|
|
54
|
+
with tqdm.tqdm(total=TOTAL_FRAMES, unit="Frame", smoothing=0) as frame_bar, \
|
|
55
|
+
tqdm.tqdm(total=TOTAL_BYTES, unit="B", smoothing=0, unit_scale=True) as byte_bar:
|
|
56
|
+
def next():
|
|
57
|
+
byte_bar.update(BYTES_PER_FRAME)
|
|
58
|
+
frame_bar.update(1)
|
|
59
|
+
yield next
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------------------------------------------------|
|
|
62
|
+
|
|
63
|
+
print("\n:: Traditional method\n")
|
|
64
|
+
|
|
65
|
+
with Progress() as progress, FFmpeg() as ffmpeg:
|
|
66
|
+
for frame in range(TOTAL_FRAMES):
|
|
67
|
+
ffmpeg.stdin.write(buffer.read())
|
|
68
|
+
progress()
|
|
69
|
+
|
|
70
|
+
print("\n:: TurboPipe method\n")
|
|
71
|
+
|
|
72
|
+
with Progress() as progress, FFmpeg() as ffmpeg:
|
|
73
|
+
fileno = ffmpeg.stdin.fileno()
|
|
74
|
+
|
|
75
|
+
for frame in range(TOTAL_FRAMES):
|
|
76
|
+
turbopipe.pipe(buffer, fileno)
|
|
77
|
+
progress()
|
|
78
|
+
turbopipe.sync()
|
|
79
|
+
|
|
80
|
+
turbopipe.close()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ----------------------------------------------|
|
|
2
|
+
|
|
3
|
+
project('turbopipe', 'cpp',
|
|
4
|
+
version: run_command(
|
|
5
|
+
['turbopipe/version.py'],
|
|
6
|
+
check: true
|
|
7
|
+
).stdout().strip(),
|
|
8
|
+
|
|
9
|
+
default_options: [
|
|
10
|
+
'warning_level=3',
|
|
11
|
+
'cpp_std=c++11',
|
|
12
|
+
'buildtype=release',
|
|
13
|
+
'optimization=3'
|
|
14
|
+
]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# ----------------------------------------------|
|
|
18
|
+
# Platform flags
|
|
19
|
+
|
|
20
|
+
cpp_args = ['-fpermissive']
|
|
21
|
+
|
|
22
|
+
if host_machine.system() == 'darwin'
|
|
23
|
+
cpp_args += ['-Wno-deprecated-declarations']
|
|
24
|
+
endif
|
|
25
|
+
|
|
26
|
+
# ----------------------------------------------|
|
|
27
|
+
# Source files
|
|
28
|
+
|
|
29
|
+
incdir = include_directories('turbopipe/include')
|
|
30
|
+
source = files('turbopipe/_turbopipe.cpp')
|
|
31
|
+
|
|
32
|
+
# ----------------------------------------------|
|
|
33
|
+
|
|
34
|
+
python = import('python').find_installation()
|
|
35
|
+
|
|
36
|
+
python.extension_module(
|
|
37
|
+
'_turbopipe', source,
|
|
38
|
+
include_directories: incdir,
|
|
39
|
+
cpp_args: cpp_args,
|
|
40
|
+
install: true,
|
|
41
|
+
subdir: 'turbopipe'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
python.install_sources(
|
|
45
|
+
['turbopipe/__init__.py'],
|
|
46
|
+
subdir: 'turbopipe',
|
|
47
|
+
pure: false
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# ----------------------------------------------|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project.urls]
|
|
2
|
+
issues = "https://github.com/BrokenSource/TurboPipe/issues"
|
|
3
|
+
repository = "https://github.com/BrokenSource/TurboPipe"
|
|
4
|
+
documentation = "https://github.com/BrokenSource/TurboPipe"
|
|
5
|
+
homepage = "https://brokensrc.dev"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "turbopipe"
|
|
9
|
+
dynamic = ["version"]
|
|
10
|
+
description = "🌀 Faster ModernGL Buffer inter process data transfers"
|
|
11
|
+
authors = [{name="Tremeschin", email="29046864+Tremeschin@users.noreply.github.com"}]
|
|
12
|
+
readme = "Readme.md"
|
|
13
|
+
license = {file="License.md"}
|
|
14
|
+
dependencies = ["moderngl"]
|
|
15
|
+
requires-python = ">=3.7"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["meson-python", "ninja"]
|
|
19
|
+
build-backend = "mesonpy"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from io import IOBase
|
|
2
|
+
|
|
3
|
+
from moderngl import Buffer
|
|
4
|
+
|
|
5
|
+
from turbopipe import _turbopipe
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def pipe(buffer: Buffer, file: IOBase) -> None:
|
|
9
|
+
"""
|
|
10
|
+
Pipe the content of a moderngl.Buffer to a file descriptor,
|
|
11
|
+
Fast, threaded and non-blocking. Call `sync()` when done!
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
```python
|
|
15
|
+
# Assuming `buffer = ctx.buffer(...)`
|
|
16
|
+
# Note: Use as `fbo.read_into(buffer)`
|
|
17
|
+
|
|
18
|
+
# As a open() file
|
|
19
|
+
with open("file.bin", "wb") as file:
|
|
20
|
+
turbopipe.pipe(buffer, file)
|
|
21
|
+
|
|
22
|
+
# As a subprocess
|
|
23
|
+
child = subprocess.Popen(..., stdin=subprocess.PIPE)
|
|
24
|
+
turbopipe.pipe(buffer, child.stdin.fileno())
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
_turbopipe.pipe(buffer.mglo, file)
|
|
28
|
+
|
|
29
|
+
def sync() -> None:
|
|
30
|
+
"""Waits for all jobs to finish"""
|
|
31
|
+
_turbopipe.sync()
|
|
32
|
+
|
|
33
|
+
def close() -> None:
|
|
34
|
+
"""Syncs and deletes objects"""
|
|
35
|
+
_turbopipe.close()
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"pipe",
|
|
39
|
+
"sync",
|
|
40
|
+
"close"
|
|
41
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// ------------------------------------------------------------------------------------------------|
|
|
2
|
+
//
|
|
3
|
+
// TurboPipe - Faster ModernGL Buffer inter process data transfers
|
|
4
|
+
//
|
|
5
|
+
// (c) 2024, Tremeschin, MIT License
|
|
6
|
+
//
|
|
7
|
+
// ------------------------------------------------------------------------------------------------|
|
|
8
|
+
|
|
9
|
+
#define PY_SSIZE_T_CLEAN
|
|
10
|
+
#include <Python.h>
|
|
11
|
+
|
|
12
|
+
#include <thread>
|
|
13
|
+
#include <mutex>
|
|
14
|
+
#include <condition_variable>
|
|
15
|
+
#include <unistd.h>
|
|
16
|
+
#include <iostream>
|
|
17
|
+
#include <unordered_map>
|
|
18
|
+
#include <deque>
|
|
19
|
+
#include <chrono>
|
|
20
|
+
#include <functional>
|
|
21
|
+
#include <set>
|
|
22
|
+
|
|
23
|
+
#include "gl_methods.hpp"
|
|
24
|
+
|
|
25
|
+
#define dict std::unordered_map
|
|
26
|
+
using namespace std;
|
|
27
|
+
|
|
28
|
+
// ------------------------------------------------------------------------------------------------|
|
|
29
|
+
// ModernGL Types - Courtesy of the moderngl package developers (MIT)
|
|
30
|
+
|
|
31
|
+
static PyTypeObject* MGLBuffer_type = nullptr;
|
|
32
|
+
|
|
33
|
+
struct MGLContext;
|
|
34
|
+
struct MGLFramebuffer;
|
|
35
|
+
|
|
36
|
+
struct MGLBuffer {
|
|
37
|
+
PyObject_HEAD
|
|
38
|
+
MGLContext* context;
|
|
39
|
+
int buffer;
|
|
40
|
+
Py_ssize_t size;
|
|
41
|
+
bool dynamic;
|
|
42
|
+
bool released;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
struct MGLContext {
|
|
46
|
+
PyObject_HEAD
|
|
47
|
+
PyObject * ctx;
|
|
48
|
+
PyObject * extensions;
|
|
49
|
+
MGLFramebuffer * default_framebuffer;
|
|
50
|
+
MGLFramebuffer * bound_framebuffer;
|
|
51
|
+
PyObject * includes;
|
|
52
|
+
int version_code;
|
|
53
|
+
int max_samples;
|
|
54
|
+
int max_integer_samples;
|
|
55
|
+
int max_color_attachments;
|
|
56
|
+
int max_texture_units;
|
|
57
|
+
int default_texture_unit;
|
|
58
|
+
float max_anisotropy;
|
|
59
|
+
int enable_flags;
|
|
60
|
+
int front_face;
|
|
61
|
+
int cull_face;
|
|
62
|
+
int depth_func;
|
|
63
|
+
bool depth_clamp;
|
|
64
|
+
double depth_range[2];
|
|
65
|
+
int blend_func_src;
|
|
66
|
+
int blend_func_dst;
|
|
67
|
+
bool wireframe;
|
|
68
|
+
bool multisample;
|
|
69
|
+
int provoking_vertex;
|
|
70
|
+
float polygon_offset_factor;
|
|
71
|
+
float polygon_offset_units;
|
|
72
|
+
GLMethods gl;
|
|
73
|
+
bool released;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ------------------------------------------------------------------------------------------------|
|
|
77
|
+
// TurboPipe internals
|
|
78
|
+
|
|
79
|
+
struct Work {
|
|
80
|
+
void* map;
|
|
81
|
+
int file;
|
|
82
|
+
size_t size;
|
|
83
|
+
|
|
84
|
+
int hash() {
|
|
85
|
+
return std::hash<int>()(file) ^ std::hash<void*>()(map);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
class TurboPipe {
|
|
90
|
+
public:
|
|
91
|
+
TurboPipe(): running(true) {}
|
|
92
|
+
~TurboPipe() {close();}
|
|
93
|
+
|
|
94
|
+
void pipe(MGLBuffer* buffer, int file) {
|
|
95
|
+
const GLMethods& gl = buffer->context->gl;
|
|
96
|
+
|
|
97
|
+
gl.BindBuffer(GL_ARRAY_BUFFER, buffer->buffer);
|
|
98
|
+
void* data = gl.MapBufferRange(GL_ARRAY_BUFFER, 0, buffer->size, GL_MAP_READ_BIT);
|
|
99
|
+
gl.UnmapBuffer(GL_ARRAY_BUFFER);
|
|
100
|
+
|
|
101
|
+
this->_pipe(data, buffer->size, file);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void sync() {
|
|
105
|
+
// Wait for all queues to be empty, as they are erased when
|
|
106
|
+
// each thread's writing loop is done, guaranteeing finish
|
|
107
|
+
for (auto& values: queue) {
|
|
108
|
+
while (!values.second.empty()) {
|
|
109
|
+
this_thread::sleep_for(chrono::milliseconds(1));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void close() {
|
|
115
|
+
sync();
|
|
116
|
+
running = false;
|
|
117
|
+
signal.notify_all();
|
|
118
|
+
for (auto& pair: threads)
|
|
119
|
+
pair.second.join();
|
|
120
|
+
threads.clear();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private:
|
|
124
|
+
dict<int, dict<int, condition_variable>> pending;
|
|
125
|
+
dict<int, deque<Work>> stream;
|
|
126
|
+
dict<int, set<int>> queue;
|
|
127
|
+
dict<int, thread> threads;
|
|
128
|
+
dict<int, mutex> mutexes;
|
|
129
|
+
condition_variable signal;
|
|
130
|
+
bool running;
|
|
131
|
+
|
|
132
|
+
void _pipe(void* data, size_t size, int file) {
|
|
133
|
+
Work work = {data, file, size};
|
|
134
|
+
int hash = work.hash();
|
|
135
|
+
|
|
136
|
+
unique_lock<mutex> lock(mutexes[file]);
|
|
137
|
+
|
|
138
|
+
// Notify this hash is queued, wait if pending
|
|
139
|
+
if (!queue[file].insert(hash).second) {
|
|
140
|
+
pending[file][hash].wait(lock, [this, file, hash] {
|
|
141
|
+
return queue[file].find(hash) == queue[file].end();
|
|
142
|
+
});
|
|
143
|
+
pending[file].erase(hash);
|
|
144
|
+
queue[file].insert(hash);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Add another job to the queue
|
|
148
|
+
stream[file].push_back(work);
|
|
149
|
+
lock.unlock();
|
|
150
|
+
|
|
151
|
+
// Each file descriptor has its own thread
|
|
152
|
+
if (threads.find(file) == threads.end())
|
|
153
|
+
threads[file] = thread(&TurboPipe::worker, this, file);
|
|
154
|
+
|
|
155
|
+
signal.notify_all();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void worker(int file) {
|
|
159
|
+
while (this->running) {
|
|
160
|
+
unique_lock<mutex> lock(mutexes[file]);
|
|
161
|
+
|
|
162
|
+
signal.wait(lock, [this, file] {
|
|
163
|
+
return (!stream[file].empty() || !this->running);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Skip on false positives, exit condition
|
|
167
|
+
if (stream[file].empty()) continue;
|
|
168
|
+
if (!this->running) break;
|
|
169
|
+
|
|
170
|
+
// Get the next work item
|
|
171
|
+
Work work = stream[file].front();
|
|
172
|
+
stream[file].pop_front();
|
|
173
|
+
lock.unlock();
|
|
174
|
+
|
|
175
|
+
// Optimization: Write in chunks of 4096 (RAM page size)
|
|
176
|
+
size_t tell = 0;
|
|
177
|
+
while (tell < work.size) {
|
|
178
|
+
size_t chunk = min(work.size - tell, static_cast<size_t>(4096));
|
|
179
|
+
ssize_t written = write(work.file, (char*) work.map + tell, chunk);
|
|
180
|
+
if (written == -1) break;
|
|
181
|
+
tell += written;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Signal work is done
|
|
185
|
+
lock.lock();
|
|
186
|
+
int hash = work.hash();
|
|
187
|
+
pending[file][hash].notify_all();
|
|
188
|
+
queue[file].erase(hash);
|
|
189
|
+
signal.notify_all();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// The main and only instance of TurboPipe
|
|
195
|
+
static TurboPipe* turbopipe = nullptr;
|
|
196
|
+
|
|
197
|
+
// ------------------------------------------------------------------------------------------------|
|
|
198
|
+
// End user methods
|
|
199
|
+
|
|
200
|
+
static PyObject* turbopipe_pipe(
|
|
201
|
+
PyObject* Py_UNUSED(self),
|
|
202
|
+
PyObject* args
|
|
203
|
+
) {
|
|
204
|
+
PyObject* buffer;
|
|
205
|
+
PyObject* file;
|
|
206
|
+
if (!PyArg_ParseTuple(args, "OO", &buffer, &file))
|
|
207
|
+
return NULL;
|
|
208
|
+
turbopipe->pipe((MGLBuffer*) buffer, PyLong_AsLong(file));
|
|
209
|
+
Py_RETURN_NONE;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static PyObject* turbopipe_sync(
|
|
213
|
+
PyObject* Py_UNUSED(self),
|
|
214
|
+
PyObject* Py_UNUSED(args)
|
|
215
|
+
) {
|
|
216
|
+
turbopipe->sync();
|
|
217
|
+
Py_RETURN_NONE;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static PyObject* turbopipe_close(
|
|
221
|
+
PyObject* Py_UNUSED(self),
|
|
222
|
+
PyObject* Py_UNUSED(args)
|
|
223
|
+
) {
|
|
224
|
+
turbopipe->close();
|
|
225
|
+
Py_RETURN_NONE;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ------------------------------------------------------------------------------------------------|
|
|
229
|
+
// Python module definition
|
|
230
|
+
|
|
231
|
+
static void turbopipe_exit() {
|
|
232
|
+
turbopipe->close();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static PyMethodDef TurboPipeMethods[] = {
|
|
236
|
+
{"pipe", (PyCFunction) turbopipe_pipe, METH_VARARGS, ""},
|
|
237
|
+
{"sync", (PyCFunction) turbopipe_sync, METH_NOARGS, ""},
|
|
238
|
+
{"close", (PyCFunction) turbopipe_close, METH_NOARGS, ""},
|
|
239
|
+
{NULL, NULL, 0, NULL}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
static struct PyModuleDef turbopipe_module = {
|
|
243
|
+
PyModuleDef_HEAD_INIT,
|
|
244
|
+
"_turbopipe",
|
|
245
|
+
NULL, -1,
|
|
246
|
+
TurboPipeMethods,
|
|
247
|
+
NULL, NULL, NULL, NULL
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
PyMODINIT_FUNC PyInit__turbopipe(void) {
|
|
251
|
+
PyObject* module = PyModule_Create(&turbopipe_module);
|
|
252
|
+
PyObject* moderngl = PyImport_ImportModule("moderngl");
|
|
253
|
+
PyObject* buffer = PyObject_GetAttrString(moderngl, "Buffer");
|
|
254
|
+
MGLBuffer_type = (PyTypeObject*) buffer;
|
|
255
|
+
turbopipe = new TurboPipe();
|
|
256
|
+
Py_AtExit(turbopipe_exit);
|
|
257
|
+
return module;
|
|
258
|
+
}
|