PyPcre 0.3.1__tar.gz → 0.4.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.
- {pypcre-0.3.1 → pypcre-0.4.0}/PKG-INFO +36 -17
- {pypcre-0.3.1 → pypcre-0.4.0}/PyPcre.egg-info/PKG-INFO +36 -17
- {pypcre-0.3.1 → pypcre-0.4.0}/PyPcre.egg-info/SOURCES.txt +5 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/README.md +34 -15
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/pcre.py +8 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/memory.c +34 -9
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/pcre2.c +250 -64
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/util.c +120 -15
- {pypcre-0.3.1 → pypcre-0.4.0}/pyproject.toml +2 -2
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_cache.py +62 -0
- pypcre-0.4.0/tests/test_cache_global.py +136 -0
- pypcre-0.4.0/tests/test_cache_global_inproc.py +163 -0
- pypcre-0.4.0/tests/test_coverage_gaps.py +366 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_errors.py +6 -0
- pypcre-0.4.0/tests/test_re_compat.py +221 -0
- pypcre-0.4.0/tests/test_threads.py +132 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/LICENSE +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/MANIFEST.in +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/PyPcre.egg-info/dependency_links.txt +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/PyPcre.egg-info/top_level.txt +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/__init__.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/_stdlib_re.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/cache.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/error.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/flags.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/re_compat.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre/threads.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/atomic_compat.h +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/cache.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/error.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/flag.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/jit_support.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/pattern_cache.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/pcre2.h +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/pcre2_module.h +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/pcre_ext/string_helpers.c +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/setup.cfg +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/setup.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/setup_utils.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_accuracy.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_api_parity.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_basic.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_bench_string.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_benchmark.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_bytes.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_clobber.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_clobber_thread.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_core.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_flags.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_jit.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_memory.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_module.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_pattern.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_setup_utils_multiarch.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_simd.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_threaded_backend.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_transformers_regex.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_utf8.py +0 -0
- {pypcre-0.3.1 → pypcre-0.4.0}/tests/test_version.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPcre
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Modern, GIL-friendly, Fast Python bindings for PCRE2 with auto caching and JIT of compiled patterns.
|
|
5
5
|
Author-email: ModelCloud <qubitium@modelcloud.ai>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/ModelCloud/pcre
|
|
8
8
|
Keywords: regex,pcre2,bindings
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -47,6 +47,7 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
## Latest News 🚀
|
|
50
|
+
* 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
|
|
50
51
|
* 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
|
|
51
52
|
* 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
|
|
52
53
|
* 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
|
|
@@ -85,26 +86,44 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
|
|
|
85
86
|
|
|
86
87
|
### Benchmark Highlights 🏁
|
|
87
88
|
|
|
88
|
-
Measured on a `Python 3.14.
|
|
89
|
+
Measured on a `Python 3.14.5` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
91
|
+
A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
|
|
92
|
+
|
|
93
|
+
#### `findall` — large multiline and lookaround workloads
|
|
94
|
+
|
|
95
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
96
|
+
| --- | ---: | ---: | ---: | --- |
|
|
97
|
+
| Extract `WARN` / `ERROR` lines (multiline) | `0.60` | `29.35` | `30.85` | **49x** vs `re`, **52x** vs `regex` |
|
|
98
|
+
| Per-line full-name extraction (multiline) | `1.02` | `29.50` | `15.95` | **29x** vs `re`, **16x** vs `regex` |
|
|
99
|
+
| Lookbehind + negative-lookahead tokens | `3.78` | `11.81` | `10.42` | **3.1x** vs `re`, **2.8x** vs `regex` |
|
|
100
|
+
|
|
101
|
+
Patterns used:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# WARN/ERROR lines and full-name extraction
|
|
105
|
+
^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
106
|
+
^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
107
|
+
|
|
108
|
+
# lookbehind + negative lookahead
|
|
109
|
+
(?:(?<=foo)bar|baz)(?!qux)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `finditer` — same workloads
|
|
113
|
+
|
|
114
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
115
|
+
| --- | ---: | ---: | ---: | --- |
|
|
116
|
+
| Extract `WARN` / `ERROR` lines | `0.60` | `29.33` | `31.23` | **49x** vs `re`, **52x** vs `regex` |
|
|
117
|
+
| Per-line full-name extraction | `1.02` | `30.09` | `16.25` | **29x** vs `re`, **16x** vs `regex` |
|
|
98
118
|
|
|
99
119
|
### Free-Threaded Benchmark Highlights 🧵
|
|
100
120
|
|
|
101
|
-
Measured in the same environment with `8` threads
|
|
121
|
+
Measured in the same environment with `8` threads fanning out over independent copies of each workload. Times are the best of several runs; lower is better.
|
|
102
122
|
|
|
103
|
-
| Workload |
|
|
104
|
-
| --- | ---: | ---: | ---: |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
| Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
|
|
123
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
124
|
+
| --- | ---: | ---: | ---: | --- |
|
|
125
|
+
| Extract `WARN` / `ERROR` lines (`findall`) | `3.26` | `32.57` | `34.41` | **10.0x** vs `re`, **10.6x** vs `regex` |
|
|
126
|
+
| Per-line full-name extraction (`findall`) | `3.18` | `31.84` | `17.31` | **10.0x** vs `re`, **5.4x** vs `regex` |
|
|
108
127
|
|
|
109
128
|
PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
|
|
110
129
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPcre
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Modern, GIL-friendly, Fast Python bindings for PCRE2 with auto caching and JIT of compiled patterns.
|
|
5
5
|
Author-email: ModelCloud <qubitium@modelcloud.ai>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/ModelCloud/pcre
|
|
8
8
|
Keywords: regex,pcre2,bindings
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -47,6 +47,7 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
## Latest News 🚀
|
|
50
|
+
* 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
|
|
50
51
|
* 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
|
|
51
52
|
* 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
|
|
52
53
|
* 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
|
|
@@ -85,26 +86,44 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
|
|
|
85
86
|
|
|
86
87
|
### Benchmark Highlights 🏁
|
|
87
88
|
|
|
88
|
-
Measured on a `Python 3.14.
|
|
89
|
+
Measured on a `Python 3.14.5` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
91
|
+
A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
|
|
92
|
+
|
|
93
|
+
#### `findall` — large multiline and lookaround workloads
|
|
94
|
+
|
|
95
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
96
|
+
| --- | ---: | ---: | ---: | --- |
|
|
97
|
+
| Extract `WARN` / `ERROR` lines (multiline) | `0.60` | `29.35` | `30.85` | **49x** vs `re`, **52x** vs `regex` |
|
|
98
|
+
| Per-line full-name extraction (multiline) | `1.02` | `29.50` | `15.95` | **29x** vs `re`, **16x** vs `regex` |
|
|
99
|
+
| Lookbehind + negative-lookahead tokens | `3.78` | `11.81` | `10.42` | **3.1x** vs `re`, **2.8x** vs `regex` |
|
|
100
|
+
|
|
101
|
+
Patterns used:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# WARN/ERROR lines and full-name extraction
|
|
105
|
+
^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
106
|
+
^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
107
|
+
|
|
108
|
+
# lookbehind + negative lookahead
|
|
109
|
+
(?:(?<=foo)bar|baz)(?!qux)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `finditer` — same workloads
|
|
113
|
+
|
|
114
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
115
|
+
| --- | ---: | ---: | ---: | --- |
|
|
116
|
+
| Extract `WARN` / `ERROR` lines | `0.60` | `29.33` | `31.23` | **49x** vs `re`, **52x** vs `regex` |
|
|
117
|
+
| Per-line full-name extraction | `1.02` | `30.09` | `16.25` | **29x** vs `re`, **16x** vs `regex` |
|
|
98
118
|
|
|
99
119
|
### Free-Threaded Benchmark Highlights 🧵
|
|
100
120
|
|
|
101
|
-
Measured in the same environment with `8` threads
|
|
121
|
+
Measured in the same environment with `8` threads fanning out over independent copies of each workload. Times are the best of several runs; lower is better.
|
|
102
122
|
|
|
103
|
-
| Workload |
|
|
104
|
-
| --- | ---: | ---: | ---: |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
| Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
|
|
123
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
124
|
+
| --- | ---: | ---: | ---: | --- |
|
|
125
|
+
| Extract `WARN` / `ERROR` lines (`findall`) | `3.26` | `32.57` | `34.41` | **10.0x** vs `re`, **10.6x** vs `regex` |
|
|
126
|
+
| Per-line full-name extraction (`findall`) | `3.18` | `31.84` | `17.31` | **10.0x** vs `re`, **5.4x** vs `regex` |
|
|
108
127
|
|
|
109
128
|
PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
|
|
110
129
|
|
|
@@ -35,18 +35,23 @@ tests/test_bench_string.py
|
|
|
35
35
|
tests/test_benchmark.py
|
|
36
36
|
tests/test_bytes.py
|
|
37
37
|
tests/test_cache.py
|
|
38
|
+
tests/test_cache_global.py
|
|
39
|
+
tests/test_cache_global_inproc.py
|
|
38
40
|
tests/test_clobber.py
|
|
39
41
|
tests/test_clobber_thread.py
|
|
40
42
|
tests/test_core.py
|
|
43
|
+
tests/test_coverage_gaps.py
|
|
41
44
|
tests/test_errors.py
|
|
42
45
|
tests/test_flags.py
|
|
43
46
|
tests/test_jit.py
|
|
44
47
|
tests/test_memory.py
|
|
45
48
|
tests/test_module.py
|
|
46
49
|
tests/test_pattern.py
|
|
50
|
+
tests/test_re_compat.py
|
|
47
51
|
tests/test_setup_utils_multiarch.py
|
|
48
52
|
tests/test_simd.py
|
|
49
53
|
tests/test_threaded_backend.py
|
|
54
|
+
tests/test_threads.py
|
|
50
55
|
tests/test_transformers_regex.py
|
|
51
56
|
tests/test_utf8.py
|
|
52
57
|
tests/test_version.py
|
|
@@ -24,6 +24,7 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
## Latest News 🚀
|
|
27
|
+
* 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
|
|
27
28
|
* 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
|
|
28
29
|
* 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
|
|
29
30
|
* 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
|
|
@@ -62,26 +63,44 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
|
|
|
62
63
|
|
|
63
64
|
### Benchmark Highlights 🏁
|
|
64
65
|
|
|
65
|
-
Measured on a `Python 3.14.
|
|
66
|
+
Measured on a `Python 3.14.5` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
68
|
+
A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
|
|
69
|
+
|
|
70
|
+
#### `findall` — large multiline and lookaround workloads
|
|
71
|
+
|
|
72
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
73
|
+
| --- | ---: | ---: | ---: | --- |
|
|
74
|
+
| Extract `WARN` / `ERROR` lines (multiline) | `0.60` | `29.35` | `30.85` | **49x** vs `re`, **52x** vs `regex` |
|
|
75
|
+
| Per-line full-name extraction (multiline) | `1.02` | `29.50` | `15.95` | **29x** vs `re`, **16x** vs `regex` |
|
|
76
|
+
| Lookbehind + negative-lookahead tokens | `3.78` | `11.81` | `10.42` | **3.1x** vs `re`, **2.8x** vs `regex` |
|
|
77
|
+
|
|
78
|
+
Patterns used:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# WARN/ERROR lines and full-name extraction
|
|
82
|
+
^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
83
|
+
^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
|
|
84
|
+
|
|
85
|
+
# lookbehind + negative lookahead
|
|
86
|
+
(?:(?<=foo)bar|baz)(?!qux)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `finditer` — same workloads
|
|
90
|
+
|
|
91
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
92
|
+
| --- | ---: | ---: | ---: | --- |
|
|
93
|
+
| Extract `WARN` / `ERROR` lines | `0.60` | `29.33` | `31.23` | **49x** vs `re`, **52x** vs `regex` |
|
|
94
|
+
| Per-line full-name extraction | `1.02` | `30.09` | `16.25` | **29x** vs `re`, **16x** vs `regex` |
|
|
75
95
|
|
|
76
96
|
### Free-Threaded Benchmark Highlights 🧵
|
|
77
97
|
|
|
78
|
-
Measured in the same environment with `8` threads
|
|
98
|
+
Measured in the same environment with `8` threads fanning out over independent copies of each workload. Times are the best of several runs; lower is better.
|
|
79
99
|
|
|
80
|
-
| Workload |
|
|
81
|
-
| --- | ---: | ---: | ---: |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
| Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
|
|
100
|
+
| Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
|
|
101
|
+
| --- | ---: | ---: | ---: | --- |
|
|
102
|
+
| Extract `WARN` / `ERROR` lines (`findall`) | `3.26` | `32.57` | `34.41` | **10.0x** vs `re`, **10.6x** vs `regex` |
|
|
103
|
+
| Per-line full-name extraction (`findall`) | `3.18` | `31.84` | `17.31` | **10.0x** vs `re`, **5.4x** vs `regex` |
|
|
85
104
|
|
|
86
105
|
PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
|
|
87
106
|
|
|
@@ -386,6 +386,14 @@ class Pattern:
|
|
|
386
386
|
) -> List[Any]:
|
|
387
387
|
if type(subject) is memoryview:
|
|
388
388
|
subject = subject.tobytes()
|
|
389
|
+
backend_findall = getattr(self._pattern, "findall", None)
|
|
390
|
+
if backend_findall is not None:
|
|
391
|
+
compiled_end = -1 if endpos is None else resolve_endpos(subject, endpos)
|
|
392
|
+
try:
|
|
393
|
+
return backend_findall(subject, pos=pos, endpos=compiled_end, options=options)
|
|
394
|
+
except TypeError:
|
|
395
|
+
pass
|
|
396
|
+
|
|
389
397
|
backend_iter = getattr(self._pattern, "finditer", None)
|
|
390
398
|
if backend_iter is not None:
|
|
391
399
|
compiled_end = -1 if endpos is None else resolve_endpos(subject, endpos)
|
|
@@ -25,7 +25,8 @@ static void *current_handle = NULL;
|
|
|
25
25
|
static alloc_fn current_alloc = (alloc_fn)PyMem_Malloc;
|
|
26
26
|
static free_fn current_free = (free_fn)PyMem_Free;
|
|
27
27
|
static const char *current_name = "pymem";
|
|
28
|
-
static int allocator_initialized = 0;
|
|
28
|
+
static ATOMIC_VAR(int) allocator_initialized = ATOMIC_VAR_INIT(0);
|
|
29
|
+
static atomic_flag allocator_init_lock = ATOMIC_FLAG_INIT;
|
|
29
30
|
|
|
30
31
|
#if defined(_WIN32)
|
|
31
32
|
static int
|
|
@@ -95,7 +96,19 @@ equals_ignore_case(const char *value, const char *target)
|
|
|
95
96
|
int
|
|
96
97
|
pcre_memory_initialize(void)
|
|
97
98
|
{
|
|
98
|
-
if (allocator_initialized) {
|
|
99
|
+
if (atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
while (atomic_flag_test_and_set_explicit(&allocator_init_lock, memory_order_acq_rel)) {
|
|
104
|
+
if (atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
105
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
111
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
99
112
|
return 0;
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -131,7 +144,8 @@ pcre_memory_initialize(void)
|
|
|
131
144
|
current_alloc = (alloc_fn)PyMem_Malloc;
|
|
132
145
|
current_free = (free_fn)PyMem_Free;
|
|
133
146
|
current_name = "pymem";
|
|
134
|
-
allocator_initialized
|
|
147
|
+
atomic_store_explicit(&allocator_initialized, 1, memory_order_release);
|
|
148
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
135
149
|
return 0;
|
|
136
150
|
}
|
|
137
151
|
|
|
@@ -140,18 +154,21 @@ pcre_memory_initialize(void)
|
|
|
140
154
|
current_alloc = malloc;
|
|
141
155
|
current_free = free;
|
|
142
156
|
current_name = "malloc";
|
|
143
|
-
allocator_initialized
|
|
157
|
+
atomic_store_explicit(&allocator_initialized, 1, memory_order_release);
|
|
158
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
144
159
|
return 0;
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
if (equals_ignore_case(forced, "jemalloc")) {
|
|
148
163
|
if (load_allocator(&jemalloc_candidate) == 0) {
|
|
149
|
-
allocator_initialized
|
|
164
|
+
atomic_store_explicit(&allocator_initialized, 1, memory_order_release);
|
|
165
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
150
166
|
return 0;
|
|
151
167
|
}
|
|
152
168
|
} else if (equals_ignore_case(forced, "tcmalloc")) {
|
|
153
169
|
if (load_allocator(&tcmalloc_candidate) == 0) {
|
|
154
|
-
allocator_initialized
|
|
170
|
+
atomic_store_explicit(&allocator_initialized, 1, memory_order_release);
|
|
171
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
155
172
|
return 0;
|
|
156
173
|
}
|
|
157
174
|
}
|
|
@@ -160,7 +177,8 @@ pcre_memory_initialize(void)
|
|
|
160
177
|
current_alloc = (alloc_fn)PyMem_Malloc;
|
|
161
178
|
current_free = (free_fn)PyMem_Free;
|
|
162
179
|
current_name = "pymem";
|
|
163
|
-
allocator_initialized
|
|
180
|
+
atomic_store_explicit(&allocator_initialized, 1, memory_order_release);
|
|
181
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
164
182
|
return 0;
|
|
165
183
|
}
|
|
166
184
|
|
|
@@ -177,13 +195,14 @@ pcre_memory_teardown(void)
|
|
|
177
195
|
current_alloc = (alloc_fn)PyMem_Malloc;
|
|
178
196
|
current_free = (free_fn)PyMem_Free;
|
|
179
197
|
current_name = "pymem";
|
|
180
|
-
allocator_initialized
|
|
198
|
+
atomic_store_explicit(&allocator_initialized, 0, memory_order_release);
|
|
199
|
+
atomic_flag_clear_explicit(&allocator_init_lock, memory_order_release);
|
|
181
200
|
}
|
|
182
201
|
|
|
183
202
|
void *
|
|
184
203
|
pcre_malloc(size_t size)
|
|
185
204
|
{
|
|
186
|
-
if (!allocator_initialized) {
|
|
205
|
+
if (!atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
187
206
|
if (pcre_memory_initialize() != 0) {
|
|
188
207
|
return NULL;
|
|
189
208
|
}
|
|
@@ -197,11 +216,17 @@ pcre_free(void *ptr)
|
|
|
197
216
|
if (ptr == NULL) {
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
219
|
+
if (!atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
220
|
+
(void)pcre_memory_initialize();
|
|
221
|
+
}
|
|
200
222
|
current_free(ptr);
|
|
201
223
|
}
|
|
202
224
|
|
|
203
225
|
const char *
|
|
204
226
|
pcre_memory_allocator_name(void)
|
|
205
227
|
{
|
|
228
|
+
if (!atomic_load_explicit(&allocator_initialized, memory_order_acquire)) {
|
|
229
|
+
(void)pcre_memory_initialize();
|
|
230
|
+
}
|
|
206
231
|
return current_name;
|
|
207
232
|
}
|