0

In my Python C Extension I am performing actions on an iterable of strings. So in a first step I call PySequence_Fast to convert it to a list and then iterate over the elements. For each string I use PyUnicode_DATA and then compare the strings using some criteria. So I only read from PyObjects, but never modify them.

Now I would like to process the list in parallel, which would require me to release the GIL. However I do not know which effects this has on my use case. Here are my current thoughts:

  1. I can still use those APIs, since they are only macros, that directly read from the PyObjects without modifying them.

  2. I have to use the APIs beforehand and store a array of structs that hold kind, length and data pointer of the strings

  3. I have to use the APis beforehand and have to store a copy of the strings in a array

Case 1 would be the most performant and memory efficient. However it is stated, that without acquiring the GIL it is not allowed to perform on Python objects (does this include reading access) or use Python/C API functions.

Case 2 would be the next most efficient, since at least I do not have to copy all strings. However when I am not allowed to read from Python objects while the GIL is released, I wonder whether I would even be allowed to use a pointer to the data inside the PyObject.

Case 3 would require me to copy all strings. In my case this might make the multithreaded solution slower than a sequential solutions.

I hope someone can help me understand what I am allowed to do while the GIL is released.

maxbachmann
  • 2,862
  • 1
  • 11
  • 35

1 Answers1

1

I think the official answer is that you should not do method 1 and should use methods 2 and 3. And that while it might work now it could change in the future and break. This is especially important if you want to support things like PyPy's C-API wrapper (which might well use a different representation that Python internally). There are increasing moves to try to hide implementation details that you slightly risk getting caught out by.

Practically I think method 1 would work fine provided you only use the macro forms with no error checking - the GIL is mainly about stopping simultaneous writes putting Python objects in an undefined state, and you aren't doing this. Where I'd be slightly careful is if you ever have (deprecated) "non-canonical" unicode objects - things that look "macro-y" like PyUnicode_READY can cause them to be modified to the canonical state. Again, be especially wary of alternative (non-CPython) implementations of the C-API.

One alternative to consider would be to use the buffer protocol instead. Although I can't find it explicitly stated in the docs, the idea is that PyObject_GetBuffer and PyBuffer_Release require the GIL but reading/writing to the buffer doesn't. Here I have two sub-suggestions:

  • can you have a single object like a Numpy array that exposes all your strings as a buffer?
  • you can also get a buffer from a unicode object (as a utf-8 C-string) - the thing to do would be to create all the buffers with the GIL, do your parallel processing without, and them free them with the GIL. It's possible that the overhead for this might be inefficient. This is basically an "official" version of method 2.

I short, you'd probably get away with it, but if it ever breaks I doubt that a bug report to Python would be well-received (since it's technically wrong)

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Thanks for pointing out the increased moves to hide implementation details. Especially the specification `Convert PyTuple_GET_ITEM() and PyList_GET_ITEM() macros to static inline functions` could stop the list iteration from working without the GIL. – maxbachmann Feb 27 '21 at 19:58
  • I suspect it won't be an immediate problem and that the inline function will look a lot like the macro, but it's still worth being a little cautious (or at least being prepared for breakages in future) – DavidW Feb 27 '21 at 21:59
  • Yes I will use them without the GIL for now, but add a comment to the code, that this might be break in the future, since it is implementation defined behavior – maxbachmann Feb 27 '21 at 22:44