6 ms·
Spawning processes generally takes much less than 1 ms on Unix Spawning a PYTHON interpreter process might take 30 ms to 300 ms before you get to main(), depen
by chubot 1y ago
Spawning processes generally takes much less than 1 ms on Unix
Spawning a PYTHON interpreter process might take 30 ms to 300 ms before you get to main(), depending on the number of imports
It's 1 to 2 orders of magnitude difference, so it's worth being precise
This is a fallacy with say CGI. A CGI in C, Rust, or Go works perfectly well.
e.g. sqlite.org runs with a process PER REQUEST - https://news.ycombinator.com/item?id=3036124 https://news.ycombinator.com/item?id=3036124
- Sharlin 1y agoUnix is not the only platform though (and is process creation fast on all Unices or just Linux?) The point about interpreter init overhead is, of course, apt.
- btilly 1y agoProcess creation should be fast on all Unices. If it isn't, then the lowly shell script (heavily used in Unix) is going to perform very poorly.
- kragen 1y agoWhile I think you've been using Unix longer than I have, shell scripts are known for performing very poorly, and on PDP-11 Unix (where perhaps shell scripts were most heavily used, since Perl didn't exist yet) fork() couldn't even do copy-on-write; it had to literally copy the process's entire data segment, which in most cases also contained a copy of its code. Moving to paged machines like the VAX and especially the 68000 family made it possible to use copy-on-write, but historically speaking, Linux has often been an order of magnitude faster than most other Unices at fork(). However, I think people mostly don't use those Unices anymore. I imagine the BSDs have pretty much caught up by now. https://news.ycombinator.com/item?id=44009754 https://news.ycombinator.com/item?id=44009754 gives some concrete details on fork() speed on current Linux: 50μs for a small process, 700μs for a regular process, 1300μs for a venti Python interpreter process, 30000–50000μs for Python interpreter creation. This is on a CPU of about 10 billion instructions per second per core, so forking costs on the order of ½–10 million instructions.
- fredoralive 1y agoPython runs on other operating systems, like NT, where AIUI processes are rather more heavyweight. Not all use cases of Python and Windows intersect (how much web server stuff is a Windows / IIS / SQL Server / Python stack? Probably not many, although WISP is a nice acronym), but you’ve still got to bear it in mind for people doing heavy numpy stuff on their work laptop or whatever.
- saagarjha 1y agoYes, this is why using shell scripts on macOS is miserable
- bobmcnamara 1y agoBefore we had MMUs and CoW, it was awful slow
- charleshn 1y ago> Spawning processes generally takes much less than 1 ms on Unix It depends on whether one uses clone, fork, posix_spawn etc. Fork can take a while depending on the size of the address space, number of VMAs etc.
- crackez 1y agoFork on Linux should use copy-on-write vmpages now, so if you fork inside python it should be cheap. If you launch a new Python process from let's say the shell, and it's already in the buffer cache, then you should only have to pay the startup CPU cost of the interpreter, since the IO should be satisfied from buffer cache...
- charleshn 1y ago> Fork on Linux should use copy-on-write vmpages now, so if you fork inside python it should be cheap. No, that's exactly the point I'm making, copying PTEs is not cheap on a large address space, woth many VMAs. You can run a simple python script allocating a large list and see how it affects fork time.
- charleshn 1y agoSee e.g. https://www.alibabacloud.com/blog/async-fork-mitigating-query-latency-spikes-incurred-by-the-fork-based-snapshot-mechanism_600205 https://www.alibabacloud.com/blog/async-fork-mitigating-quer...
- knome 1y agofor glibc and linux, fork just calls clone. as does posix_spawn, using the flag CLONE_VFORK.
- LPisGood 1y agoMy understanding is that spawning a thread takes just a few micro seconds, so whether you’re talking about a process or a Python interpreter process there are still orders of magnitude to be gained.
- kragen 1y agoTo be concrete about this, http://canonical.org/~kragen/sw/dev3/forkovh.c http://canonical.org/~kragen/sw/dev3/forkovh.c took 670μs to fork, exit, and wait on the first laptop I tried it on, but only 130μs compiled with dietlibc instead of glibc, and with glibc on a 2.3 GHz E5-2697 Xeon, it took 130μs compiled with glibc. httpdito http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/server.s (which launches a process per request) seems to take only about 50μs because it's not linked with any C library and therefore only maps 5 pages. Also, that doesn't include the time for exit() because it runs multiple concurrent child processes. On this laptop, a Ryzen 5 3500U running at 2.9GHz, forkovh takes about 330μs built with glibc and about 130–140μs built with dietlibc, and `time python3 -c True` takes about 30000–50000μs. I wrote a Python version of forkovh http://canonical.org/~kragen/sw/dev3/forkovh.py http://canonical.org/~kragen/sw/dev3/forkovh.py and it takes about 1200μs to fork(), _exit(), and wait(). If anyone else wants to clone that repo and test their own machines, I'm interested to hear the results, especially if they aren't in Linux. `make forkovh` will compile the C version. 1200μs is pretty expensive in some contexts but not others. Certainly it's cheaper than spawning a new Python interpreter by more than an order of magnitude.
- kragen 1y agoOn my cellphone forkovh is 700μs and forkovh.py is 3700μs. Qualcomm SDM630. All the forkovh numbers are with 102400 bytes of data.
- jaoane 1y ago>Spawning a PYTHON interpreter process might take 30 ms to 300 ms before you get to main(), depending on the number of imports That's lucky. On constrained systems launching a new interpreter can very well take 10 seconds. Python is ssssslllloooowwwww.
- morningsam 1y ago>Spawning a PYTHON interpreter process might take 30 ms to 300 ms Which is why, at least on Linux, Python's multiprocessing doesn't do that but fork()s the interpreter, which takes low-single-digit ms as well.
- zahlman 1y agoEven when the 'spawn' strategy is used (default on Windows, and can be chosen explicitly on Linux), the overhead can largely be avoided. (Why choose it on Linux? Apparently forking can cause problems if you also use threads.) Python imports can be deferred (`import` is a statement, not a compiler or pre-processor directive), and child processes (regardless of the creation strategy) name the main module as `__mp_main__` rather than `__main__`, allowing the programmer to distinguish. (Being able to distinguish is of course necessary here, to avoid making a fork bomb - since the top-level code runs automatically and `if __name__ == '__main__':` is normally top-level code.) But also keep in mind that cleanup for a Python process also takes time, which is harder to trace. Refs: https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods https://docs.python.org/3/library/multiprocessing.html#conte... https://stackoverflow.com/questions/72497140 https://stackoverflow.com/questions/72497140
- kstrauser 1y agoI really wish Python had a way to annotate things you don't care about cleaning up. I don't know what the API would look like, but I imagine something like: l = list(cleanup=False) for i in range(1_000_000_000): l.append(i) telling the runtime that we don't need to individually GC each of those tiny objects and just let the OS's process model free the whole thing at once. Sure, close TCP connections before you kill the whole thing. I couldn't care less about most objects, though.
- zahlman 1y agoYou'd presumably need to do something involving weakrefs, since it would be really bad if you told Python that the elements can be GCd at all (never mind whether it can be done all at once) but someone else had a reference. Or completely rearchitect the language to have a model of automatic (in the C sense) allocation. I can't see that ever happening.
- ori_b 1y agoAs another example: I run https://shithub.us https://shithub.us with shell scripts, serving a terabyte or so of data monthly (mostly due to AI crawlers that I can't be arsed to block). I'm launching between 15 and 3000 processes per request. While Plan 9 is about 10x faster at spawning processes than Linux, it's telling that 3000 C processes launching in a shell is about as fast as one python interpreter.
- kstrauser 1y agoThe interpreter itself is pretty quick: ᐅ time echo "print('hi'); exit()" | python hi ________________________________________________________ Executed in 21.48 millis fish external usr time 16.35 millis 146.00 micros 16.20 millis sys time 4.49 millis 593.00 micros 3.89 millis
- ori_b 1y agoMy machine is marginally faster for that; I get about 17ms doing that with python, without the print: time echo "exit()" | python3 real 0m0.017s user 0m0.014s sys 0m0.003s That's... still pretty slow. Here's a C program, run 100 times: range=`seq 100` time for i in $range; do ./a.out; done real 0m0.038s user 0m0.024s sys 0m0.016s And finally, for comparison on Plan 9: range=`{seq 2000} time rc -c 'for(s in $range){ ./6.nop }' 0.01u 0.09s 0.16r rc -c for(s in $range){ ./6.nop } the C program used was simply: int main(void) { return 0; } Of course, the more real work you do in the program, the less it matters -- but there's a hell of a lot you can do in the time it takes Python to launch.
- Too 1y agoIn Python, if you are spawning processes or even threads in a tight loop you have already lost. Use ThreadPoolExecutor or ProcessPoolExecutor from concurrent.futures instead. Then startup time becomes no factor.
- seunosewa 1y agoYou can use a pool of interpreter processes. You don't have to spawn one for each request.