mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00

PyMutex is a one byte lock with fast, inlineable lock and unlock functions for the common uncontended case. The design is based on WebKit's WTF::Lock. PyMutex is built using the _PyParkingLot APIs, which provides a cross-platform futex-like API (based on WebKit's WTF::ParkingLot). This internal API will be used for building other synchronization primitives used to implement PEP 703, such as one-time initialization and events. This also includes tests and a mini benchmark in Tools/lockbench/lockbench.py to compare with the existing PyThread_type_lock. Uncontended acquisition + release: * Linux (x86-64): PyMutex: 11 ns, PyThread_type_lock: 44 ns * macOS (arm64): PyMutex: 13 ns, PyThread_type_lock: 18 ns * Windows (x86-64): PyMutex: 13 ns, PyThread_type_lock: 38 ns PR Overview: The primary purpose of this PR is to implement PyMutex, but there are a number of support pieces (described below). * PyMutex: A 1-byte lock that doesn't require memory allocation to initialize and is generally faster than the existing PyThread_type_lock. The API is internal only for now. * _PyParking_Lot: A futex-like API based on the API of the same name in WebKit. Used to implement PyMutex. * _PyRawMutex: A word sized lock used to implement _PyParking_Lot. * PyEvent: A one time event. This was used a bunch in the "nogil" fork and is useful for testing the PyMutex implementation, so I've included it as part of the PR. * pycore_llist.h: Defines common operations on doubly-linked list. Not strictly necessary (could do the list operations manually), but they come up frequently in the "nogil" fork. ( Similar to https://man.freebsd.org/cgi/man.cgi?queue) --------- Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
179 lines
5.7 KiB
C
179 lines
5.7 KiB
C
/*
|
|
* C Extension module to smoke test pyatomic.h API.
|
|
*
|
|
* This only tests basic functionality, not any synchronizing ordering.
|
|
*/
|
|
|
|
/* Always enable assertions */
|
|
#undef NDEBUG
|
|
|
|
#include "Python.h"
|
|
#include "parts.h"
|
|
|
|
// We define atomic bitwise operations on these types
|
|
#define FOR_BITWISE_TYPES(V) \
|
|
V(uint8, uint8_t) \
|
|
V(uint16, uint16_t) \
|
|
V(uint32, uint32_t) \
|
|
V(uint64, uint64_t) \
|
|
V(uintptr, uintptr_t)
|
|
|
|
// We define atomic addition on these types
|
|
#define FOR_ARITHMETIC_TYPES(V) \
|
|
FOR_BITWISE_TYPES(V) \
|
|
V(int, int) \
|
|
V(uint, unsigned int) \
|
|
V(int8, int8_t) \
|
|
V(int16, int16_t) \
|
|
V(int32, int32_t) \
|
|
V(int64, int64_t) \
|
|
V(intptr, intptr_t) \
|
|
V(ssize, Py_ssize_t)
|
|
|
|
// We define atomic load, store, exchange, and compare_exchange on these types
|
|
#define FOR_ALL_TYPES(V) \
|
|
FOR_ARITHMETIC_TYPES(V) \
|
|
V(ptr, void*)
|
|
|
|
#define IMPL_TEST_ADD(suffix, dtype) \
|
|
static PyObject * \
|
|
test_atomic_add_##suffix(PyObject *self, PyObject *obj) { \
|
|
dtype x = 0; \
|
|
assert(_Py_atomic_add_##suffix(&x, 1) == 0); \
|
|
assert(x == 1); \
|
|
assert(_Py_atomic_add_##suffix(&x, 2) == 1); \
|
|
assert(x == 3); \
|
|
assert(_Py_atomic_add_##suffix(&x, -2) == 3); \
|
|
assert(x == 1); \
|
|
assert(_Py_atomic_add_##suffix(&x, -1) == 1); \
|
|
assert(x == 0); \
|
|
assert(_Py_atomic_add_##suffix(&x, -1) == 0); \
|
|
assert(x == (dtype)-1); \
|
|
assert(_Py_atomic_add_##suffix(&x, -2) == (dtype)-1); \
|
|
assert(x == (dtype)-3); \
|
|
assert(_Py_atomic_add_##suffix(&x, 2) == (dtype)-3); \
|
|
assert(x == (dtype)-1); \
|
|
Py_RETURN_NONE; \
|
|
}
|
|
FOR_ARITHMETIC_TYPES(IMPL_TEST_ADD)
|
|
|
|
#define IMPL_TEST_COMPARE_EXCHANGE(suffix, dtype) \
|
|
static PyObject * \
|
|
test_atomic_compare_exchange_##suffix(PyObject *self, PyObject *obj) { \
|
|
dtype x = (dtype)0; \
|
|
dtype y = (dtype)1; \
|
|
dtype z = (dtype)2; \
|
|
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 0); \
|
|
assert(x == 0); \
|
|
assert(y == 0); \
|
|
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 1); \
|
|
assert(x == z); \
|
|
assert(y == 0); \
|
|
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 0); \
|
|
assert(x == z); \
|
|
assert(y == z); \
|
|
Py_RETURN_NONE; \
|
|
}
|
|
FOR_ALL_TYPES(IMPL_TEST_COMPARE_EXCHANGE)
|
|
|
|
#define IMPL_TEST_EXCHANGE(suffix, dtype) \
|
|
static PyObject * \
|
|
test_atomic_exchange_##suffix(PyObject *self, PyObject *obj) { \
|
|
dtype x = (dtype)0; \
|
|
dtype y = (dtype)1; \
|
|
dtype z = (dtype)2; \
|
|
assert(_Py_atomic_exchange_##suffix(&x, y) == (dtype)0); \
|
|
assert(x == (dtype)1); \
|
|
assert(_Py_atomic_exchange_##suffix(&x, z) == (dtype)1); \
|
|
assert(x == (dtype)2); \
|
|
assert(_Py_atomic_exchange_##suffix(&x, y) == (dtype)2); \
|
|
assert(x == (dtype)1); \
|
|
Py_RETURN_NONE; \
|
|
}
|
|
FOR_ALL_TYPES(IMPL_TEST_EXCHANGE)
|
|
|
|
#define IMPL_TEST_LOAD_STORE(suffix, dtype) \
|
|
static PyObject * \
|
|
test_atomic_load_store_##suffix(PyObject *self, PyObject *obj) { \
|
|
dtype x = (dtype)0; \
|
|
dtype y = (dtype)1; \
|
|
dtype z = (dtype)2; \
|
|
assert(_Py_atomic_load_##suffix(&x) == (dtype)0); \
|
|
assert(x == (dtype)0); \
|
|
_Py_atomic_store_##suffix(&x, y); \
|
|
assert(_Py_atomic_load_##suffix(&x) == (dtype)1); \
|
|
assert(x == (dtype)1); \
|
|
_Py_atomic_store_##suffix##_relaxed(&x, z); \
|
|
assert(_Py_atomic_load_##suffix##_relaxed(&x) == (dtype)2); \
|
|
assert(x == (dtype)2); \
|
|
Py_RETURN_NONE; \
|
|
}
|
|
FOR_ALL_TYPES(IMPL_TEST_LOAD_STORE)
|
|
|
|
#define IMPL_TEST_AND_OR(suffix, dtype) \
|
|
static PyObject * \
|
|
test_atomic_and_or_##suffix(PyObject *self, PyObject *obj) { \
|
|
dtype x = (dtype)0; \
|
|
dtype y = (dtype)1; \
|
|
dtype z = (dtype)3; \
|
|
assert(_Py_atomic_or_##suffix(&x, z) == (dtype)0); \
|
|
assert(x == (dtype)3); \
|
|
assert(_Py_atomic_and_##suffix(&x, y) == (dtype)3); \
|
|
assert(x == (dtype)1); \
|
|
Py_RETURN_NONE; \
|
|
}
|
|
FOR_BITWISE_TYPES(IMPL_TEST_AND_OR)
|
|
|
|
static PyObject *
|
|
test_atomic_fences(PyObject *self, PyObject *obj) {
|
|
// Just make sure that the fences compile. We are not
|
|
// testing any synchronizing ordering.
|
|
_Py_atomic_fence_seq_cst();
|
|
_Py_atomic_fence_release();
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *
|
|
test_atomic_release_acquire(PyObject *self, PyObject *obj) {
|
|
void *x = NULL;
|
|
void *y = &y;
|
|
assert(_Py_atomic_load_ptr_acquire(&x) == NULL);
|
|
_Py_atomic_store_ptr_release(&x, y);
|
|
assert(x == y);
|
|
assert(_Py_atomic_load_ptr_acquire(&x) == y);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// NOTE: all tests should start with "test_atomic_" to be included
|
|
// in test_pyatomic.py
|
|
|
|
#define BIND_TEST_ADD(suffix, dtype) \
|
|
{"test_atomic_add_" #suffix, test_atomic_add_##suffix, METH_NOARGS},
|
|
#define BIND_TEST_COMPARE_EXCHANGE(suffix, dtype) \
|
|
{"test_atomic_compare_exchange_" #suffix, test_atomic_compare_exchange_##suffix, METH_NOARGS},
|
|
#define BIND_TEST_EXCHANGE(suffix, dtype) \
|
|
{"test_atomic_exchange_" #suffix, test_atomic_exchange_##suffix, METH_NOARGS},
|
|
#define BIND_TEST_LOAD_STORE(suffix, dtype) \
|
|
{"test_atomic_load_store_" #suffix, test_atomic_load_store_##suffix, METH_NOARGS},
|
|
#define BIND_TEST_AND_OR(suffix, dtype) \
|
|
{"test_atomic_and_or_" #suffix, test_atomic_and_or_##suffix, METH_NOARGS},
|
|
|
|
static PyMethodDef test_methods[] = {
|
|
FOR_ARITHMETIC_TYPES(BIND_TEST_ADD)
|
|
FOR_ALL_TYPES(BIND_TEST_COMPARE_EXCHANGE)
|
|
FOR_ALL_TYPES(BIND_TEST_EXCHANGE)
|
|
FOR_ALL_TYPES(BIND_TEST_LOAD_STORE)
|
|
FOR_BITWISE_TYPES(BIND_TEST_AND_OR)
|
|
{"test_atomic_fences", test_atomic_fences, METH_NOARGS},
|
|
{"test_atomic_release_acquire", test_atomic_release_acquire, METH_NOARGS},
|
|
{NULL, NULL} /* sentinel */
|
|
};
|
|
|
|
int
|
|
_PyTestCapi_Init_PyAtomic(PyObject *mod)
|
|
{
|
|
if (PyModule_AddFunctions(mod, test_methods) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|