Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Speedup tests by caching HomeServerConfig instances
Browse files Browse the repository at this point in the history
These two lines:

```
config_obj = HomeServerConfig()
config_obj.parse_config_dict(config, "", "")
```

are called many times with the exact same value for `config`.

As the test suite is CPU-bound and non-negligeably time is spent in
`parse_config_dict`, this saves ~5% on the overall runtime of the Trial
test suite (tested with both `-j2` and `-j12` on a 12t CPU).

This is sadly rather limited, as the cache cannot be shared between
processes (it contains at least jinja2.Template and RLock objects which
aren't pickleable), and Trial tends to run close tests in different
processes.
  • Loading branch information
progval committed Mar 19, 2023
1 parent 3d70cc3 commit 2202072
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.d/15283.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speedup tests by caching HomeServerConfig instances
69 changes: 67 additions & 2 deletions tests/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import gc
import hashlib
import hmac
import json
import logging
import secrets
import time
Expand All @@ -28,12 +29,14 @@
Generic,
Iterable,
List,
Literal,
NoReturn,
Optional,
Tuple,
Type,
TypeVar,
Union,
overload,
)
from unittest.mock import Mock, patch

Expand All @@ -53,6 +56,7 @@
from synapse import events
from synapse.api.constants import EventTypes
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.config._base import Config, RootConfig
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import DEFAULT_ROOM_VERSION
from synapse.crypto.event_signing import add_hashes_and_signatures
Expand Down Expand Up @@ -124,6 +128,68 @@ def new(*args: P.args, **kwargs: P.kwargs) -> R:
return _around


@overload
def deepcopy_config(config: RootConfig, root: Literal[True]) -> RootConfig:
...


@overload
def deepcopy_config(config: Config, root: Literal[False]) -> Config:
...


def deepcopy_config(config, root):
if root:
new_config = config.__class__(config.config_files)
else:
new_config = config.__class__(config.root)

for attr_name in config.__dict__:
if attr_name.startswith("__") or attr_name == "root":
continue
attr = getattr(config, attr_name)
if isinstance(attr, Config):
new_attr = deepcopy_config(attr, root=False)
else:
new_attr = attr

setattr(new_config, attr_name, new_attr)

return new_config


_make_homeserver_config_obj_cache: Dict[str, Union[RootConfig, Config]] = {}


def make_homeserver_config_obj(config: Dict[str, Any]) -> RootConfig:
"""Creates a :class:`HomeServerConfig` instance with the given configuration dict.
This is equivalent to::
config_obj = HomeServerConfig()
config_obj.parse_config_dict(config, "", "")
but it keeps a cache of `HomeServerConfig` instances and deepcopies them as needed,
to avoid validating the whole configuration every time.
"""
cache_key = json.dumps(config)

if cache_key in _make_homeserver_config_obj_cache:
# Cache hit: reuse the existing instance
config_obj = _make_homeserver_config_obj_cache[cache_key]
else:
# Cache miss; create the actual instance
config_obj = HomeServerConfig()
config_obj.parse_config_dict(config, "", "")

# Add to the cache
_make_homeserver_config_obj_cache[cache_key] = config_obj

assert isinstance(config_obj, RootConfig)

return deepcopy_config(config_obj, root=True)


class TestCase(unittest.TestCase):
"""A subclass of twisted.trial's TestCase which looks for 'loglevel'
attributes on both itself and its individual test methods, to override the
Expand Down Expand Up @@ -518,8 +584,7 @@ def setup_test_homeserver(self, *args: Any, **kwargs: Any) -> HomeServer:
config = kwargs["config"]

# Parse the config from a config dict into a HomeServerConfig
config_obj = HomeServerConfig()
config_obj.parse_config_dict(config, "", "")
config_obj = make_homeserver_config_obj(config)
kwargs["config"] = config_obj

async def run_bg_updates() -> None:
Expand Down

0 comments on commit 2202072

Please sign in to comment.