Skip to content

Commit 4a068e1

Browse files
developer: add a configuration for turning lru_caching off
1 parent 5fff43f commit 4a068e1

File tree

1 file changed

+58
-31
lines changed

1 file changed

+58
-31
lines changed

cylc/flow/cycling/iso8601.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,46 @@
1818

1919
import contextlib
2020
from functools import lru_cache
21+
import os
2122
import re
22-
from typing import List, Optional, TYPE_CHECKING, Tuple
23+
from typing import TYPE_CHECKING, List, Optional, Tuple
2324

24-
from metomi.isodatetime.data import Calendar, CALENDAR, Duration
25+
from metomi.isodatetime.data import CALENDAR, Calendar, Duration
2526
from metomi.isodatetime.dumpers import TimePointDumper
26-
from metomi.isodatetime.timezone import (
27-
get_local_time_zone, get_local_time_zone_format, TimeZoneFormatMode)
2827
from metomi.isodatetime.exceptions import IsodatetimeError
2928
from metomi.isodatetime.parsers import ISO8601SyntaxError
30-
from cylc.flow.time_parser import CylcTimeParser
29+
from metomi.isodatetime.timezone import (
30+
TimeZoneFormatMode,
31+
get_local_time_zone,
32+
get_local_time_zone_format,
33+
)
34+
3135
from cylc.flow.cycling import (
32-
PointBase, IntervalBase, SequenceBase, ExclusionBase, cmp
36+
ExclusionBase,
37+
IntervalBase,
38+
PointBase,
39+
SequenceBase,
40+
cmp,
3341
)
3442
from cylc.flow.exceptions import (
3543
CylcConfigError,
3644
IntervalParsingError,
3745
PointParsingError,
3846
SequenceDegenerateError,
39-
WorkflowConfigError
47+
WorkflowConfigError,
4048
)
41-
from cylc.flow.wallclock import get_current_time_string
4249
from cylc.flow.parsec.validate import IllegalValueError
50+
from cylc.flow.time_parser import CylcTimeParser
51+
from cylc.flow.wallclock import get_current_time_string
52+
4353

4454
if TYPE_CHECKING:
4555
from metomi.isodatetime.data import TimePoint
4656
from metomi.isodatetime.parsers import (
47-
DurationParser, TimePointParser, TimeRecurrenceParser)
57+
DurationParser,
58+
TimePointParser,
59+
TimeRecurrenceParser,
60+
)
4861

4962
CYCLER_TYPE_ISO8601 = "iso8601"
5063
CYCLER_TYPE_SORT_KEY_ISO8601 = 1
@@ -57,6 +70,17 @@
5770
"(incompatible with [cylc]cycle point num expanded year digits = %s ?)")
5871

5972

73+
# NOTE: We cache some datetime cycling operations to improve compute
74+
# perforance. For profiling, this can be disabled by setting the environment
75+
# variable CYLC_CYCLER_LRU_CACHE_SIZE=0.
76+
77+
# The number of cycling operations to cache:
78+
_LRU_CACHE_SIZE = int(os.environ.get('CYLC_CYCLER_LRU_CACHE_SIZE', '10000'))
79+
80+
# A smaller cache for use with larger objecs (to reduce memory impact):
81+
_LARGE_LRU_CACHE_SIZE = int(_LRU_CACHE_SIZE / 100) if _LRU_CACHE_SIZE else 0
82+
83+
6084
class WorkflowSpecifics:
6185

6286
"""Store workflow-setup-specific constants and utilities here."""
@@ -123,7 +147,7 @@ def sub(self, other):
123147
))
124148

125149
@staticmethod
126-
@lru_cache(10000)
150+
@lru_cache(_LRU_CACHE_SIZE)
127151
def _iso_point_add(point_string, interval_string, _calendar_mode):
128152
"""Add the parsed point_string to the parsed interval_string."""
129153
point = point_parse(point_string)
@@ -134,23 +158,23 @@ def _cmp(self, other: 'ISO8601Point') -> int:
134158
return self._iso_point_cmp(self.value, other.value, CALENDAR.mode)
135159

136160
@staticmethod
137-
@lru_cache(10000)
161+
@lru_cache(_LRU_CACHE_SIZE)
138162
def _iso_point_cmp(point_string, other_point_string, _calendar_mode):
139163
"""Compare the parsed point_string to the other one."""
140164
point = point_parse(point_string)
141165
other_point = point_parse(other_point_string)
142166
return cmp(point, other_point)
143167

144168
@staticmethod
145-
@lru_cache(10000)
169+
@lru_cache(_LRU_CACHE_SIZE)
146170
def _iso_point_sub_interval(point_string, interval_string, _calendar_mode):
147171
"""Return the parsed point_string minus the parsed interval_string."""
148172
point = point_parse(point_string)
149173
interval = interval_parse(interval_string)
150174
return str(point - interval)
151175

152176
@staticmethod
153-
@lru_cache(10000)
177+
@lru_cache(_LRU_CACHE_SIZE)
154178
def _iso_point_sub_point(point_string, other_point_string, _calendar_mode):
155179
"""Return the difference between the two parsed point strings."""
156180
point = point_parse(point_string)
@@ -216,7 +240,7 @@ def __bool__(self):
216240
return self._iso_interval_nonzero(self.value)
217241

218242
@staticmethod
219-
@lru_cache(10000)
243+
@lru_cache(_LRU_CACHE_SIZE)
220244
def _iso_interval_abs(interval_string, other_interval_string):
221245
"""Return the absolute (non-negative) value of an interval_string."""
222246
interval = interval_parse(interval_string)
@@ -226,38 +250,38 @@ def _iso_interval_abs(interval_string, other_interval_string):
226250
return interval_string
227251

228252
@staticmethod
229-
@lru_cache(10000)
253+
@lru_cache(_LRU_CACHE_SIZE)
230254
def _iso_interval_add(interval_string, other_interval_string):
231255
"""Return one parsed interval_string plus the other one."""
232256
interval = interval_parse(interval_string)
233257
other = interval_parse(other_interval_string)
234258
return str(interval + other)
235259

236260
@staticmethod
237-
@lru_cache(10000)
261+
@lru_cache(_LRU_CACHE_SIZE)
238262
def _iso_interval_cmp(interval_string, other_interval_string):
239263
"""Compare one parsed interval_string with the other one."""
240264
interval = interval_parse(interval_string)
241265
other = interval_parse(other_interval_string)
242266
return cmp(interval, other)
243267

244268
@staticmethod
245-
@lru_cache(10000)
269+
@lru_cache(_LRU_CACHE_SIZE)
246270
def _iso_interval_sub(interval_string, other_interval_string):
247271
"""Subtract one parsed interval_string from the other one."""
248272
interval = interval_parse(interval_string)
249273
other = interval_parse(other_interval_string)
250274
return str(interval - other)
251275

252276
@staticmethod
253-
@lru_cache(10000)
277+
@lru_cache(_LRU_CACHE_SIZE)
254278
def _iso_interval_mul(interval_string, factor):
255279
"""Multiply one parsed interval_string's values by factor."""
256280
interval = interval_parse(interval_string)
257281
return str(interval * factor)
258282

259283
@staticmethod
260-
@lru_cache(10000)
284+
@lru_cache(_LRU_CACHE_SIZE)
261285
def _iso_interval_nonzero(interval_string):
262286
"""Return whether the parsed interval_string is a null interval."""
263287
interval = interval_parse(interval_string)
@@ -318,7 +342,6 @@ class ISO8601Sequence(SequenceBase):
318342

319343
TYPE = CYCLER_TYPE_ISO8601
320344
TYPE_SORT_KEY = CYCLER_TYPE_SORT_KEY_ISO8601
321-
_MAX_CACHED_POINTS = 100
322345

323346
__slots__ = ('dep_section', 'context_start_point', 'context_end_point',
324347
'offset', '_cached_first_point_values',
@@ -346,7 +369,9 @@ def __init__(
346369

347370
# cache is_on_sequence
348371
# see B019 - https://github.com/PyCQA/flake8-bugbear#list-of-warnings
349-
self.is_on_sequence = lru_cache(maxsize=100)(self._is_on_sequence)
372+
self.is_on_sequence = lru_cache(_LARGE_LRU_CACHE_SIZE)(
373+
self._is_on_sequence
374+
)
350375

351376
if (
352377
context_start_point is None
@@ -462,8 +487,7 @@ def is_valid(self, point):
462487
return self._cached_valid_point_booleans[point.value]
463488
except KeyError:
464489
is_valid = self.is_on_sequence(point)
465-
if (len(self._cached_valid_point_booleans) >
466-
self._MAX_CACHED_POINTS):
490+
if len(self._cached_valid_point_booleans) > _LARGE_LRU_CACHE_SIZE:
467491
self._cached_valid_point_booleans.popitem()
468492
self._cached_valid_point_booleans[point.value] = is_valid
469493
return is_valid
@@ -555,14 +579,15 @@ def _check_and_cache_next_point(self, point, next_point):
555579
)
556580

557581
# Cache the answer for point -> next_point.
558-
if (len(self._cached_next_point_values) >
559-
self._MAX_CACHED_POINTS):
582+
if len(self._cached_next_point_values) > _LARGE_LRU_CACHE_SIZE:
560583
self._cached_next_point_values.popitem()
561584
self._cached_next_point_values[point.value] = next_point.value
562585

563586
# Cache next_point as a valid starting point for this recurrence.
564-
if (len(self._cached_next_point_values) >
565-
self._MAX_CACHED_POINTS):
587+
if (
588+
_LARGE_LRU_CACHE_SIZE
589+
and len(self._cached_next_point_values) > _LARGE_LRU_CACHE_SIZE
590+
):
566591
self._cached_recent_valid_points.pop(0)
567592
self._cached_recent_valid_points.append(next_point)
568593

@@ -600,8 +625,10 @@ def get_first_point(
600625
# Check multiple exclusions
601626
if ret and ret in self.exclusions:
602627
return self.get_next_point_on_sequence(ret)
603-
if (len(self._cached_first_point_values) >
604-
self._MAX_CACHED_POINTS):
628+
if (
629+
len(self._cached_first_point_values)
630+
> _LARGE_LRU_CACHE_SIZE
631+
):
605632
self._cached_first_point_values.popitem()
606633
self._cached_first_point_values[point.value] = (
607634
first_point_value)
@@ -950,7 +977,7 @@ def is_offset_absolute(offset_string):
950977
return False
951978

952979

953-
@lru_cache(10000)
980+
@lru_cache(_LRU_CACHE_SIZE)
954981
def _interval_parse(interval_string):
955982
"""Parse an interval_string into a proper Duration object."""
956983
return WorkflowSpecifics.interval_parser.parse(interval_string)
@@ -965,7 +992,7 @@ def point_parse(point_string: str) -> 'TimePoint':
965992
)
966993

967994

968-
@lru_cache(10000)
995+
@lru_cache(_LRU_CACHE_SIZE)
969996
def _point_parse(point_string: str, _dump_fmt, _tz) -> 'TimePoint':
970997
"""Parse a point_string into a proper TimePoint object.
971998

0 commit comments

Comments
 (0)