-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
Memory keeps increasing with fixed-size dict during multi-threaded set/delete in 3.13.3t #133136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This bug can be reproduced on my Linux environment. I think may need some time to figure it out |
For now, I think this may related with the gc timing import random
import time
from threading import Lock, Thread
import resource
import gc
class Cache:
def __init__(self, cap):
self.m = {}
self.mu = Lock()
self.cap = cap
def get(self, key):
with self.mu:
try:
v = self.m[key]
except KeyError:
return 0, False
return v, True
def set(self, key, value):
with self.mu:
self.m[key] = value
if len(self.m) > self.cap:
self.m.popitem()
def set_key(cache):
c = 0
while True:
key = random.randint(0, 1000000000)
cache.set(key, key)
c += 1
if c == 100000:
c = 0
print(".", end="", flush=True)
def run():
cache = Cache(500000)
for _ in range(4):
thread = Thread(target=set_key, args=[cache])
thread.start()
while True:
time.sleep(1)
with cache.mu:
usage = resource.getrusage(resource.RUSAGE_SELF)
rss_mb = usage.ru_maxrss
print(f"Process RSS: {rss_mb:.2f} KB")
# gc.collect()
if __name__ == "__main__":
run() if we run the gc.collect manually, the memory size will keep normal. Otherwise, the memory size will increase in 40MB in regular peroid |
It seems to run normally in v3.13.0a3. After 10-minute loops, the memory size remains at less than 200MB. This is a good signal. I think I can bisect for the bad commit. |
@Zheaoli Thanks for investigating the issue. I tested by manually triggering GC every 10 seconds and every 1 second. For the 10-second interval, the RSS stabilized around |
Got it. I think this bug is introduced into the codebase in #116336 But I don't have enough idea about how to fix this. Would you mind taking a little bit of help? @colesbury |
@colesbury PTAL when you got time |
I think this issue is still related with the GC. I count the |
This seems like a reasonable explanation for the issue. Hopefully, @colesbury can take a look when he has time, or if he's too busy, perhaps someone else familiar with free-threading could also help investigate. |
I think we need better heuristics for the QSBR here. The repeated insertions and We "buffer" QSBR frees in two ways:
Line 1203 in 1ffe913
Lines 1208 to 1210 in 1ffe913
In addition to periodic processing every 10 or 254 calls, I think we probably want to take into account the amount of memory that the QSBR subsystem is holding. |
The free threading build uses QSBR to delay the freeing of dictionary keys and list arrays when the objects are accessed by multiple threads in order to allow concurrent reads to proceeed with holding the object lock. The requests are processed in batches to reduce execution overhead, but for large memory blocks this can lead to excess memory usage. Take into account the size of the memory block when deciding when to process QSBR requests.
Uh oh!
There was an error while loading. Please reload this page.
Bug report
Bug description:
Platform: macOS-14.6.1-x86_64-i386-64bit-Mach-O
Python 3.13.3t
Python 3.13.3
CPython versions tested on:
3.13
Operating systems tested on:
macOS
Linked PRs
The text was updated successfully, but these errors were encountered: