Skip to content

Commit bde44fd

Browse files
authored
Merge pull request #442 from pythonspeed/381-python-311-support
Python 3.11 support
2 parents f111c33 + eda0a0c commit bde44fd

File tree

8 files changed

+100
-58
lines changed

8 files changed

+100
-58
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
name: "${{ matrix.os }}: Python ${{ matrix.python-version }}"
2424
strategy:
2525
matrix:
26-
python-version: ["3.7", "3.8", "3.9", "3.10"]
27-
os: ["ubuntu-20.04", "macos-10.15"]
26+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
27+
os: ["ubuntu-20.04", "macos-latest"]
2828

2929
runs-on: "${{ matrix.os }}"
3030

@@ -76,11 +76,11 @@ jobs:
7676
make test
7777
- name: "Build macOS wheels"
7878
if: startsWith(matrix.os, 'mac') && (matrix.python-version == '3.9')
79-
uses: pypa/cibuildwheel@v2.9.0
79+
uses: pypa/cibuildwheel@v2.11.2
8080
env:
8181
MACOSX_DEPLOYMENT_TARGET: "10.15"
8282
CIBW_ARCHS_MACOS: "x86_64 arm64"
83-
CIBW_SKIP: "cp37-macosx_arm64 cp36* pp* cp311*"
83+
CIBW_SKIP: "cp37-macosx_arm64 cp36* pp*"
8484
CIBW_BEFORE_BUILD: "touch filpreload/src/_filpreload.c" # force rebuild of Python code with new interpreter
8585
CIBW_TEST_COMMAND: python -m filprofiler run {project}/benchmarks/pystone.py
8686
with:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release notes
22

3+
## 2022.11.0 (2022-11-07)
4+
5+
### Features
6+
7+
- Added initial Python 3.11 support; unfortunately this increased performance overhead a little. ([#381](https://github.com/pythonspeed/filprofiler/issues/381))
8+
39
## 2022.10.0 (2022-10-19)
410

511
### Bugfixes

Makefile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,13 @@ test-python-no-deps:
4040
cd tests/test-scripts && python -m numpy.f2py -c fortran.f90 -m fortran
4141
env RUST_BACKTRACE=1 py.test -v tests/
4242

43-
.PHONY: docker-image
44-
docker-image:
45-
docker build -t manylinux-rust -f wheels/Dockerfile.build .
46-
4743
.PHONY: wheel
4844
wheel:
4945
python setup.py bdist_wheel
5046

5147
.PHONY: manylinux-wheel
5248
manylinux-wheel:
53-
docker run -u $(shell id -u):$(shell id -g) -v $(PWD):/src quay.io/pypa/manylinux2010_x86_64:latest /src/wheels/build-wheels.sh
49+
docker run -u $(shell id -u):$(shell id -g) -v $(PWD):/src quay.io/pypa/manylinux2014_x86_64:latest /src/wheels/build-wheels.sh
5450

5551
.PHONY: clean
5652
clean:

filpreload/src/_filpreload.c

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
#include "Python.h"
2+
#include "ceval.h"
3+
#if PY_MINOR_VERSION < 11
24
#include "code.h"
5+
#else
6+
#include "internal/pycore_code.h"
7+
#include "internal/pycore_frame.h"
8+
#endif
9+
#include "frameobject.h"
310
#include "object.h"
11+
412
#ifndef _GNU_SOURCE
513
#define _GNU_SOURCE
614
#endif
7-
#include "frameobject.h"
815
#include <dlfcn.h>
916
#include <pthread.h>
1017
#include <stdatomic.h>
@@ -16,6 +23,20 @@
1623
#include <stdbool.h>
1724
#include <errno.h>
1825

26+
#if PY_MINOR_VERSION < 9
27+
PyFrameObject *PyFrame_GetBack(PyFrameObject *frame) {
28+
if (frame->f_back != NULL) {
29+
Py_INCREF(frame->f_back);
30+
}
31+
return frame->f_back;
32+
}
33+
34+
PyCodeObject *PyFrame_GetCode(PyFrameObject *frame) {
35+
Py_INCREF(frame->f_code);
36+
return frame->f_code;
37+
}
38+
#endif
39+
1940
// Macro to create the publicly exposed symbol:
2041
#ifdef __APPLE__
2142
#define SYMBOL_PREFIX(func) reimplemented_##func
@@ -33,9 +54,9 @@
3354
#define likely(x) __builtin_expect(!!(x), 1)
3455
#define unlikely(x) __builtin_expect(!!(x), 0)
3556

36-
// Underlying APIs we're wrapping:
37-
static void *(*underlying_real_mmap)(void *addr, size_t length, int prot,
38-
int flags, int fd, off_t offset) = 0;
57+
// Underlying APIs we're wrapping:
58+
static void *(*underlying_real_mmap)(void *addr, size_t length, int prot,
59+
int flags, int fd, off_t offset) = 0;
3960
static int (*underlying_real_pthread_create)(pthread_t *thread,
4061
const pthread_attr_t *attr,
4162
void *(*start_routine)(void *),
@@ -118,8 +139,22 @@ static inline int should_track_memory() {
118139
return (likely(initialized) && atomic_load_explicit(&tracking_allocations, memory_order_acquire) && !am_i_reentrant());
119140
}
120141

121-
// Current thread's Python state:
122-
static _Thread_local PyFrameObject *current_frame = NULL;
142+
// Current thread's Python state; typically only set in C functions where GIL
143+
// might be released.
144+
static _Thread_local int current_line_number = -1;
145+
146+
static inline int get_current_line_number() {
147+
if (PyGILState_Check()) {
148+
PyFrameObject *frame = PyEval_GetFrame();
149+
if (frame != NULL) {
150+
return PyFrame_GetLineNumber(frame);
151+
}
152+
}
153+
if (current_line_number != -1) {
154+
return current_line_number;
155+
}
156+
return 0;
157+
}
123158

124159
// The file and function name responsible for an allocation.
125160
struct FunctionLocation {
@@ -200,13 +235,16 @@ static void __attribute__((constructor)) constructor() {
200235
initialized = 1;
201236
}
202237

203-
static void start_call(uint64_t function_id, uint16_t line_number) {
238+
static void start_call(uint64_t function_id, uint16_t line_number, PyFrameObject* current_frame) {
204239
if (should_track_memory()) {
205240
increment_reentrancy();
206241
uint16_t parent_line_number = 0;
207-
if (current_frame != NULL && current_frame->f_back != NULL) {
208-
PyFrameObject *f = current_frame->f_back;
209-
parent_line_number = PyFrame_GetLineNumber(f);
242+
if (current_frame != NULL) {
243+
PyFrameObject *parent = PyFrame_GetBack(current_frame);
244+
if (parent != NULL ){
245+
parent_line_number = PyFrame_GetLineNumber(parent);
246+
Py_DECREF(parent);
247+
}
210248
}
211249
pymemprofile_start_call(parent_line_number, function_id, line_number);
212250
decrement_reentrancy();
@@ -226,39 +264,53 @@ __attribute__((visibility("hidden"))) int
226264
fil_tracer(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg) {
227265
switch (what) {
228266
case PyTrace_CALL:
229-
// Store the current frame, so malloc() can look up line number:
230-
current_frame = frame;
231-
232267
/*
233268
We want an efficient identifier for filename+fuction name. So we register
234269
the function + filename with some Rust code that gives back its ID, and
235270
then store the ID. Due to bad API design, value 0 indicates "no result",
236271
so we actually store the result + 1.
237272
*/
273+
current_line_number = frame->f_lineno;
238274
uint64_t function_id = 0;
239275
assert(extra_code_index != -1);
240-
_PyCode_GetExtra((PyObject *)frame->f_code, extra_code_index,
241-
(void **)&function_id);
276+
PyCodeObject *code = PyFrame_GetCode(frame);
277+
_PyCode_GetExtra((PyObject *)code, extra_code_index, (void **)&function_id);
242278
if (function_id == 0) {
243279
Py_ssize_t filename_length, function_length;
244-
const char* filename = PyUnicode_AsUTF8AndSize(frame->f_code->co_filename,
280+
const char* filename = PyUnicode_AsUTF8AndSize(code->co_filename,
245281
&filename_length);
246-
const char* function_name = PyUnicode_AsUTF8AndSize(frame->f_code->co_name,
282+
const char* function_name = PyUnicode_AsUTF8AndSize(code->co_name,
247283
&function_length);
248284
increment_reentrancy();
249285
function_id = pymemprofile_add_function_location(filename, (uint64_t)filename_length, function_name, (uint64_t)function_length);
250286
decrement_reentrancy();
251-
_PyCode_SetExtra((PyObject *)frame->f_code, extra_code_index,
287+
_PyCode_SetExtra((PyObject *)code, extra_code_index,
252288
(void *)function_id + 1);
289+
Py_DECREF(code);
253290
} else {
254291
function_id -= 1;
255292
}
256-
start_call(function_id, frame->f_lineno);
293+
start_call(function_id, current_line_number, frame);
257294
break;
258295
case PyTrace_RETURN:
259296
finish_call();
260-
// We're done with this frame, so set the parent frame:
261-
current_frame = frame->f_back;
297+
if (frame != NULL) {
298+
PyFrameObject* parent = PyFrame_GetBack(frame);
299+
if (parent == NULL) {
300+
current_line_number = -1;
301+
} else {
302+
current_line_number = PyFrame_GetLineNumber(parent);
303+
Py_DECREF(parent);
304+
}
305+
}
306+
break;
307+
case PyTrace_C_CALL:
308+
// C calls might release GIL, in which case they won't change the line
309+
// number, so record it.
310+
current_line_number = PyFrame_GetLineNumber(frame);
311+
break;
312+
case PyTrace_C_RETURN:
313+
current_line_number = -1;
262314
break;
263315
default:
264316
break;
@@ -315,20 +367,12 @@ fil_dump_peak_to_flamegraph(const char *path) {
315367

316368
// *** End APIs called by Python ***
317369
static void add_allocation(size_t address, size_t size) {
318-
uint16_t line_number = 0;
319-
PyFrameObject *f = current_frame;
320-
if (f != NULL) {
321-
line_number = PyFrame_GetLineNumber(f);
322-
}
370+
uint16_t line_number = get_current_line_number();
323371
pymemprofile_add_allocation(address, size, line_number);
324372
}
325373

326374
static void add_anon_mmap(size_t address, size_t size) {
327-
uint16_t line_number = 0;
328-
PyFrameObject *f = current_frame;
329-
if (f != NULL) {
330-
line_number = PyFrame_GetLineNumber(f);
331-
}
375+
uint16_t line_number = get_current_line_number();
332376
pymemprofile_add_anon_mmap(address, size, line_number);
333377
}
334378

filprofiler/_report.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,23 +76,26 @@ def render_report(output_path: str, now: datetime) -> str:
7676
7777
<h2>Profiling result</h2>
7878
<div style="text-align: center;"><p><input type="button" onclick="fullScreen('#peak');" value="Full screen"> · <a href="peak-memory.svg" target="_blank"><button>Open in new window</button></a></p>
79-
<iframe id="peak" src="peak-memory.svg" width="100%" height="400" scrolling="auto" frameborder="0"></iframe><br>
79+
<iframe id="peak" src="peak-memory.svg" width="100%" height="400" scrolling="auto" frameborder="0"></iframe>
8080
</div>
81+
<br>
8182
<blockquote class="center">
8283
<p style="text-align: center;"><em>Check out my other project:</em></p>
8384
<h3>Find memory and performance bottlenecks in production!</h3>
8485
<p>When your data pipeline is too slow in production, reproducing the problem
8586
on your laptop is hard or impossible—which means identifying and fixing the problem can be tricky.</p>
8687
<p>What if you knew the cause of the problem as soon as you realized it was happening?</p>
87-
<p>That's why you need
88-
<strong><a href="https://sciagraph.com/">the Sciagraph profiler</a></strong>, designed to <strong>find performance
88+
<p>That's how
89+
<strong><a href="https://sciagraph.com/">the Sciagraph profiler</a></strong> can help you:
90+
it's designed to <strong>find performance
8991
and memory bottlenecks by continuously profiling in production.</strong></p></blockquote>
9092
<br>
9193
<br>
9294
<div style="text-align: center;"><p><input type="button" onclick="fullScreen('#peak-reversed');" value="Full screen"> ·
9395
<a href="peak-memory-reversed.svg" target="_blank"><button>Open in new window</button></a></p>
9496
<iframe id="peak-reversed" src="peak-memory-reversed.svg" width="100%" height="400" scrolling="auto" frameborder="0"></iframe><br>
9597
</div>
98+
9699
<div class="center">
97100
<blockquote><strong>Need help, or does something look wrong?</strong>
98101
<a href="https://pythonspeed.com/fil/docs/">Read the documentation</a>,

memapi/src/memorytracking.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ fn runpy_prefix_length(calls: std::slice::Iter<(CallSiteId, (&str, &str))>) -> u
236236
let mut length = 0;
237237
let runpy_path = get_runpy_path();
238238
for (_, (_, filename)) in calls {
239-
if *filename == runpy_path {
239+
// On Python 3.11 it uses <frozen runpy> for some reason.
240+
if *filename == runpy_path || *filename == "<frozen runpy>" {
240241
length += 1;
241242
} else {
242243
return length;

wheels/Dockerfile.build

Lines changed: 0 additions & 9 deletions
This file was deleted.

wheels/build-wheels.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ rm -f filprofiler/_filpreload*.so
1717
rm -f filprofiler/_filpreload*.dylib
1818
rm -rf build
1919

20-
for PYBIN in /opt/python/cp{37,38,39,310}*/bin; do
20+
for PYBIN in /opt/python/cp{37,38,39,310,311}*/bin; do
2121
touch filpreload/src/_filpreload.c # force rebuild of Python code with new interpreter
2222
export PYO3_PYTHON="$PYBIN/python"
2323
"${PYBIN}/pip" install -U setuptools wheel setuptools-rust pip
2424
"${PYBIN}/python" -m pip wheel -w /tmp/wheel .
2525
done
2626

27-
auditwheel repair --plat manylinux2010_x86_64 -w dist/ /tmp/wheel/filprofiler*cp37*whl
28-
auditwheel repair --plat manylinux2010_x86_64 -w dist/ /tmp/wheel/filprofiler*cp38*whl
29-
auditwheel repair --plat manylinux2010_x86_64 -w dist/ /tmp/wheel/filprofiler*cp39*whl
30-
auditwheel repair --plat manylinux2010_x86_64 -w dist/ /tmp/wheel/filprofiler*cp310*whl
27+
auditwheel repair --plat manylinux2014_x86_64 -w dist/ /tmp/wheel/filprofiler*cp37*whl
28+
auditwheel repair --plat manylinux2014_x86_64 -w dist/ /tmp/wheel/filprofiler*cp38*whl
29+
auditwheel repair --plat manylinux2014_x86_64 -w dist/ /tmp/wheel/filprofiler*cp39*whl
30+
auditwheel repair --plat manylinux2014_x86_64 -w dist/ /tmp/wheel/filprofiler*cp310*whl
31+
auditwheel repair --plat manylinux2014_x86_64 -w dist/ /tmp/wheel/filprofiler*cp311*whl
3132

0 commit comments

Comments
 (0)