> On Jan 24, 2018, at 17:42, Sergiy Lozovsky <
sergiy....@gmail.com> wrote:
>
>
> With looping 3 times:
>
> $ python test10.py
> 129015808.0
> memdiff: 4546560.0
>
> Looping 3000 times:
>
> $ python test10.py
> 129011712.0
> memdiff: 11939840.0
>
> 5 seconds is more than enough for all greenlets to end, but memdiff grows with the number of greenlets.
I believe what you're seeing is simply the internals of CPython getting warmed up. Things like the tuple and list and frame object freelists are getting populated, the integer object cache being populated, things like that. Not to mention the C standard library data, shared objects being mapped in (and copied to physical memory if written to), things like that. If that's true, then eventually the process will reach a steady state. One iteration of the outer loop won't account for that.
> There is a question of calculating the memory used. Code in the test is probably not perfect (Linux process doesn't return memory to the system). Are there better ways to find if all greenlet memory is freed after it is ended and there are no references to it?
I recommend the portable `psutil` and `objgraph` libraries, the former for operating system level stats, the later for Python stats.
I wrote a script using them to examine this behaviour in more depth. It's down below, but I want to go over the output first. The script collects and prints memory and object snapshots before doing *anything*, after spawning 3000 Greenlets, and then after sleeping to let those greenlets run. It does this repeatedly. Every 10 runs, it shows the difference since the script was started.
Here's the first iteration on macOS 10.13.2 with CPython 3.6.4:
>> ============================
>> Beginning iteration 0
>> ===============
>> Before spawn
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.00390625
>> Delta VMS 0.0
>> ===============
>> Before sleep
>> Num greenlets 1
>> Num Greenlets 3000
>> Delta RSS 1.7578125
>> Delta VMS 9.5
>> ===============
>> After sleep
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.015625
>> Delta VMS 7.75
>> ============================
>>
>> ===============
>> Since beginning at 0
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 2.0546875
>> Delta VMS 17.25
Before we sleep you can see that we've added 3000 Greenlet objects, exactly as expected. The RSS (non-swapped physical memory) has gone up by 1.75MB, and the total virtual memory (VMS) has gone up by 9.5MB. That's more or less expected too. After we sleep and the greenlets have exited, we can see that the are no longer found as Python objects, and yet our VMS has increased more. That's likely the result of mapping in the code needed to execute the greenlets. In total, we've added 2.05MB of physical memory and 17MB of virtual address space since the process started.
The second, third and fourth iterations show small increases in RSS and VMS, but by the fifth iteration, we've reached steady state:
>> ============================
>> Beginning iteration 4
>> ===============
>> Before spawn
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ===============
>> Before sleep
>> Num greenlets 1
>> Num Greenlets 3000
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ===============
>> After sleep
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ============================
The final report shows us very similar to where we were after our first iteration (after accounting for the small increases in the next few iterations):
>> ===============
>> Final report
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 3.38671875
>> Delta VMS 17.5
So if greenlets leak, it's an immeasurably small one :)
YMMV, of course, and your numbers won't match mine and they may vary substantially between runs on one machine (for example, a separate run of the test reported only 1.8 and 1.25 MB increases in RSS and VMS, respectively, after the first iteration), but I think the overall trend to steady state should be the same.
(On Ubuntu 16.04 with CPython 3.5.2 steady state looks different; the deltas added are exactly offset by the deltas removed:
>> ============================
>> Beginning iteration 40
>> ===============
>> Before spawn
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ===============
>> Before sleep
>> Num greenlets 1
>> Num Greenlets 3000
>> Delta RSS 0.51171875
>> Delta VMS 0.65234375
>> ===============
>> After sleep
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS -0.51171875
>> Delta VMS -0.65234375
>> ============================
)
There are occasionally things I cannot explain. For example, in a separate run of the test, I saw a large increase in VMS at iteration 15:
>> ============================
>> Beginning iteration 15
>> ===============
>> Before spawn
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ===============
>> Before sleep
>> Num greenlets 1
>> Num Greenlets 3000
>> Delta RSS 0.26953125
>> Delta VMS 9.0
>> ===============
>> After sleep
>> Num greenlets 1
>> Num Greenlets 0
>> Delta RSS 0.0
>> Delta VMS 0.0
>> ============================
I've no idea what happened there. Maybe a Python process in the background exited and we'd been sharing mappings with it, and now that they were no longer shared they were accounted for here? No clue.
Jason
PS: The test script:
>> import objgraph
>> import psutil
>> import gevent
>>
>> # Warm up the hub. We
>> # should now have one greenlet
>> gevent.sleep(1)
>>
>> proc = psutil.Process()
>>
>> def f(x):
>> return x + 1
>>
>> def report(title, mem_before):
>> mem_now = proc.memory_info()
>>
>> print("===============")
>> print(title)
>> print("Num greenlets", objgraph.count('greenlet'))
>> print("Num Greenlets", objgraph.count('Greenlet'))
>> print("Delta RSS ", (mem_now.rss - mem_before.rss) / 1024 / 1024)
>> print("Delta VMS ", (mem_now.vms - mem_before.vms) / 1024 / 1024)
>> return mem_now
>>
>> def spawn(iter_number, mem_before):
>> print("============================")
>> print("Beginning iteration %s" % (iter_number))
>> mem_now = report("Before spawn", mem_before)
>>
>> for i in range(3000):
>> gevent.spawn(f, i)
>>
>> mem_after_spawn = report("Before sleep", mem_now)
>> gevent.sleep(3)
>> mem_after_sleep = report("After sleep", mem_after_spawn)
>> print("============================")
>> return mem_after_sleep
>>
>> def test():
>> ultimate_mem_before = mem_before = proc.memory_info()
>>
>> for iter_number in range(50):
>> mem_before = spawn(iter_number, mem_before)
>> if iter_number % 10 == 0:
>> print()
>> report("Since beginning at %i" % (iter_number), ultimate_mem_before)
>> print()
>>
>> print()
>> report("Final report", ultimate_mem_before)
>>
>>
>> if __name__ == '__main__':
>> test()