500 Python Interpreters Izzy Muerte

500 Python Interpreters

As the final release date of Python 3. 13 approaches, we are seeing lively discussions about introducing an optional GIL in 3. 13. While abandoning the GIL is long overdue for normal users (I've been dreaming of this for nearly 20 years), there have actually been two parallel attempts to improve Python's multithreading performance: the optional GIL defined in PEP 703, and the introduction of an interpreter-based GIL defined in PEP 684 and introduced in Python 3. 12.

In this post, I'll talk about the situation with the GIL, how it influenced the design of Python's C API, and what steps were taken at the C API level to get to where we are today. I'll also briefly talk about how PEP 684 and PEP 703 can work together. And to tie it all together, let's start with my own situation on the ground and my first encounter (and then my first encounter) with the GIL in Python. Learn to make games

Video games V I D E O G A M E S ✨ .

A Dark Seed Is Planted

In the summer of 2005, I attended a game development camp based at the Stanford Research Institute from June to August. This was my first real exposure to programming. I'm not even counting the C++ class I took in my freshman year of high school, because all I did was scan a C++ book and copy words from a JPEG file, scaled down to fit two pages on a 8, 5x11 inch piece of paper. At the time, it was no different (to me) than reading the Voynich Manuscript or the Seraphinianus Codex.

I spent a year learning Python 2. 4 before Python 2. 5 came out. I spent a year learning 4. At that time, PyGame was around version 1. 1, and during this time, you would need some Python knowledge now to use it effectively, for example. In fact, even now, if you are learning Python through PyGame, it is probably not the right introduction to Python as a language - it is an intermediate step after you have mastered the basics like objects, for loops, etc. But we used a library called livewires (a slightly modified fork) that provided a fairly simplified wrapper around the PyGame logic, allowing you to focus on your learning.

Michael Dawson is a software developer, television scriptwriter, and teacher. Those who read the title of this section and the symbols that represent the old video game situation will probably recognize his name as the main software developer, scriptwriter, and star of the 1992 game for DOS, Darkseid 1. For me, for example, this was quite radical, as the weight of his teachings was diverted. Of course, once, I can arrange it in the same way. So, how 🙂 .

At this stage of the research, I was benefited by a huge number of sprites. This is an atlas of sprites, not to mention the fact that it is realized with the support of the API that benefited from Livewire. The loading times were terrible. There were more than 100 sprites, each with about 20 animations. Honestly, I was at a loss. Besides the training sessions, there were help events and opportunities to distribute to others attending the camp. They had a GP2X and had managed to get their own games to work on this device. I explained their work, they set up a way to profile my code, and we realized that it would take a whole century for me to upload sprites for 1, invent another spray and read. During this time, they at least once suggested applying the Jet module to speed up the work. After spending time fussing, we came to the fact that he deserved & amp; amp; the loading times increased 😭.

The next day, I asked Michael a question that could be a problem. When I encountered the oldest Pytho n-case For 2: This accursed Gil, he happened to attribute it to Jets and Interpreter Python, with a listless sigh.

I have yet to meet a single person who encountered this task in the context of a game. In Sid Meier's Civilisation IV, Python was used for the game logic and scripting system. The game started slower the more units there were on the map, and the more moves there were. Really, in fact, Civ IV is (and probably also admits that it is actually) a performance problem to this day and age. With the release of Sid Meier's Civilisation V, the team made the switch to Lua, and this was seen as a big deal, as it meant a huge performance boost. And this was indeed true! The number of locks went down, and the game didn't take centuries to process the AI ​​execution (almost).

Too bad! My animosity towards Gil started as a dark seed some 20 years ago. For many years I experienced jealousy towards Lua, to the point of refusing to use Lua unless forced to for the past 9 years, mostly due to having to manipulate the stack directly and having to care about things like Upalues. What's an up value? I don't know, what's up with that?

Erm, what the sigma GIL?

So there is a common misunderstanding about what Gil is and why it exists. To better understand this situation, I will talk specifically about the evolution of Cpython and its interpreter.

A Short History of Threading

Stream processing was first introduced in Python in Python 1. 5, released on February 17, 1998. This release also saw the appearance of the stream module, as well as the appearance of the PyInterpreteterstate and PythreadState objects. Prior to that, Python had a simple evaluation phase with just the Code_EVAL function, which would sometimes switch to itself. The arrival of PyinterPretEterState and PythreadState was a very important event in the sense that Python finally moved to a built-in state, allowing users to not only extend Python, but also embed it. This may not seem like a big deal, but it was 1998. At the time, many scripting languages ​​were used, each with their own limitations on interacting with C in user programs. If you look at Perl's API today, you can see the design of the API in the 1990s. It may seem anti-establishment, but in fact it was just a pragmatic approach that suggested that if you tried to integrate Perl as a C library, it was a Perl interpreter.

This new PyInterPretEterState is the only instance, and in the upcoming Python 3. 13, there is still a static object pyinterpreteterstate* that is the main pyinterpreteterstate. Even if this pyinterpreteterstate is not used, it still exists.

This is why multiprocessor-processing modules have historically been fast in Python: you don't need to worry about Gil because you create a completely new copy of the interpreter as a subprogram. But you have to pay the cost of some work for the whole subprocess and the interaction between them. Especially on Windows, this is a very hard approach to speed up, but it has proven to be fast in high-level competitive cases.

Так оазом, с ыом вывода 3. 13, уоца та еом уохововололололололололах е gil, тод поталож быт борож, чолололососадочых е еах сах н.

Big GIL, So What?

Therefore, this situation may not have a special meaning if you come from other scripting languages. For those from Lua_state* s, jscontextRef S, V8 :: ISOLATE S, etc., it is necessary to look at how Python has developed. There were few real parts in the sand that explained how Python would develop. Although there is no speculation area, the function has been added because it was needed by someone, and it is unlikely to have a purpose. The release 1. 5 was released for the first time (with the progress of PEP) before Python 2. 0 was released before Python Software Foundation and Pep 1 appeared. Thus, Python has no real direction in which someone has the opportunity to go besides the job that someone is ready, and as a result, these configurations are & amp; amp; mldr; Well, anyway, it was made for the facts of what happened. Lua 3. 0 was released that we had many implementations of Perl, TCL, absolutely forgotten scheme, and Lua 3. 1 in July 1998 was only five months, Lua_state, which is well known. It was released with *(including) (and there was a meaningful problem that was not corrected before the release of 4. 0 during this time).

In fact, if you look at what we have to use to use to use it, the only measurement design for implementation is TCL (if you try to copy the design, It was OpenGL because you did not copy it for the fact of acquiring CMAKE 3. In OpenGL, there is an object "Context" that needs to be connected/ created in the provided stream, so you will sell similar interfaces in Python's Python with this refusal to a single-flow interpreter. You need it.

Let's not forget that at the time, the possibilities for making such decisions were limited. In the API field of dynamically typed scripting languages, people were more or less walking in the dark. They could not study decades of bugs and real-world usage. Even today, sometimes the worst option wins, even when we know there is a better option. For example, Lua is always praised in all quarters for being register-based. But wasme, which is becoming the cross-platform VM of the 21st century, chose a stack-based VM. This decision was made only for ✨ reasons ✨ that go beyond this note, and I have no reason to give them, and if you think you can stay silent, you're a botanist.

How Can She Interpret‽‽

As this explanation ends here, let's briefly consider how Gil actually works at the C API level, before it became optional. First of all, the user creates what is called a sub-interpreter, which is basically a PythreadState. This is done via an interpreter with the poor name py_newinterpreter. When called, it uses what is installed as the global interpreter and adds it to the PythreadStat e-list following PyInterprestate . It is assumed that at some point the user will take the GIL from the PythreadState , then value a specific Pytho n-code, then release the GIL from the PythreadState , at which point they can either destroy the PythreadState or just leave it nearby and continue with whatever execution they need. Whatever happens, there will be a lock. Two PythreadState s cannot execute Python bytecode at the same time. But they can execute multiple calls at the same time. That is why it is recommended to release the GIL temporarily for long-running operations in pure extensions or built-i n-i n-developers. Technically, something like a threadsanitizer can help.

The approach to such extensions is one of the reasons for GIL. Until the s o-called mult i-step initialization of the extension module by PEP 489 in Python 3. 5 was introduced, there was no way to really separate the module after import. All objects, exceptions, methods, etc. were added at the same time and were inserted into the center masterfanel. This purpose is to effectively separate the native code (no matter what language written, most people use C ++, C, Zig, Rust today) and the actual binding. Thus, "Python has locks", and it is reliable that developers with extensions can cut and enable locks until four Python objects are touched. You can get it.

As far as I know, if not coded at the C ABI level, Rust LifeStyle cannot be fully realized with these dynamic library expansion. Then, warn users about unprocessed lock/ unlock operations, and instead prioritize the Allow_threads method that processes lock/ unlock with the context of the ending user who is performing the actual work (in a language that supports RAII). If so, it is an operation that can provide rationality and security).

In Python 3. 2, I saw Python's first debris with an API that can create a su b-interpreter that has nothing to do with global interpreter. This API exists in the form of Py_newinterPRETERFROMCONFIG and sends a pyinterpreterConfig structure. This structure enables various "hard" settings. For example, you can set a flow module, OS. EXECV (), or disable the processing of the process (however, launch a completely new process, rather than forcibly generate a process. Masu). However, the most important parameters are the members of Pyinterpreterconfig. gil, jointly using the main interpreter's GIL and the su b-interpreter GIL, and the su b-interpreta can use the remaining GIL of the system. There are some warnings here. Sys. Stdin objects are unique Pyobject*, but refer to the same basic handle as other sys. stdin objects. This problem can be solved by simply changing.

500 Interpreter States

Finally, everything that has been said so far should already prepare you more or less to understand why such unusual misconduct occurs. For example, let's take an extreme example. Let's create one:

500 Interpreter States

This code test will be quite "high level". We will not run the following API design tests, because this is a model, not production code. With this in mind, we need to organize a few things before we organize the Python tooling. The first of them is to create an isolated configuration for the default interpreter. This is very basic, and if you do the right embedding, it is a way to not care about these things, for example, user collections and things similar to modify how Python has the ability to be generated.

Pyconfi g-config<>; Use Pyconfig_inithisolatedConfig(&config); if (AutomaticStatus=Py_initializeFromConfig(&config); Pystatus_iserror(status)) /* Do monitoring processing here */ > Clear Pyconfig_clear (&config); 

There is some extra work you can do with API Pypreconfi g-kit support, but I don't care. Read the documentation. I'm sure you'll understand.

This format takes the important step of initializing Python in a pre-determined, isolated state in case users may not provide standard library python with their executable.

Then it simply starts all streams. When developing a new PythreadStat e-specimen, you specify the current state of the stream you are creating. This basically means that you have to initialize it from inside Jet, more or less. With C++ you can approach history creatively. This also allows you to acknowledge whether this stream was initialized for streams or not. Since this is Bubby's "first model", we won't do any substantively unusual checks here, but a similar design paradigm may be necessary. Also, to save time, we'll factor in the maximum number of states as a constant.

// Inline it if you put it in the title. Throw away common sense 🙂 .  static streaming_local inlinePythreadState*state= nullptr; // 500 states for the interpreter 😌.  static Contexpr Automaticmaxstate= 463; 

Next, we'll clean up the usual pyinterpreterconfig. We want to capture this config object so it doesn't have to be huge in the function we run.

PyinterPRETERCONFIG=  US_main_obmallloc= 0,  Folk permission= 0,  Execution permit= 0,  Thread permission= 0,  Accept the thread of the demon= 0,  Check for mult i-interpr expansion= 1,  gil=PyinterPRETERCONFIG_OWN_GIL, >; 

There are some details that should be explained in a python document, but two luggage is the most important: check_multi_interp_extens and gil. CHECK_MULTI_INTERP_EXTENSIONS means, for example, forcing all expansion to be called multilateral expansion. This is when you initialize some module data, but call different functions, including objects, exceptions, or actual Pytho n-bindings. This was a major obstacle until Per-GIL's sub-inner print and optional GIL appeared. After forcing all expansion to be higher than the maximum, you can make a copy of all objects of each extension. This may be difficult to connect if there is a ball hidden in the native code, but you won't lie about this.

Then, generate all streams to create an interpreter status, complete the python code, display it in sys. stdout, and complete the interpreter. Feelback, pid, lemon kids.

// We hope that this STI will be unified during the liquidation.  STD::vectorSTD::JTHREED>task< >; task. Esreve (Max_states); To (AutomaticDig= 0ZU; DigTask capacity (); count++)  Task EMPLACE_BACK ([Config, Count]) if (AutomaticStatus=Py_newinterPRETERFROMCONFIG (&state,&config); Pystatus_iserror(status)) STD::Println ("The jet position could not be initialized.<>. The monitored was acquired<>"Count, status. Err_msg);  space;  >  Automaticword=STD::Format (R)"(Print (")Hello space!From the wire<>"))"count);  Automaticsphere=Pydict_new ();  Automaticcode=Py_compilestring (text. Data (), __file__, py_eval_input);  Automaticresult=Pyeval_evalcode (Code, Globals, Globals);  Py_decref (result);  Py_decref (code);  Py_decref (global);  Py_endINTERPRETER (Stat);  situation= nullptr;  >); > 

This allows JETS ELEMENTARY to expire outside the visible area and call py_finalize (). What is actually wrong?

FUCK YOU, ASSHOLE!

If you are such a fool and are actually trying to run a 500 463 python interpreter at the debug-meeting, you are a pretty stupid guy who has fallen into a big building hell interpreter.

  • Memory damage!
  • Synchronous issue
  • A message that hurts the terminal by tracking Python's memory
  • devil

If you think, this code works with Si and C ++ without a task.

You can kiss me at the fifth point

You heard what I said

You can kiss me at the fifth point

OK, hold up, what’s happening?

While writing this article in the crow, he hesitated, for example, hesitant (I wanted to publish last weekend). I hit the task of memory impairment. I thought, "Hey, for example, I would probably ruin and use the STD thread API C + + a while ago."

I didn't fail, and the code above C+was right. Similarly, if you do something that suppresses the 500 state of the Python interpreter in the error detection mode, there are some hidden difficulties due to memory damage. The crow failed to associate this with a specific task. "At one point later, at one point, one of the 10 or less Pythreadstate was formed and destroyed," did not count.

If you think about it, you can see later that in the case of memory playback, the destroyed PythReadstate is not actually completely reset, leading to Python's description of memory deletion. In essence, we violate some of the Python C code to suppress this PythReadState as we did. It doesn't take into account whether this is a security job, but this is definitely a decision, including whether or not I found that the cause of the fuckin g-gender 😅. I gathered a lot of information that I could collect and sent the case to Python's GitHub repository.

The Dream Of The Child

And in 2024, what I was expecting as a child (Gil) and dreamed of it is finally approaching. I keep in mind that the current situation of the python field is not all bad, but for example, it works for a lon g-running language, so you are actually important. The problem is that you cannot surprise you on this path.

For example, the only thing that really depresses me is that the best conclusion in our time is actually the introduction of Python for built-in-language scenarios (not called Lua). From the implementation of JS, this one as V8, autonomous deployment possibility to . NET-core, wasme-CPU implementation-emulator, library, this one. Including other Python VM implementations, something like Pocketpy seems to be the best variation.

  1. Yes, I studied with Michael Dawson. ︎
  2. No, I don't think that JIT or sweeping bytecode with native challenge was a similar task. This is a scripting language. You knew it was actually running. If you needed speed or JIT, you would have used a rush language until the V8 engine caught your attention. ︎
  3. If you're not the first to see my website, did you really really think I wouldn't mention CMake? Seriously? ︎
  4. And what if "trust of trust" were to extend to all APIs you deal with ᙂ ︎.

avatar-logo

Elim Poon - Journalist, Creative Writer

Last modified: 27.08.2024

I care a great deal about scalable development from 1 person to people. This site acts as both a programming focused blog, as well as a personal journal. Hello. I have a new blog post up, where I create "" Python Interpreters (featuring the bug I ran into that delayed this post going live). Izzy, Cubby, Skully, Bucky, Marina the Mermaid, Captain Hook, Mr. Smee ( Nieces & Nephews), Wooster, Jagular, Long John Cottontail, Dexter.

Play for real with EXCLUSIVE BONUSES
Play
enaccepted