> (b) Why limit coroutines? It's just another Python object and has no
operating resources associated with it. Perhaps your definition of
coroutine is different, and you are thinking of OS threads?

This was my primary concern with the proposed PEP. At the moment, it's
rather trivial to create one million coroutines, and the total memory taken
up by each individual coroutine object is very minimal compared to each OS
thread.

There's also a practical use case for having a large number of coroutine
objects, such as for asynchronously:

1) Handling a large number of concurrent clients on a continuously running
web server that receives a significant amount of traffic.
2) Sending a large number of concurrent database transactions to run on a
cluster of database servers.

I don't know that anyone is currently using production code that results in
1 million coroutine objects within the same interpreter at once, but
something like this definitely scales over time. Arbitrarily placing a
limit on the total number of coroutine objects doesn't make sense to me for
that reason.

OS threads on the other hand take significantly more memory. From a recent
(but entirely unrelated) discussion where the memory usage of threads was
brought up, Victor Stinner wrote a program that demonstrated that each OS
thread takes up approximately ~13.2kB on Linux, which I verified on kernel
version 5.3.8. See https://bugs.python.org/msg356596.

For comparison, I just wrote a similar program to compare the memory usage
between 1M threads and 1M coroutines:

```
import asyncio
import threading
import sys
import os

def wait(event):
    event.wait()

class Thread(threading.Thread):
    def __init__(self):
        super().__init__()
        self.stop_event = threading.Event()
        self.started_event = threading.Event()

    def run(self):
        self.started_event.set()
        self.stop_event.wait()

    def stop(self):
        self.stop_event.set()
        self.join()

def display_rss():
    os.system(f"grep ^VmRSS /proc/{os.getpid()}/status")

async def test_mem_coros(count):
    print("Coroutine memory usage before:")
    display_rss()
    coros = tuple(asyncio.sleep(0) for _ in range(count))
    print("Coroutine memory usage after creation:")
    display_rss()
    await asyncio.gather(*coros)
    print("Coroutine memory usage after awaiting:")
    display_rss()

def test_mem_threads(count):
    print("Thread memory usage before:")
    display_rss()
    threads = tuple(Thread() for _ in range(count))
    print("Thread memory usage after creation:")
    display_rss()
    for thread in threads:
        thread.start()
    print("Thread memory usage after starting:")
    for thread in threads:
        thread.run()
    print("Thread memory usage after running:")
    display_rss()
    for thread in threads:
        thread.stop()
    print("Thread memory usage after stopping:")
    display_rss()

if __name__ == '__main__':
    count = 1_000_000
    arg = sys.argv[1]
    if arg == 'threads':
        test_mem_threads(count)
    if arg == 'coros':
        asyncio.run(test_mem_coros(count))

```
Here are the results:

1M coroutine objects:

Coroutine memory usage before:
VmRSS:     14800 kB
Coroutine memory usage after creation:
VmRSS:    651916 kB
Coroutine memory usage after awaiting:
VmRSS:   1289528 kB

1M OS threads:

Thread memory usage before:
VmRSS:     14816 kB
Thread memory usage after creation:
VmRSS:   4604356 kB
Traceback (most recent call last):
  File "temp.py", line 60, in <module>
    test_mem_threads(count)
  File "temp.py", line 44, in test_mem_threads
    thread.start()
  File "/usr/lib/python3.8/threading.py", line 852, in start
    _start_new_thread(self._bootstrap, ())
RuntimeError: can't start new thread

(Python version: 3.8)
(Linux kernel version: 5.13)

As is present in the results above, 1M OS threads can't even be ran at
once, and the memory taken up just to create the 1M threads is ~3.6x more
than it costs to concurrently await the 1M coroutine objects. Based on
that, I think it would be reasonable to place a limit of 1M on the total
number of OS threads. It seems unlikely that a system would be able to
properly handle 1M threads at once anyways, whereas that seems entirely
feasible with 1M coroutine objects. Especially on a high traffic server.

On Mon, Dec 9, 2019 at 12:01 PM Guido van Rossum <gu...@python.org> wrote:

> I want to question two specific limits.
>
> (a) Limiting the number of classes, in order to potentially save space in
> object headers, sounds like a big C API change, and I think it's better to
> lift this idea out of PEP 611 and debate the and cons separately.
>
> (b) Why limit coroutines? It's just another Python object and has no
> operating resources associated with it. Perhaps your definition of
> coroutine is different, and you are thinking of OS threads?
>
> --
> --Guido van Rossum (python.org/~guido)
> *Pronouns: he/him **(why is my pronoun here?)*
> <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-le...@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/CJO36YRFWCTEUUROJVXIQDMWGZBFAD5T/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/WYZHKRGNOT252O3BUTFNDVCIYI6WSBXZ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to