[SOLVED] Unloading modules frees RAM only when unloading root package

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

[SOLVED] Unloading modules frees RAM only when unloading root package

Post by kevinkk525 » Fri Sep 21, 2018 8:19 pm

I was testing unloading modules to free up RAM that is allocated by functions I only need once. Then I realized that unloading modules behaves very weird and therefore does not really work in a normal environment.

Expected behaviour:

Code: Select all

from some import module1
from some import module2
del module1
del sys.module["module1"]
This should free up the RAM of module1 while some.module2 is still loaded. But "module1" does not exist in sys.modules, only "some.module1" does but even deleting this does not free the RAM.

Actual behaviour:
RAM is freed once root package "some" is removed from sys.modules.
If you choose an example with a deeper structure like in the real example below, it gets even more confusing as unloading a module does nothing as long as higher package trees are still present. All used higher packages are just empty "__init__.py" files.

Code: Select all

>>> import gc
>>> import sys
>>> sys.modules
{'main': <module 'main'>}
>>> gc.collect();gc.mem_free()
35568
>>> from pysmartnode.networking import mqtt_receive_config
>>> gc.collect();gc.mem_free()
18112
>>> from pysmartnode.utils.subscriptionHandlers import subscription
>>> gc.collect();gc.mem_free()
17024
>>> sys.modules
{'pysmartnode.networking': <module 'pysmartnode.networking'>, 'pysmartnode.utils.subscriptionHandlers.subscription': <module 'pysmartnode.utils.subscriptionHandlers.subscription'>, 'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'pysmartnode.networking.mqtt_receive_config': <module 'pysmartnode.networking.mqtt_receive_config'>}
>>> del subscription
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.subscription"]
>>> gc.collect();gc.mem_free()
16960
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers"]
>>> del sys.modules["pysmartnode.utils"]
>>> gc.collect();gc.mem_free()
16976
>>> del sys.modules["pysmartnode"]
>>> gc.collect();gc.mem_free()
17952
>>> sys.modules
{'pysmartnode.networking': <module 'pysmartnode.networking'>, 'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>, 'pysmartnode.networking.mqtt_receive_config': <module 'pysmartnode.networking.mqtt_receive_config'>}
>>> gc.collect();gc.mem_free()
18016
>>> del mqtt_receive_config
>>> del sys.modules["pysmartnode.networking.mqtt_receive_config"]
>>> gc.collect();gc.mem_free()
18032
>>> del sys.modules["pysmartnode.networking"]
>>> gc.collect();gc.mem_free()
18400
>>> sys.modules
{'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>}
Like this it works:

Code: Select all

import some.module1
del some.module1
del sys.modules["some.module1"]

Code: Select all

>>> gc.collect();gc.mem_free()
22448
>>> import pysmartnode.networking.mqtt_receive_config
>>> gc.collect();gc.mem_free()
18160
>>> import pysmartnode.utils.subscriptionHandlers.subscription
>>> gc.collect();gc.mem_free()
17088
>>> sys.modules
{'pysmartnode.networking': <module 'pysmartnode.networking'>, 'pysmartnode.utils.subscriptionHandlers.subscription': <module 'pysmartnode.utils.subscriptionHandlers.subscription'>, 'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'pysmartnode.networking.mqtt_receive_config': <module 'pysmartnode.networking.mqtt_receive_config'>}
>>> gc.collect();gc.mem_free()
17040
>>> del pysmartnode.networking.mqtt_receive_config
>>> del sys.modules["pysmartnode.networking.mqtt_receive_config"]
>>> gc.collect();gc.mem_free()
17280
>>> sys.modules
{'pysmartnode.networking': <module 'pysmartnode.networking'>, 'pysmartnode.utils.subscriptionHandlers.subscription': <module 'pysmartnode.utils.subscriptionHandlers.subscription'>, 'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>}
>>> gc.collect();gc.mem_free()
17328
>>> del pysmartnode.utils.subscriptionHandlers.subscription
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.subscription"]
>>> gc.collect();gc.mem_free()
17920
>>> sys.modules
{'pysmartnode.networking': <module 'pysmartnode.networking'>, 'uasyncio.core': <module 'uasyncio.core'>, 'uasyncio': <module 'uasyncio'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>}
It now frees up the RAM when deleting the imported module and not when deleting the parent packages. As you can see the parent packages are still in sys.modules. Unloading these would give only a few Bytes back, just as it should be. In a bigger project you quickly get a big tree of modules and should be able to unload a certain submodule imported like "from some.package import module" without having to unload the parent packages "some.package" and "some", which is not feasible in a project where a lot of modules share the same root packages.
Last edited by kevinkk525 on Mon Sep 24, 2018 9:35 pm, edited 2 times in total.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

jickster
Posts: 629
Joined: Thu Sep 07, 2017 8:57 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by jickster » Fri Sep 21, 2018 9:18 pm

This is already being discussed

viewtopic.php?f=2&t=2639&start=20#p30327

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by kevinkk525 » Sat Sep 22, 2018 7:03 am

It's not the same as the thread you linked to and that thread is a mess.

Here I'm talking about the difference of "from some import module" and "import some.module", which should behave the same way but don't.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by pythoncoder » Sat Sep 22, 2018 7:25 am

I think the unload trick is a bit more involved with Python packages. Consider this session on a Pyboard (uasyncio is frozen as bytecode).

Code: Select all

>>> import sys, gc
>>> gc.collect();gc.mem_free()
101504
>>> import uasyncio
>>> gc.collect();gc.mem_free()
97296
>>> del uasyncio
>>> del sys.modules['uasyncio']
>>> gc.collect();gc.mem_free()
97216
>>> 
RAM was recovered by

Code: Select all

>>> sys.modules
{'uasyncio.core': <module 'uasyncio.core'>}
>>> del uasyncio.core
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'uasyncio' is not defined
>>> del sys.modules['uasyncio.core']
>>> gc.collect();gc.mem_free()
101264
>>> 
So maybe you need to examine sys.modules to establish any lingering references which are no longer needed.
Peter Hinch
Index to my micropython libraries.

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by kevinkk525 » Sat Sep 22, 2018 7:31 am

You are right, should have added the sys.modules output. pysmartnode.networking.mqtt_receive_config imports only uasyncio, time, json.
uasyncio stays in sys.modules after unloading. All modules are frozen bytecode.
Still unloading a module imported like "from some import module" does not release the RAM while unloading a module imported like "import some.module" does release the RAM of the module itself.
This is quite strange in my opinion as the difference here should just be "syntactic sugar" as they should behave the same. Just by changing the way I import modules that I later unload in my project, made a difference of 1.5kB already.

I updated the examples in the first post with the sys.modules output. As you can see in variant (1) ("from some.package import module") the RAM only gets freed once all parent packages "some.package" and "some" are unloaded from sys.modules. In variant (2) ("import some.package.module") the RAM gets freed on unloading that module, even though the parent packages "some.package" and "some" remain in sys.modules. Unloding these will only get a few Bytes back as they are empty packages (empty __init__.py).

In a bigger project you quickly get a big tree of modules and should be able to unload a certain submodule imported like "from some.package import module" without having to unload the parent packages "some.package" and "some", which is not feasible in a project where a lot of modules share the same root packages.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by pythoncoder » Sun Sep 23, 2018 8:16 am

Interesting, but beyond my experience. My own (arguably antediluvian) attitude is that Python packages are a complication too far in a microcontroller environment, so I never use them in my own code.
Peter Hinch
Index to my micropython libraries.

jickster
Posts: 629
Joined: Thu Sep 07, 2017 8:57 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by jickster » Sun Sep 23, 2018 2:32 pm

It would be easier to solve your problem if you reduce your code to the minimum thing that still exhibits the bad behavior.

I know to the OP the code is familiar but to us we don’t know what’s fluff and what’s relevant hence MCVE.

MCVE - minimal complete verifiable example

Also when you a sequence of REPL commands, do:

gc.collect(); gc.mem_free(); dir(); sys.modules;

Sometimes you leave out one of the last 2.




Sent from my iPhone using Tapatalk Pro

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by kevinkk525 » Sun Sep 23, 2018 4:51 pm

Allright, here's a MCVE:

I'm using the function you suggested in the module pysmartnode.utils.subscriptionHandlers.test:

Code: Select all

def b():
b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b();b(); return 0
All packages have empty "__init__.py" files.

Method 1, importing like "from some.package import module":

Code: Select all

>>> import gc
>>> import sys
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
35536
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>}
>>> from pysmartnode.utils.subscriptionHandlers import test
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21872
['uos', '__name__', 'main', 'gc', 'sys', 'test']
{'pysmartnode.utils.subscriptionHandlers.test': <module 'pysmartnode.utils.subscriptionHandlers.test'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del test
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.test"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21712
['uos', '__name__', 'main', 'gc', 'sys']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21616
['uos', '__name__', 'main', 'gc', 'sys']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21632
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21984
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>}
RAM gets released only after unloading the root package "pysmartnode" instead of when unloading the module "test".

Method 2, import like "import some.package.module":

Code: Select all

>>> import gc
>>> import sys
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
35536
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>}
>>> import pysmartnode.utils.subscriptionHandlers.test
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21872
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils.subscriptionHandlers.test': <module 'pysmartnode.utils.subscriptionHandlers.test'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del pysmartnode.utils.subscriptionHandlers.test
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.test"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21744
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21744
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21648
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21664
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21680
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'main': <module 'main'>}
Here's where it gets weird. Method 2, which worked with a different module, now does NOT release RAM at all and it's impossible to unload the root package "pysmartnode".


Second test using a real module that has some useful code in it like this, which does not have external dependencies:

Code: Select all

class _subscription:
    def __init__(self, values):
        self.values = values
        self.next = None

class SubscriptionHandler:
    def __init__(self, len_structure=1):
        """
        len_structure: number of attributes to be saved separately.
        identifier does not count to length of structure.
        """
        self.ifirst = None
        self.__values = len_structure + 1

    def get(self, identifier, index):
        if index > self.__values:
            raise IndexError("Index greater than object tuple length")
        if type(identifier) == _subscription:
            obj = identifier
        else:
            obj = self.__getObject(identifier)
        if obj is not None:
            return obj.values[index]
        raise IndexError("Object {!s} does not exist".format(identifier))

    def set(self, identifier, index, value, extend=False):
        if index > self.__values:
            raise IndexError("Index greater than object tuple length")
        if type(identifier) == _subscription:
            obj = identifier
        else:
            obj = self.__getObject(identifier)
        if obj is not None:
            if extend and type(obj.values[index] == list):
                obj.values[index].append(value)
            elif extend:
                raise ValueError("Can only extend a list")
            else:
                values = list(obj.values)
                values[index] = value
                obj.values = tuple(values)
        else:
            raise IndexError("Object {!s} does not exist".format(identifier))

    def getFunctions(self, identifier):
        return self.get(identifier, 1)

    def setFunctions(self, identifier, value):
        return self.set(identifier, 1, value)

    @staticmethod
    def matchesSubscription(topic, subscription):
        if topic == subscription:
            return True
        if subscription.endswith("/#"):
            lens = len(subscription)
            if topic[:lens - 2] == subscription[:-2]:
                if len(topic) == lens - 2 or topic[lens - 2] == "/":
                    # check if identifier matches subscription or has sublevel
                    # (home/test/# does not listen to home/testing)
                    return True
        return False

    def __getObject(self, identifier, get=True):
        iObject = self.ifirst
        while iObject is not None:
            obj_val = iObject.values[0]
            if obj_val == identifier:
                return iObject
            elif get and obj_val.endswith("/#"):
                # check if identifier is found in subscription
                if identifier[:len(obj_val) - 2] == obj_val[:-2]:
                    if len(identifier) == len(obj_val) - 2 or \
                            identifier[len(obj_val) - 2] == "/":
                        # check if identifier matches subscription or has sublevel
                        # (home/test/# does not listen to home/testing)
                        return iObject
            iObject = iObject.next
        return None

    def addObject(self, identifier, *args):
        if len(args) + 1 > self.__values:
            raise IndexError("More arguements than structure allows")
        obj = self.__getObject(identifier, get=False)
        if obj is None:
            attribs = (identifier,) + args
            iObject = self.ifirst
            if iObject is None:
                self.ifirst = _subscription(attribs)
                return
            while iObject.next is not None:
                iObject = iObject.next
            iObject.next = _subscription(attribs)
        else:
            # raise IndexError("Object with identifier already exists")
            self._set(obj, (identifier,) + args)

    def _set(self, obj, values):
        values_obj = list(obj.values or [None] * self.__values)
        for i in range(1, len(values)):
            if values_obj[i] is None:
                values_obj[i] = values[i]
            elif type(values_obj[i]) != list:
                values_obj[i] = [values_obj[i]]
                values_obj[i].append(values[i])
            else:
                values_obj[i].append(values[i])
        obj.values = values_obj

    def removeObject(self, identifier):
        obj = self.__getObject(identifier, get=False)
        if obj == self.ifirst:
            self.ifirst = None
            del obj
            return
        if obj is not None:
            iObject = self.ifirst
            while iObject.next != obj:
                iObject = iObject.next
            iObject.next = obj.next
            del obj

    def print(self):
        for obj in self:
            print(obj)

    def __iter__(self, with_path=False):
        # with_path only for compatibility to tree
        obj = self.ifirst
        while obj is not None:
            if with_path:
                yield obj, obj.values[0]
            else:
                yield obj
            obj = obj.next
Method 1, importing like "from some.package import module", looks exactly like with the function before:

Code: Select all

>>> import gc
>>> import sys
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
35536
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>}
>>> from pysmartnode.utils.subscriptionHandlers import test
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21232
['uos', '__name__', 'main', 'gc', 'sys', 'test']
{'pysmartnode.utils.subscriptionHandlers.test': <module 'pysmartnode.utils.subscriptionHandlers.test'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del test
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.test"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21072
['uos', '__name__', 'main', 'gc', 'sys']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
20944
['uos', '__name__', 'main', 'gc', 'sys']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
20960
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21968
['uos', '__name__', 'main', 'gc', 'sys']
{'main': <module 'main'>}
Method 2, importing like "import some.package.module":

Code: Select all

>>> import sys
>>> import gc
>>> import pysmartnode.utils.subscriptionHandlers.test
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21328
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils.subscriptionHandlers.test': <module 'pysmartnode.utils.subscriptionHandlers.test'>, 'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del pysmartnode.utils.subscriptionHandlers.test
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers.test"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21824
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode.utils.subscriptionHandlers': <module 'pysmartnode.utils.subscriptionHandlers'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils.subscriptionHandlers"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21712
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'pysmartnode.utils': <module 'pysmartnode.utils'>, 'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode.utils"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21680
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'main': <module 'main'>, 'pysmartnode': <module 'pysmartnode'>}
>>> del sys.modules["pysmartnode"]
>>> gc.collect(); gc.mem_free(); dir(); sys.modules;
21696
['uos', '__name__', 'main', 'gc', 'sys', 'pysmartnode']
{'main': <module 'main'>}
In this case with a proper module freeing RAM works as described in the first post. Therefore I'd consider this the MCVE, although you could use another module.
Last edited by kevinkk525 on Mon Sep 24, 2018 7:52 am, edited 1 time in total.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

jickster
Posts: 629
Joined: Thu Sep 07, 2017 8:57 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by jickster » Mon Sep 24, 2018 2:47 am

Give me the latest .py files you’ve used for this thread.

I want to experiment myself. I think it’ll go faster that way.


Sent from my iPhone using Tapatalk Pro

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: [BUG?] Unloading modules frees RAM only when unloading root package

Post by kevinkk525 » Mon Sep 24, 2018 7:39 am

Allright, here you go:

https://raw.githubusercontent.com/kevin ... rs/test.py

That's the file I used for the 2nd test (the 1st was with your function inside this file). You have to create the structure yourself, or clone the repository dev branch.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

Post Reply