Skip to content
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

with statement in loop takes cyclically increasing time #2602

Closed
dhalbert opened this issue Feb 7, 2020 · 4 comments · Fixed by #2614
Closed

with statement in loop takes cyclically increasing time #2602

dhalbert opened this issue Feb 7, 2020 · 4 comments · Fixed by #2614
Milestone

Comments

@dhalbert
Copy link
Collaborator

dhalbert commented Feb 7, 2020

import board, digitalio
class WithTest:
    def __init__(self):
        pass
    def __enter__(self):
        pass
    def __exit__(self, *exc):
        pass

d13 = digitalio.DigitalInOut(board.D13)
d13.switch_to_output()

# CYCLICALLY INCREASING INTERVALS
wt = WithTest()
while True:
    with wt as wtx:
        d13.value = not d13.value

#OK: CONSTANT INTERVALS
while True:
    with WithTest() as wt:
        d13.value = not d13.value

Putting a scope on D13 will show a square wave whose period gets longer and longer and then snaps back and starts over. On an M0 the period is increasing from very roughly 0.5 msec to 5 msec (eyeballing it on the scope).

The upper loop shows the problem. The lower loop (comment out the upper one) shows constant intervals.

This was originally discovered by @ladyada when monitoring an I2C transaction that used adafruit_bus_device.I2CDevice. But it has nothing to do with I2C.

This is easiest to see on an oscilloscope:
https://youtu.be/tGB2uVwig_4 (unlisted; use the link)

Screen shots from Saleae:
first loop (taken at gap: starts fast after a gap and slows down):
Selection_129

second loop (constant before and after gap):
Selection_130

Also filed as micropython#5622.

@DavePutz
Copy link
Collaborator

Is this possibly related to memory management? I added a call to print gc_free_mem() inside the loop and see a steadily decreasing amount of free memory - 32 bytes less each iteration. When it reached almost no free memory it was all freed (due to garbage collecting, which would use time?). Adding a call to gc.collect() inside the loop resolved this; but I don't have a scope to see if this actually affected the signal timing.

@tannewt
Copy link
Member

tannewt commented Feb 11, 2020

@DavePutz Ya, it is related. See micropython#5622 for a longer discussion.

The short version is that the *kwargs in the exit of the with causes a dictionary allocation that is two blocks (32 bytes) long. MicroPython (and therefore CircuitPython) optimizes searching for free memory blocks by storing a start location after a successful allocation. However, this location is only updated if the allocation was 1 block long. In this case, it is two blocks which means that the search always starts from the beginning.

@dhalbert
Copy link
Collaborator Author

However, this location is only updated if the allocation was 1 block long. In this case, it is two blocks which means that the search always starts from the beginning.

So is that a bug?

@tannewt
Copy link
Member

tannewt commented Feb 11, 2020

I don't think it is a bug because if you update it on a larger allocation you'll ignore smaller holes the next time. We could, however, track a few start locations based on the allocation size say 1-4 to speed up the smallest, and (likely) most common, allocations while falling back to the largest tracked location if the allocation is above that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants