Repository: Project-MONAI/MONAI

Issue:
`ConfigParser` doesn't need `deepcopy`
**Is your feature request related to a problem? Please describe.**
In the current implementation, `ConfigParser` executes `deepcopy` for every config item when recursively parse the whole config:
https://github.com/Project-MONAI/MONAI/blob/8e8dd1bab8d730807ff939a42886c80e84e2ebad/monai/bundle/config_parser.py#L379
But actually, all the parsing logic just read the config item content and generate a new config object:
https://github.com/Project-MONAI/MONAI/blob/8e8dd1bab8d730807ff939a42886c80e84e2ebad/monai/bundle/reference_resolver.py#L295
So I would suggest to remove this `deepcopy` logic, it can bring 3 benefits:
1. If use python object in the config dict, `deepcopy` it may cause `Invertd` failure due to mismatch object ID.
2. If the config structure contains very big python object (like CacheDataset), it will increase memory usage, especially considering it's a recursive operation.
3. Some python objects may not support pickle operation and deepcopy will fail.




Discussion:

Resolving patch:
diff --git a/monai/bundle/config_parser.py b/monai/bundle/config_parser.py
--- a/monai/bundle/config_parser.py
+++ b/monai/bundle/config_parser.py
@@ -345,7 +345,8 @@ def _do_resolve(self, config: Any, id: str = "") -> Any:
             if config.startswith(MACRO_KEY):
                 path, ids = ConfigParser.split_path_id(config[len(MACRO_KEY) :])
                 parser = ConfigParser(config=self.get() if not path else ConfigParser.load_config_file(path))
-                return parser[ids]
+                # deepcopy to ensure the macro replacement is independent config content
+                return deepcopy(parser[ids])
         return config
 
     def resolve_macro_and_relative_ids(self):
@@ -356,7 +357,7 @@ def resolve_macro_and_relative_ids(self):
         ``"%default_net"``, ``"%/data/config.json#net"``.
 
         """
-        self.set(self._do_resolve(config=deepcopy(self.get())))
+        self.set(self._do_resolve(config=self.get()))
 
     def _do_parse(self, config: Any, id: str = "") -> None:
         """
@@ -375,14 +376,12 @@ def _do_parse(self, config: Any, id: str = "") -> None:
                 sub_id = f"{id}{ID_SEP_KEY}{k}" if id != "" else k
                 self._do_parse(config=v, id=sub_id)
 
-        # copy every config item to make them independent and add them to the resolver
-        item_conf = deepcopy(config)
-        if ConfigComponent.is_instantiable(item_conf):
-            self.ref_resolver.add_item(ConfigComponent(config=item_conf, id=id, locator=self.locator))
-        elif ConfigExpression.is_expression(item_conf):
-            self.ref_resolver.add_item(ConfigExpression(config=item_conf, id=id, globals=self.globals))
+        if ConfigComponent.is_instantiable(config):
+            self.ref_resolver.add_item(ConfigComponent(config=config, id=id, locator=self.locator))
+        elif ConfigExpression.is_expression(config):
+            self.ref_resolver.add_item(ConfigExpression(config=config, id=id, globals=self.globals))
         else:
-            self.ref_resolver.add_item(ConfigItem(config=item_conf, id=id))
+            self.ref_resolver.add_item(ConfigItem(config=config, id=id))
 
     @classmethod
     def load_config_file(cls, filepath: PathLike, **kwargs: Any) -> dict:


Test patch: