diff --git a/.idea/PyFastUtil.iml b/.idea/PyFastUtil.iml index e897a98..4160845 100644 --- a/.idea/PyFastUtil.iml +++ b/.idea/PyFastUtil.iml @@ -319,6 +319,10 @@ + + + + diff --git a/pyfastutil/ints.pyi b/pyfastutil/ints.pyi index d66480c..1cf2ebe 100644 --- a/pyfastutil/ints.pyi +++ b/pyfastutil/ints.pyi @@ -1,4 +1,4 @@ -from typing import overload, Iterable, SupportsIndex, final +from typing import overload, Iterable, SupportsIndex, final, Iterator, _T_co @final @@ -133,3 +133,46 @@ class IntArrayList(list[int]): [1, 2] """ pass + + +@final +class IntArrayListIter(Iterator[int]): + """ + Iterator for IntArrayList. + + This class provides an iterator over an `IntArrayList`, allowing you to iterate over the elements + of the list one by one. + + Note: + This class cannot be directly instantiated by users. It is designed to be used internally by + `IntArrayList` and can only be obtained by calling the `__iter__` method on an `IntArrayList` object. + + Example: + >>> int_list = IntArrayList([1, 2, 3]) + >>> iter_obj = iter(int_list) # This returns an IntArrayListIter instance + >>> next(iter_obj) + 1 + >>> next(iter_obj) + 2 + >>> next(iter_obj) + 3 + >>> next(iter_obj) # Raises StopIteration + + Raises: + TypeError: If attempted to be instantiated directly. + """ + + def __next__(self) -> int: + """ + Return the next element in the iteration. + + This method retrieves the next element from the `IntArrayList` that this iterator is associated with. + If all elements have been iterated over, it raises a `StopIteration` exception. + + Returns: + int: The next integer in the `IntArrayList`. + + Raises: + StopIteration: If there are no more elements to iterate over. + """ + pass diff --git a/pyfastutil/src/PyFastUtil.cpp b/pyfastutil/src/PyFastUtil.cpp index cd5c8b5..a28b299 100644 --- a/pyfastutil/src/PyFastUtil.cpp +++ b/pyfastutil/src/PyFastUtil.cpp @@ -5,6 +5,7 @@ #include "PyFastUtil.h" #include "utils/simd/BitonicSort.h" #include "ints/IntArrayList.h" +#include "ints/IntArrayListIter.h" #include "unsafe/Unsafe.h" static struct PyModuleDef pyfastutilModule = { @@ -26,6 +27,8 @@ PyMODINIT_FUNC PyInit___pyfastutil() { return nullptr; PyModule_AddObject(parent, "IntArrayList", PyInit_IntArrayList()); + PyModule_AddObject(parent, "IntArrayListIter", PyInit_IntArrayListIter()); + PyModule_AddObject(parent, "Unsafe", PyInit_Unsafe()); return parent; diff --git a/pyfastutil/src/ints/IntArrayList.cpp b/pyfastutil/src/ints/IntArrayList.cpp index 8a891a9..705500f 100644 --- a/pyfastutil/src/ints/IntArrayList.cpp +++ b/pyfastutil/src/ints/IntArrayList.cpp @@ -6,18 +6,14 @@ #include #include #include +#include #include "utils/PythonUtils.h" -#include "utils/ParseUtils.h" #include "utils/TimSort.h" #include "utils/simd/BitonicSort.h" #include "utils/memory/AlignedAllocator.h" +#include "ints/IntArrayListIter.h" extern "C" { -typedef struct IntArrayList { - PyObject_HEAD; - // we use 64 bytes memory aligned to support faster SIMD, suggestion by ChatGPT. - std::vector> vector; -} IntArrayList; static PyTypeObject IntArrayListType = { PyVarObject_HEAD_INIT(&PyType_Type, 0) @@ -454,35 +450,54 @@ static PyObject *IntArrayList_sort(PyObject *pySelf, PyObject *args, PyObject *k Py_RETURN_NONE; } +Py_ssize_t IntArrayList_len(PyObject *pySelf) { + auto *self = reinterpret_cast(pySelf); + + return static_cast(self->vector.size()); +} + + +PyObject *IntArrayList_iter(PyObject *pySelf) { + auto *self = reinterpret_cast(pySelf); + + return reinterpret_cast(IntArrayListIter_create(self)); +} + static PyMethodDef IntArrayList_methods[] = { {"from_range", (PyCFunction) IntArrayList_from_range, METH_VARARGS | METH_STATIC}, - {"resize", (PyCFunction) IntArrayList_resize, METH_O}, - {"copy", (PyCFunction) IntArrayList_copy, METH_NOARGS}, - {"append", (PyCFunction) IntArrayList_append, METH_O}, - {"extend", (PyCFunction) IntArrayList_extend, METH_O}, - {"pop", (PyCFunction) IntArrayList_pop, METH_VARARGS}, - {"index", (PyCFunction) IntArrayList_index, METH_VARARGS}, - {"count", (PyCFunction) IntArrayList_count, METH_O}, - {"insert", (PyCFunction) IntArrayList_insert, METH_VARARGS}, - {"remove", (PyCFunction) IntArrayList_remove, METH_O}, - {"sort", (PyCFunction) IntArrayList_sort, METH_VARARGS | METH_KEYWORDS}, + {"resize", (PyCFunction) IntArrayList_resize, METH_O}, + {"copy", (PyCFunction) IntArrayList_copy, METH_NOARGS}, + {"append", (PyCFunction) IntArrayList_append, METH_O}, + {"extend", (PyCFunction) IntArrayList_extend, METH_O}, + {"pop", (PyCFunction) IntArrayList_pop, METH_VARARGS}, + {"index", (PyCFunction) IntArrayList_index, METH_VARARGS}, + {"count", (PyCFunction) IntArrayList_count, METH_O}, + {"insert", (PyCFunction) IntArrayList_insert, METH_VARARGS}, + {"remove", (PyCFunction) IntArrayList_remove, METH_O}, + {"sort", (PyCFunction) IntArrayList_sort, METH_VARARGS | METH_KEYWORDS}, {nullptr} }; static struct PyModuleDef IntArrayList_module = { PyModuleDef_HEAD_INIT, "__pyfastutil.IntArrayList", - "A IntArrayList_module that creates an IntArrayList", + "An IntArrayList_module that creates an IntArrayList", -1, nullptr, nullptr, nullptr, nullptr, nullptr }; +static PySequenceMethods IntArrayList_asSequence = { + .sq_length = (lenfunc) IntArrayList_len +}; + void initializeIntArrayListType(PyTypeObject &type) { type.tp_name = "IntArrayList"; type.tp_basicsize = sizeof(IntArrayList); type.tp_itemsize = 0; type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + type.tp_as_sequence = &IntArrayList_asSequence; + type.tp_iter = IntArrayList_iter; type.tp_methods = IntArrayList_methods; type.tp_init = (initproc) IntArrayList_init; type.tp_new = PyType_GenericNew; diff --git a/pyfastutil/src/ints/IntArrayList.h b/pyfastutil/src/ints/IntArrayList.h index 8d61de2..e98989b 100644 --- a/pyfastutil/src/ints/IntArrayList.h +++ b/pyfastutil/src/ints/IntArrayList.h @@ -6,6 +6,16 @@ #define PYFASTUTIL_INTARRAYLIST_H #include "utils/PythonPCH.h" +#include "utils/memory/AlignedAllocator.h" +#include + +extern "C" { +typedef struct IntArrayList { + PyObject_HEAD; + // we use 64 bytes memory aligned to support faster SIMD, suggestion by ChatGPT. + std::vector> vector; +} IntArrayList; +} PyMODINIT_FUNC PyInit_IntArrayList(); diff --git a/pyfastutil/src/ints/IntArrayListIter.cpp b/pyfastutil/src/ints/IntArrayListIter.cpp new file mode 100644 index 0000000..624e697 --- /dev/null +++ b/pyfastutil/src/ints/IntArrayListIter.cpp @@ -0,0 +1,91 @@ +// +// Created by xia__mc on 2024/11/13. +// + +#include "IntArrayListIter.h" +#include "utils/PythonUtils.h" + +extern "C" { + +static PyTypeObject IntArrayListIterType = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) +}; + +IntArrayListIter *IntArrayListIter_create(IntArrayList *list) { + auto *instance = Py_CreateObjNoInit(IntArrayListIterType); + + Py_INCREF(list); + instance->container = list; + instance->index = 0; + + return instance; +} + +static void IntArrayListIter_dealloc(IntArrayListIter *self) { + SAFE_DECREF(self->container); + Py_TYPE(self)->tp_free((PyObject *) self); +} + +static PyObject *IntArrayListIter_next(PyObject *pySelf) { + auto *self = reinterpret_cast(pySelf); + + if (self->index >= self->container->vector.size()) { + PyErr_SetNone(PyExc_StopIteration); + return nullptr; + } + + auto element = self->container->vector[self->index]; + self->index++; + return PyLong_FromLong(static_cast(element)); +} + +static PyObject *IntArrayListIter_iter(PyObject *pySelf) { + return pySelf; +} + +static PyMethodDef IntArrayListIter_methods[] = { + {nullptr} +}; + +static struct PyModuleDef IntArrayListIter_module = { + PyModuleDef_HEAD_INIT, + "__pyfastutil.IntArrayListIter", + "An IntArrayListIter_module that creates an IntArrayList", + -1, + nullptr, nullptr, nullptr, nullptr, nullptr +}; + +void initializeIntArrayListIterType(PyTypeObject &type) { + type.tp_name = "IntArrayListIter"; + type.tp_basicsize = sizeof(IntArrayListIter); + type.tp_itemsize = 0; + type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + type.tp_iter = IntArrayListIter_iter; + type.tp_iternext = IntArrayListIter_next; + type.tp_methods = IntArrayListIter_methods; + type.tp_dealloc = (destructor) IntArrayListIter_dealloc; + type.tp_alloc = PyType_GenericAlloc; + type.tp_free = PyObject_Del; +} + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" +PyMODINIT_FUNC PyInit_IntArrayListIter() { + initializeIntArrayListIterType(IntArrayListIterType); + + PyObject *object = PyModule_Create(&IntArrayListIter_module); + if (object == nullptr) + return nullptr; + + Py_INCREF(&IntArrayListIterType); + if (PyModule_AddObject(object, "IntArrayList", (PyObject *) &IntArrayListIterType) < 0) { + Py_DECREF(&IntArrayListIterType); + Py_DECREF(object); + return nullptr; + } + + return object; +} +#pragma clang diagnostic pop + +} diff --git a/pyfastutil/src/ints/IntArrayListIter.h b/pyfastutil/src/ints/IntArrayListIter.h new file mode 100644 index 0000000..dbfc6e3 --- /dev/null +++ b/pyfastutil/src/ints/IntArrayListIter.h @@ -0,0 +1,24 @@ +// +// Created by xia__mc on 2024/11/13. +// + +#ifndef PYFASTUTIL_INTARRAYLISTITER_H +#define PYFASTUTIL_INTARRAYLISTITER_H + +#include "utils/PythonPCH.h" +#include "IntArrayList.h" + +extern "C" { +typedef struct IntArrayListIter { + PyObject_HEAD; + IntArrayList *container; + size_t index; +} IntArrayListIter; + +IntArrayListIter *IntArrayListIter_create(IntArrayList *list); + +} + +PyMODINIT_FUNC PyInit_IntArrayListIter(); + +#endif //PYFASTUTIL_INTARRAYLISTITER_H diff --git a/pyfastutil/src/unsafe/Unsafe.cpp b/pyfastutil/src/unsafe/Unsafe.cpp index bcbcbd6..f0b00e0 100644 --- a/pyfastutil/src/unsafe/Unsafe.cpp +++ b/pyfastutil/src/unsafe/Unsafe.cpp @@ -6,9 +6,6 @@ #include "mutex" extern "C" { -typedef struct Unsafe { - PyObject_HEAD; -} Unsafe; static PyTypeObject UnsafeType = { PyVarObject_HEAD_INIT(&PyType_Type, 0) @@ -159,6 +156,10 @@ static PyObject *Unsafe_decref([[maybe_unused]] PyObject *self, PyObject *pyObje Py_RETURN_NONE; } +static PyObject *Unsafe_refcnt([[maybe_unused]] PyObject *self, PyObject *pyObject) { + return PyLong_FromSsize_t(Py_REFCNT(pyObject)); +} + static PyObject *Unsafe_fputs([[maybe_unused]] PyObject *pySelf, PyObject *pyStr) { if (!PyUnicode_Check(pyStr)) { PyErr_SetString(PyExc_TypeError, "Argument must be a string."); @@ -217,6 +218,7 @@ static PyMethodDef Unsafe_methods[] = { {"memcpy", (PyCFunction) Unsafe_memcpy, METH_VARARGS, nullptr}, {"incref", (PyCFunction) Unsafe_incref, METH_O, nullptr}, {"decref", (PyCFunction) Unsafe_decref, METH_O, nullptr}, + {"refcnt", (PyCFunction) Unsafe_refcnt, METH_O, nullptr}, {"fputs", (PyCFunction) Unsafe_fputs, METH_O, nullptr}, {"fflush", (PyCFunction) Unsafe_fflush, METH_NOARGS, nullptr}, {"fgets", (PyCFunction) Unsafe_fgets, METH_O, nullptr}, diff --git a/pyfastutil/src/unsafe/Unsafe.h b/pyfastutil/src/unsafe/Unsafe.h index f66a473..8de0b7e 100644 --- a/pyfastutil/src/unsafe/Unsafe.h +++ b/pyfastutil/src/unsafe/Unsafe.h @@ -7,6 +7,12 @@ #include "utils/PythonPCH.h" +extern "C" { +typedef struct Unsafe { + PyObject_HEAD; +} Unsafe; +} + PyMODINIT_FUNC PyInit_Unsafe(); #endif //PYFASTUTIL_UNSAFE_H diff --git a/pyfastutil/src/utils/ParseUtils.h b/pyfastutil/src/utils/ParseUtils.h deleted file mode 100644 index 094ebc8..0000000 --- a/pyfastutil/src/utils/ParseUtils.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Created by xia__mc on 2024/11/10. -// - -#ifndef PYFASTUTIL_PARSEUTILS_H -#define PYFASTUTIL_PARSEUTILS_H - -#include "PythonPCH.h" -#include "Compat.h" - -/** - * Try to parse args like built-in range function - * If not successful, function will raise python exception. - * @return if successful - */ -static __forceinline bool PyParse_EvalRange(PyObject *&args, Py_ssize_t &start, Py_ssize_t &stop, Py_ssize_t &step) { - Py_ssize_t arg1 = PY_SSIZE_T_MAX; - Py_ssize_t arg2 = PY_SSIZE_T_MAX; - Py_ssize_t arg3 = PY_SSIZE_T_MAX; - - if (!PyArg_ParseTuple(args, "n|nn", &arg1, &arg2, &arg3)) { - return false; - } - - if (arg2 == PY_SSIZE_T_MAX) { - start = 0; - stop = arg1; - step = 1; - } else if (arg3 == PY_SSIZE_T_MAX) { - start = arg1; - stop = arg2; - step = 1; - } else if (arg3 != 0) { - start = arg1; - stop = arg2; - step = arg3; - } else { - PyErr_SetString(PyExc_ValueError, "Arg 3 must not be zero."); - return false; - } - - return true; -} - -#endif //PYFASTUTIL_PARSEUTILS_H diff --git a/pyfastutil/src/utils/PythonPCH.h b/pyfastutil/src/utils/PythonPCH.h index 6bcfdd6..40612b7 100644 --- a/pyfastutil/src/utils/PythonPCH.h +++ b/pyfastutil/src/utils/PythonPCH.h @@ -11,4 +11,6 @@ #pragma warning(pop) +#include "Compat.h" + #endif //PYFASTUTIL_PYTHONPCH_H diff --git a/pyfastutil/src/utils/PythonUtils.h b/pyfastutil/src/utils/PythonUtils.h index 03a4096..a0a2709 100644 --- a/pyfastutil/src/utils/PythonUtils.h +++ b/pyfastutil/src/utils/PythonUtils.h @@ -8,7 +8,8 @@ #include "PythonPCH.h" #include "Compat.h" -static __forceinline void SAFE_DECREF(PyObject *&object) { +template +static __forceinline void SAFE_DECREF(T *&object) { if (object == nullptr) return; Py_DECREF(object); @@ -25,4 +26,38 @@ static __forceinline T *Py_CreateObjNoInit(PyTypeObject &typeObj) { return reinterpret_cast(_PyObject_New(&typeObj)); } +/** + * Try to parse args like built-in range function + * If not successful, function will raise python exception. + * @return if successful + */ +static __forceinline bool PyParse_EvalRange(PyObject *&args, Py_ssize_t &start, Py_ssize_t &stop, Py_ssize_t &step) { + Py_ssize_t arg1 = PY_SSIZE_T_MAX; + Py_ssize_t arg2 = PY_SSIZE_T_MAX; + Py_ssize_t arg3 = PY_SSIZE_T_MAX; + + if (!PyArg_ParseTuple(args, "n|nn", &arg1, &arg2, &arg3)) { + return false; + } + + if (arg2 == PY_SSIZE_T_MAX) { + start = 0; + stop = arg1; + step = 1; + } else if (arg3 == PY_SSIZE_T_MAX) { + start = arg1; + stop = arg2; + step = 1; + } else if (arg3 != 0) { + start = arg1; + stop = arg2; + step = arg3; + } else { + PyErr_SetString(PyExc_ValueError, "Arg 3 must not be zero."); + return false; + } + + return true; +} + #endif //PYFASTUTIL_PYTHONUTILS_H diff --git a/pyfastutil/src/utils/TimSort.h b/pyfastutil/src/utils/TimSort.h index d02d4a3..6d5a7ba 100644 --- a/pyfastutil/src/utils/TimSort.h +++ b/pyfastutil/src/utils/TimSort.h @@ -710,7 +710,7 @@ namespace gfx { > requires std::sortable, Compare, Projection> [[maybe_unused]] auto timmerge(Range &&range, std::ranges::iterator_t middle, - Compare comp = {}, Projection proj = {}) + Compare comp = {}, Projection proj = {}) -> std::ranges::borrowed_iterator_t { return gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj); } diff --git a/pyfastutil/src/utils/memory/AlignedAllocator.h b/pyfastutil/src/utils/memory/AlignedAllocator.h index 9b0f38d..d203d2f 100644 --- a/pyfastutil/src/utils/memory/AlignedAllocator.h +++ b/pyfastutil/src/utils/memory/AlignedAllocator.h @@ -6,6 +6,7 @@ #define PYFASTUTIL_ALIGNEDALLOCATOR_H #include +#include "stdexcept" #include "Compat.h" template diff --git a/pyfastutil/src/utils/memory/PreFetch.h b/pyfastutil/src/utils/memory/PreFetch.h index 2f3bfdf..8d3eb84 100644 --- a/pyfastutil/src/utils/memory/PreFetch.h +++ b/pyfastutil/src/utils/memory/PreFetch.h @@ -13,17 +13,17 @@ #elif defined(__aarch64__) || defined(__arm__) // ARM platform (32-bit or 64-bit) // Using __builtin_prefetch for ARM platforms - #define _MM_HINT_T0 0 // Prefetch to L1 - #define _MM_HINT_T1 0 // Prefetch to L2 (no direct equivalent, use L1) - #define _MM_HINT_T2 0 // Prefetch to L3 (no direct equivalent, use L1) - #define _MM_HINT_NTA 0 // Non-temporal prefetch (no direct equivalent, fallback to L1) +#define _MM_HINT_T0 0 // Prefetch to L1 +#define _MM_HINT_T1 0 // Prefetch to L2 (no direct equivalent, use L1) +#define _MM_HINT_T2 0 // Prefetch to L3 (no direct equivalent, use L1) +#define _MM_HINT_NTA 0 // Non-temporal prefetch (no direct equivalent, fallback to L1) // Define _mm_prefetch to use __builtin_prefetch for ARM - #define _mm_prefetch(p, hint) __builtin_prefetch(p, 0, hint) +#define _mm_prefetch(p, hint) __builtin_prefetch(p, 0, hint) #else // Other platforms: No prefetch available - #define _mm_prefetch(p, hint) ((void)0) // No-op for unsupported platforms +#define _mm_prefetch(p, hint) ((void)0) // No-op for unsupported platforms #endif __forceinline void prefetchL1(const void *pointer) { diff --git a/pyfastutil/unsafe.pyi b/pyfastutil/unsafe.pyi index 49cf1e9..d3f9122 100644 --- a/pyfastutil/unsafe.pyi +++ b/pyfastutil/unsafe.pyi @@ -169,6 +169,14 @@ class Unsafe: """ pass + def refcnt(self, __object: object) -> int: + """ + Get the reference count of a Python object. + + :param __object: The Python object. + """ + pass + def fputs(self, __str: str) -> None: """ Writes a string to a low-level output stream. diff --git a/setup.py b/setup.py index db033ca..4e6dfad 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ "-fno-tree-vectorize" # "-Wno-error=unguarded-availability-new" # already handle in Compat.h, and this option doesn't exist on gcc ] - EXTRA_LINK_ARG = ["-shared"] + EXTRA_LINK_ARG = ["-bundle"] if __name__ == "__main__": if os.path.exists("./build"): diff --git a/tests/test_int_array_list.py b/tests/test_int_array_list.py index 5373ad6..809b1ee 100644 --- a/tests/test_int_array_list.py +++ b/tests/test_int_array_list.py @@ -5,7 +5,18 @@ class MyTestCase(unittest.TestCase): def test_index(self): lst = IntArrayList([1, 2, 3]) - self.assertEqual(lst.index(3), 2) # add assertion here + self.assertEqual(lst.index(1), 0) + + def test_len(self): + lst = IntArrayList([1, 2, 3]) + self.assertEqual(len(lst), 3) + + def test_iter(self): + lst = [1, 2, 3] + newLst = [] + for i in IntArrayList(lst): + newLst.append(i) + self.assertEqual(lst, newLst) if __name__ == '__main__':