22 * PyFastPFOR
33 *
44 * Python bindings for the FastPFOR library:
5- * https://github.com/lemire/FastPFor
5+ * https://github.com/lemire/FastPFor
66 *
77 * This code is released under the
88 * Apache License Version 2.0 http://www.apache.org/licenses/.
1111
1212#include < cstdint>
1313#include < iostream>
14+ #include < mutex>
1415#include < string>
1516
1617#include < pybind11/pybind11.h>
@@ -27,6 +28,9 @@ using namespace FastPForLib;
2728
2829const char * module_name = " pyfastpfor" ;
2930
31+ // encodeArray/decodeArray release the GIL for the duration of the call; mutex_
32+ // serializes concurrent calls on the same instance since the underlying
33+ // codec's scratch state isn't otherwise safe for concurrent use.
3034struct IntegerCODECWrapper {
3135public:
3236 IntegerCODECWrapper (const std::string& codecName) {
@@ -36,13 +40,14 @@ struct IntegerCODECWrapper {
3640 py::array_t <uint32_t , py::array::c_style> input, size_t inputSize,
3741 py::array_t <uint32_t , py::array::c_style> output, size_t outputSize) {
3842 py::gil_scoped_release l;
43+ std::lock_guard<std::mutex> lock (mutex_);
3944
4045 const uint32_t * inpBuff = input.data ();
4146
4247 uint32_t * outBuff = output.mutable_data ();
4348 size_t compSize = outputSize;
4449
45- codec_->encodeArray (inpBuff, inputSize,
50+ codec_->encodeArray (inpBuff, inputSize,
4651 outBuff, compSize);
4752
4853 return compSize;
@@ -51,6 +56,7 @@ struct IntegerCODECWrapper {
5156 py::array_t <uint32_t , py::array::c_style> input, size_t inputSize,
5257 py::array_t <uint32_t , py::array::c_style> output, size_t outputSize) {
5358 py::gil_scoped_release l;
59+ std::lock_guard<std::mutex> lock (mutex_);
5460
5561 const uint32_t * inpBuff = input.data ();
5662
@@ -64,16 +70,21 @@ struct IntegerCODECWrapper {
6470private:
6571 CODECFactory factory;
6672 IntegerCODEC* codec_;
73+ std::mutex mutex_;
6774};
6875
69- /*
70- * PYBIND11_MODULE is a replacement for PYBIND11_PLUGIN
76+ /*
77+ * PYBIND11_MODULE is a replacement for PYBIND11_PLUGIN
7178 * introduced in Pybind 2.2. However, we don't require
72- * Pybind to be >= 2.0 so we attempt to support older
79+ * Pybind to be >= 2.0 so we attempt to support older
7380 * Pybind versions as well.
7481 */
7582#ifdef PYBIND11_MODULE
76- PYBIND11_MODULE (pyfastpfor, m) {
83+ // mod_gil_not_used (pybind11 >= 2.13) declares this module free-threading
84+ // safe: every codec object owns its own private CODECFactory/codec state
85+ // (no cross-instance sharing), and the module-scope statics in vsencoding.h
86+ // are populated once at load and only read afterward.
87+ PYBIND11_MODULE (pyfastpfor, m, py::mod_gil_not_used()) {
7788 m.doc () = " Python Bindings for FastPFor library (fast integer compression)." ;
7889#else
7990PYBIND11_PLUGIN (pyfastpfor) {
@@ -94,7 +105,7 @@ PYBIND11_PLUGIN(pyfastpfor) {
94105 [](const std::string & codecName) {
95106 // We know that FastPFor will keep this shared pointer alive forever
96107 // so it is safe just to reference codec
97- return py::cast (new IntegerCODECWrapper (codecName),
108+ return py::cast (new IntegerCODECWrapper (codecName),
98109 py::return_value_policy::take_ownership);
99110 },
100111 py::arg (" codecName" ),
@@ -204,16 +215,16 @@ PYBIND11_PLUGIN(pyfastpfor) {
204215
205216void exportCodecs (py::module & m) {
206217 py::class_<IntegerCODECWrapper>(m, " IntegerCODEC" )
207- .def (" encodeArray" , &IntegerCODECWrapper::encodeArray,
208- py::arg (" input" ), py::arg (" inputSize" ),
218+ .def (" encodeArray" , &IntegerCODECWrapper::encodeArray,
219+ py::arg (" input" ), py::arg (" inputSize" ),
209220 py::arg (" output" ), py::arg (" outputSize" ),
210221 " Compress input array.\n\n "
211222 " Parameters\n "
212223 " ----------\n "
213224 " input: numpy C-style contiguous array to be compressed, e.g.:\n "
214225 " input = numpy.array(range(256), dtype = np.uint32).ravel()\n "
215226 " inputSize: a number of integers to compress: it can be less than\n "
216- " than the total number of integers in the numpy array.\n "
227+ " than the total number of integers in the numpy array.\n "
217228 " output: numpy C-style contiguous array with compressed data, e.g.:\n "
218229 " output = np.zeros(buffSize, dtype = np.uint32).ravel()\n "
219230 " outputSize: a capacity of the output buffer: it can be less than\n "
@@ -223,15 +234,15 @@ void exportCodecs(py::module& m) {
223234 " ----------\n "
224235 " A number of integers in the compressed output." )
225236 .def (" decodeArray" , &IntegerCODECWrapper::decodeArray,
226- py::arg (" input" ), py::arg (" inputSize" ),
237+ py::arg (" input" ), py::arg (" inputSize" ),
227238 py::arg (" output" ), py::arg (" outputSize" ),
228239 " Uncompress input array.\n\n "
229240 " Parameters\n "
230241 " ----------\n "
231242 " input: numpy C-style contiguous array to be uncompressed, e.g.:\n "
232243 " input = numpy.array(range(256), dtype = np.uint32).ravel()\n "
233244 " inputSize: a number of integers to compress: it can be less than\n "
234- " than the total number of integers in the numpy array.\n "
245+ " than the total number of integers in the numpy array.\n "
235246 " output: numpy C-style contiguous array with compressed data, e.g.:\n "
236247 " output = np.zeros(buffSize, dtype = np.uint32).ravel()\n "
237248 " outputSize: a capacity of the output buffer: it can be less than\n "
@@ -243,4 +254,3 @@ void exportCodecs(py::module& m) {
243254 )
244255 ;
245256}
246-
0 commit comments