diff --git a/tangostationcontrol/integration_test/default/devices/test_device_rcu2h.py b/tangostationcontrol/integration_test/default/devices/test_device_rcu2h.py
deleted file mode 100644
index 35b3b30c5ffecc78d39bf327cfeee4f0f133284e..0000000000000000000000000000000000000000
--- a/tangostationcontrol/integration_test/default/devices/test_device_rcu2h.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-
-from integration_test.default.devices.base import AbstractTestBases
-
-
-class TestDeviceRECV(AbstractTestBases.TestDeviceBase):
-    def setUp(self):
-        super().setUp("STAT/RECVH/H0")
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_recvh.py b/tangostationcontrol/integration_test/default/devices/test_device_recvh.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba2122c5d3be90776819fb0ec9e5dcfd59b31498
--- /dev/null
+++ b/tangostationcontrol/integration_test/default/devices/test_device_recvh.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import time
+
+import numpy
+
+from integration_test.default.devices.base import AbstractTestBases
+
+
+class TestDeviceRECV(AbstractTestBases.TestDeviceBase):
+    HBAT_DELAY_UPDATE_INTERVAL = 0.1
+
+    def setUp(self):
+        super().setUp("STAT/RECVH/H0")
+
+    def test_update_HBAT_bf_delay_steps(self):
+        """Verify HBAT_bf_delay_steps_stage propagates to HBAT_bf_delay_steps."""
+
+        # update faster to speed up tests
+        self.proxy.put_property(
+            {"HBAT_BF_delay_steps_update_interval": self.HBAT_DELAY_UPDATE_INTERVAL}
+        )
+        self.proxy.off()
+        self.proxy.boot()
+
+        # write new value to HBAT_BF_delay_steps_stage_RW
+        delay_steps = numpy.ones(
+            self.proxy.HBAT_BF_delay_steps_stage_RW.shape, dtype=numpy.uint64
+        )
+        self.proxy.HBAT_BF_delay_steps_stage_RW = delay_steps
+
+        # make sure HBAT_BF_delay_steps_RW is updated
+        time.sleep(self.HBAT_DELAY_UPDATE_INTERVAL * 2)
+
+        # verify whether it did
+        numpy.testing.assert_equal(self.proxy.HBAT_BF_delay_steps_RW, delay_steps)
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_rcu2l.py b/tangostationcontrol/integration_test/default/devices/test_device_recvl.py
similarity index 100%
rename from tangostationcontrol/integration_test/default/devices/test_device_rcu2l.py
rename to tangostationcontrol/integration_test/default/devices/test_device_recvl.py
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
index c4be173ce21673437db27e37444eff6e2ff999f8..991b3b47997504710f30de65fa719dd092bc9e00 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
@@ -93,7 +93,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
 
         # Verify attribute is present (all zeros if never used before)
         delays_r1 = numpy.array(
-            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value
         )
 
         self.assertIsNotNone(delays_r1)
@@ -103,7 +103,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         # Verify writing operation does not lead to errors
         self.proxy.set_pointing(self.POINTING_DIRECTION)  # write values to RECV
         delays_r2 = numpy.array(
-            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value
         )
 
         self.assertIsNotNone(delays_r2)
@@ -121,7 +121,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         )
 
         calculated_HBAT_delay_steps = numpy.array(
-            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value
         )
 
         expected_HBAT_delay_steps = numpy.array(
@@ -142,9 +142,9 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         self.proxy.set_pointing(["AZELGEO", "0rad", "0rad"] * CS001_TILES)
 
         # obtain delays of the X polarisation of all the elements of the first tile
-        north_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_RW[0].reshape(
-            4, 4, 2
-        )[:, :, 0]
+        north_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[
+            0
+        ].reshape(4, 4, 2)[:, :, 0]
 
         # delays must differ under rotation, or our test will give a false positive
         self.assertNotEqual(
@@ -157,7 +157,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
             self.proxy.set_pointing(["AZELGEO", f"{angle}rad", "0rad"] * CS001_TILES)
 
             # obtain delays of the X polarisation of all the elements of the first tile
-            angled_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_RW[
+            angled_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[
                 0
             ].reshape(4, 4, 2)[:, :, 0]
 
@@ -188,7 +188,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         self.proxy.set_pointing_for_specific_time(json_string)
 
         calculated_HBAT_delay_steps = numpy.array(
-            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value
         )  # dims (CS001_TILES, 32)
 
         # Check all delay steps are zero with small margin
diff --git a/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py b/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
index 64383773efcef5622c774fbeab3102653b71fa8a..650d6069cebb3f5e84871d136e56fe98868099ab 100644
--- a/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
+++ b/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
@@ -33,6 +33,7 @@ class TileBeamManager(AbstractBeamManager):
         # Labels for DurationMetric
         self.metric_labels = device_labels(device)
 
+    @DurationMetric()
     def delays(self, pointing_direction: numpy.array, timestamp: datetime.datetime):
         """
         Calculate the delays (in seconds) based on the pointing list and the timestamp.
@@ -138,11 +139,14 @@ class TileBeamManager(AbstractBeamManager):
             (len(recv_proxies), MAX_ANTENNA, N_elements * N_pol)
         )
 
+        # Stage the new delays for periodic writes that combine
+        # delays for other antenna fields, and only applies when the
+        # value changes.
         for idx, recv_proxy in enumerate(recv_proxies):
             self.device.atomic_read_modify_write_attribute(
                 mapped_values[idx],
                 recv_proxy,
-                "HBAT_bf_delay_steps_RW",
+                "HBAT_bf_delay_steps_stage_RW",
                 cast_type=numpy.int64,
             )
 
diff --git a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py
index 542b947b5c36fcd5f8a4b80da4704e14c7f2f6ec..e11225004eb763fc80c9140fbb025edc7618acc3 100644
--- a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py
+++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py
@@ -4,6 +4,7 @@
 import asyncio
 import logging
 import socket
+import textwrap
 from typing import Dict, List
 
 import asyncua
@@ -479,7 +480,9 @@ class ProtocolAttribute:
             value = value.tolist() if type(value) == numpy.ndarray else value
 
         if self.log_writes:
-            logger.info(f"OPC-UA write: {self.name} := {str(value)[:80]}")
+            logger.info(
+                f"OPC-UA write: {self.name} := {textwrap.shorten(str(value), 150)}"
+            )
 
         try:
             await self.node.set_data_value(
diff --git a/tangostationcontrol/tangostationcontrol/common/asyncio.py b/tangostationcontrol/tangostationcontrol/common/asyncio.py
index e5c0c40e7b591af7847114779e59ff4d006b35c3..f716bc83a9fc6cb2e01fa57d97c7c111539aa93a 100644
--- a/tangostationcontrol/tangostationcontrol/common/asyncio.py
+++ b/tangostationcontrol/tangostationcontrol/common/asyncio.py
@@ -65,7 +65,12 @@ class EventLoopThread:
 class PeriodicTask:
     """Provide a periodic call to a coroutine."""
 
-    def __init__(self, event_loop, func: Callable[[], None], interval: float = 1.0):
+    def __init__(
+        self,
+        event_loop,
+        func: Callable[[], None],
+        interval: float = 1.0,
+    ):
         self.event_loop = event_loop
         self.func = func
         self.interval = interval
diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield/afh.py b/tangostationcontrol/tangostationcontrol/devices/antennafield/afh.py
index 79652c4b72e59c43acdc29459caa0b06a31f6a06..cdd4368f7b31ae68ef7a980c93e9b63c87a8e402 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield/afh.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield/afh.py
@@ -146,6 +146,13 @@ class AFH(AF):
         max_dim_y=MAX_ANTENNA,
         access=AttrWriteType.READ_WRITE,
     )
+    HBAT_BF_delay_steps_stage_RW = MappedAttribute(
+        "HBAT_BF_delay_steps_stage_RW",
+        dtype=((numpy.int64,),),
+        max_dim_x=N_elements * N_pol,
+        max_dim_y=MAX_ANTENNA,
+        access=AttrWriteType.READ_WRITE,
+    )
     HBAT_LED_on_R = MappedAttribute(
         "HBAT_LED_on_R",
         dtype=((bool,),),
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
index 5122e67ec89fe71cb107ccd106903f3f8600469b..63523df6228635fdc0bb13d513303da6f37bc3a5 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
@@ -78,7 +78,7 @@ class BeamDevice(AsyncDevice):
         dtype="DevFloat",
         doc="Beam weights updating interval time [seconds]",
         mandatory=False,
-        default_value=10.0,
+        default_value=1.0,
     )
 
     Beam_tracking_application_offset = device_property(
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
index 57381221c60aa81fa225dd99e971ac1efc607dbc..e5d1248daf69da168813556dcd75225454676719 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
@@ -283,7 +283,7 @@ class LOFARDevice(Device):
     )
 
     poll_thread_running_R = attribute(
-        doc="Whether the attributes are being polled." "",
+        doc="Whether the attributes are being polled.",
         dtype=bool,
         fget=lambda self: (
             self.event_loop_thread
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
index 1872e1f09b7de82eb73e5add59ed57af978045eb..5c0b17347564ea35796fbba8a70383d1d068f614 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
@@ -283,6 +283,7 @@ class AntennaToRecvMapper(ABC, AntennaMapper):
             "ANT_mask_RW": value_map_ant_bool,
             "HBAT_BF_delay_steps_R": value_map_ant_32_int,
             "HBAT_BF_delay_steps_RW": value_map_ant_32_int,
+            "HBAT_BF_delay_steps_stage_RW": value_map_ant_32_int,
             "HBAT_LED_on_R": value_map_ant_32_bool,
             "HBAT_LED_on_RW": value_map_ant_32_bool,
             "HBAT_PWR_LNA_on_R": value_map_ant_32_bool,
diff --git a/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py b/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
index e8c43bb59fa9ad14ec83ed518949ba5caf7042bb..2f2f68d1f7eb8d23c4a97b3ee10137d65121c4ef 100644
--- a/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
+++ b/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
@@ -4,26 +4,36 @@
 """ Receiver Unit High Device Server for LOFAR2.0
 
 """
+import logging
+
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
 from tango import AttrWriteType, DevVarFloatArray
 
 # PyTango imports
-from tango.server import command, device_property
+from tango.server import command, device_property, attribute
 
 # Additional import
+from tangostationcontrol.common.asyncio import PeriodicTask
 from tangostationcontrol.common.constants import (
     N_rcu,
     N_elements,
     N_pol,
     N_rcu_inp,
+    DEFAULT_POLLING_PERIOD_MS,
+)
+from tangostationcontrol.common.device_decorators import DurationMetric
+from tangostationcontrol.common.lofar_logging import (
+    device_logging_to_python,
+    exception_to_str,
 )
-from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.recv_device import RECVDevice
 from tangostationcontrol.metrics import device_metrics
 
 __all__ = ["RECVH"]
 
+logger = logging.getLogger()
+
 
 @device_logging_to_python()
 @device_metrics(
@@ -44,6 +54,13 @@ class RECVH(RECVDevice):
     # Device Properties
     # -----------------
 
+    HBAT_BF_delay_steps_update_interval = device_property(
+        dtype="DevFloat",
+        doc="Interval with which to update HBAT_BF_delay_steps_RW [seconds]",
+        mandatory=False,
+        default_value=10.0,
+    )
+
     # ----- Default settings
 
     HBAT_PWR_on_RW_default = device_property(
@@ -118,6 +135,21 @@ class RECVH(RECVDevice):
     # ----------
     # Attributes
     # ----------
+
+    bf_delay_steps_thread_running_R = attribute(
+        doc="Whether the HBAT_BF_delay_steps_RW is being updated.",
+        dtype=bool,
+        fget=lambda self: (
+            self.event_loop_thread
+            and self.event_loop_thread.is_running()
+            and self.bf_delay_steps_update_task
+            and self.bf_delay_steps_update_task.is_running()
+        ),
+        # Tango needs to poll this, as otherwise this attribute will never
+        # be exposed as "False" as the event thread must run to do so.
+        polling_period=DEFAULT_POLLING_PERIOD_MS,
+    )
+
     RCU_firmware_version_R = AttributeWrapper(
         comms_annotation=["RCU_firmware_version_R"],
         datatype=numpy.int64,
@@ -146,16 +178,37 @@ class RECVH(RECVDevice):
     # The 32 delays deconstruct as delays[polarisation][dipole],
     # and each delay is the number of 'delay steps' to apply (0.5ns for HBAT1).
     HBAT_BF_delay_steps_R = AttributeWrapper(
-        comms_annotation={"path": ["HBAT_BF_delay_steps_R"], "log_writes": False},
+        comms_annotation=["HBAT_BF_delay_steps_R"],
         datatype=numpy.int64,
         dims=(N_rcu * N_rcu_inp, N_elements, N_pol),
     )
     HBAT_BF_delay_steps_RW = AttributeWrapper(
-        comms_annotation={"path": ["HBAT_BF_delay_steps_RW"], "log_writes": False},
+        doc="HBAT beamformer delay steps. The hardware supports writing this value only once every 10s. This device will write the value of HBAT_BF_delay_steps_stage_RW every HBAT_BF_delay_steps_update_interval seconds.",
+        comms_annotation=["HBAT_BF_delay_steps_RW"],
         datatype=numpy.int64,
         dims=(N_rcu * N_rcu_inp, N_elements, N_pol),
         access=AttrWriteType.READ_WRITE,
     )
+
+    # Multiple TileBeams want to read-modify-write HBAT_BF_delay_steps to
+    # interleave the delays for their respective antenna fields. Yet we are only
+    # allowed to write HBAT_BF_delay_steps_RW every 10s. This "stage" attribute
+    # allows collecting changes, which are periodically applied through
+    # the PeriodicTask "_update_bf_delay_steps".
+    @attribute(
+        doc="HBAT beamformer delay steps staged for writing at the next interval.",
+        dtype=((numpy.int64,),),
+        max_dim_y=N_rcu * N_rcu_inp,
+        max_dim_x=N_elements * N_pol,
+        access=AttrWriteType.READ_WRITE,
+    )
+    def HBAT_BF_delay_steps_stage_RW(self):
+        return self._HBAT_BF_delay_steps_stage_RW
+
+    @HBAT_BF_delay_steps_stage_RW.write
+    def HBAT_BF_delay_steps_stage_RW(self, value):
+        self._HBAT_BF_delay_steps_stage_RW = value
+
     HBAT_LED_on_R = AttributeWrapper(
         comms_annotation=["HBAT_LED_on_R"],
         datatype=bool,
@@ -190,9 +243,53 @@ class RECVH(RECVDevice):
         access=AttrWriteType.READ_WRITE,
     )
 
+    @DurationMetric()
+    async def _update_bf_delay_steps(self):
+        try:
+            staged_value = await self.async_read_attribute(
+                "HBAT_BF_delay_steps_stage_RW"
+            )
+            last_written_value = await self.async_read_attribute(
+                "HBAT_BF_delay_steps_RW"
+            )
+
+            if (staged_value != last_written_value).all():
+                logger.debug(
+                    "Writing HBAT_BF_delay_steps_RW := HBAT_BF_delay_steps_stage_RW after detecting changes"
+                )
+                self.proxy.write_attribute("HBAT_BF_delay_steps_RW", staged_value)
+        except Exception as ex:
+            logger.error(
+                f"Failed to update HBAT_BF_delay_steps_RW: {exception_to_str(ex)}"
+            )
+
     # --------
     # overloaded functions
     # --------
+    def __init__(self, cl, name):
+        self.bf_delay_steps_update_task = None
+        self._HBAT_BF_delay_steps_stage_RW = numpy.zeros(
+            (N_rcu * N_rcu_inp, N_elements * N_pol), dtype=numpy.int64
+        )
+
+        super().__init__(cl, name)
+
+    def configure_for_on(self):
+        super().configure_for_on()
+
+        self.bf_delay_steps_update_task = PeriodicTask(
+            self.event_loop_thread.event_loop,
+            self._update_bf_delay_steps,
+            self.HBAT_BF_delay_steps_update_interval,
+        )
+
+    def configure_for_off(self):
+        # stop writing delays (gracefully, as it is fast)
+        if self.bf_delay_steps_update_task:
+            self.bf_delay_steps_update_task.stop()
+
+        super().configure_for_off()
+
     def properties_changed(self):
         super().properties_changed()
 
diff --git a/tangostationcontrol/test/beam/managers/test_tilebeam_manager.py b/tangostationcontrol/test/beam/managers/test_tilebeam_manager.py
index be4ae13a9601df4735cbd1483d62c54a0d22e79f..af4d8738327b9d556f830efa654a2596fe7088db 100644
--- a/tangostationcontrol/test/beam/managers/test_tilebeam_manager.py
+++ b/tangostationcontrol/test/beam/managers/test_tilebeam_manager.py
@@ -19,11 +19,17 @@ from tangostationcontrol.common.constants import (
 class TestTileBeamManager(base.TestCase):
     """Test class for TileBeam manager"""
 
-    def test_delays(self):
+    @patch.object(device_decorators, "get_current_device")
+    def test_delays(self, mock_get_current_device):
         """Verify delays are retrieved with correct dimensions"""
         _dt = datetime.datetime.now()
+
         device_mock = MagicMock()
         device_mock.get_name = MagicMock(return_value="domain/family/member")
+        device_mock.metric_labels = {"foo": "bar"}
+
+        mock_get_current_device.return_value = device_mock
+
         sut = TileBeamManager(device_mock)
 
         sut.nr_tiles = DEFAULT_N_HBA_TILES
diff --git a/tangostationcontrol/test/beam/test_delays.py b/tangostationcontrol/test/beam/test_delays.py
index 809aaa21714ba0284849087b3eacee5b95847d6e..4169f4d53b76d429a5a9efc03d4a7403dcae1e4b 100644
--- a/tangostationcontrol/test/beam/test_delays.py
+++ b/tangostationcontrol/test/beam/test_delays.py
@@ -3,6 +3,7 @@
 
 import datetime
 import logging
+import statistics
 import time
 
 import casacore
@@ -11,7 +12,7 @@ import numpy
 import numpy.testing
 import threading
 
-from tangostationcontrol.beam.delays import Delays
+from tangostationcontrol.beam.delays import Delays, threading as delays_threading
 from tangostationcontrol.common.constants import MAX_ANTENNA, N_beamlets_ctrl
 
 from test import base
@@ -170,67 +171,52 @@ class TestDelays(base.TestCase):
 
         self.assertAlmostEqual(0.1, delays[0], 6, f"delays[0] = {delays[0]}")
 
-    def test_delays_bulk(self):
-        d = Delays([0, 0, 0])
+
+class TestDelaysBulk(base.TestCase):
+    def setUp(self):
+        self.d = Delays([0, 0, 0])
         timestamp = datetime.datetime(
             2022, 3, 1, 0, 0, 0
         )  # timestamp does not actually matter, but casacore doesn't know that.
-        d.set_measure_time(timestamp)
+        self.d.set_measure_time(timestamp)
 
         # generate different positions and directions
-        positions = numpy.array([[i, 2, 3] for i in range(5)])
-        directions = numpy.array(
+        self.positions = numpy.array([[i, 2, 3] for i in range(MAX_ANTENNA)])
+        self.directions = numpy.array(
             [
                 ["J2000", f"{i*numpy.pi/180}rad", f"{i*numpy.pi/180}rad"]
-                for i in range(90)
+                for i in range(N_beamlets_ctrl)
             ]
         )
 
-        bulk_result = d.delays_bulk(directions, positions)
+    def test_delays_bulk(self):
+        bulk_result = self.d.delays_bulk(self.directions, self.positions)
 
         # verify parallellisation along direction axis
-        for i, single_dir in enumerate(directions):
-            single_dir_result = d.delays_bulk([single_dir], positions)
+        for i, single_dir in enumerate(self.directions):
+            single_dir_result = self.d.delays_bulk([single_dir], self.positions)
             numpy.testing.assert_almost_equal(
                 single_dir_result[:, 0], bulk_result[:, i], 4
             )
 
         # verify parallellisation along position axis
-        for i, single_pos in enumerate(positions):
-            single_pos_result = d.delays_bulk(directions, [single_pos])
+        for i, single_pos in enumerate(self.positions):
+            single_pos_result = self.d.delays_bulk(self.directions, [single_pos])
             numpy.testing.assert_almost_equal(
                 single_pos_result[0, :], bulk_result[i, :], 4
             )
 
     def test_delays_bulk_speed(self):
-        d = Delays([0, 0, 0])
-        timestamp = datetime.datetime(
-            2022, 3, 1, 0, 0, 0
-        )  # timestamp does not actually matter, but casacore doesn't know that.
-        d.set_measure_time(timestamp)
-
-        positions = numpy.array([[1, 2, 3]] * MAX_ANTENNA)
-        directions = numpy.array([["J2000", "0rad", "0rad"]] * N_beamlets_ctrl)
-
         count = 10
         before = time.monotonic_ns()
         for _ in range(count):
-            _ = d.delays_bulk(directions, positions)
+            _ = self.d.delays_bulk(self.directions, self.positions)
         after = time.monotonic_ns()
         logging.error(
             f"delays bulk averaged {(after - before) / count / 1e6} ms to convert 488 directions for 96 antennas."
         )
 
     def test_delays_bulk_parallel_speed(self):
-        d = Delays([0, 0, 0])
-        timestamp = datetime.datetime(
-            2022, 3, 1, 0, 0, 0
-        )  # timestamp does not actually matter, but casacore doesn't know that.
-        d.set_measure_time(timestamp)
-
-        positions = numpy.array([[1, 2, 3]] * MAX_ANTENNA)
-        directions = numpy.array([["J2000", "0rad", "0rad"]] * N_beamlets_ctrl)
-
         count = 10
         nr_threads = 3
         duration_results_ms: list[float] = []
@@ -238,11 +224,22 @@ class TestDelays(base.TestCase):
         def run_delays_bulk():
             before = time.monotonic_ns()
             for _ in range(count):
-                _ = d.delays_bulk(directions, positions)
+                _ = self.d.delays_bulk(self.directions, self.positions)
             after = time.monotonic_ns()
 
             duration_results_ms.append((after - before) / count / 1e6)
 
+        # measure single-threaded performance first
+        for _ in range(nr_threads):
+            run_delays_bulk()
+        single_thread_execution_time = statistics.median(duration_results_ms)
+
+        logging.error(
+            f"delays bulk single-threaded cost averages at {single_thread_execution_time} ms per call to convert 488 directions for 96 antennas {count} times."
+        )
+
+        # compare against multi-threaded performance
+        duration_results_ms = []
         threads = [threading.Thread(target=run_delays_bulk) for _ in range(nr_threads)]
         [t.start() for t in threads]
         [t.join() for t in threads]
@@ -251,9 +248,25 @@ class TestDelays(base.TestCase):
             f"delays bulk multi-threaded averages are {[int(d) for d in duration_results_ms]} ms per call to convert 488 directions for 96 antennas {count} times using {nr_threads} threads."
         )
 
-        MAX_CALL_DURATION_MS = 150
-
         # as we have a real time system and sufficient cores, we care
         # most about the worst case for a thread. We assume the worst
         # case thread is slowed down by all the work of the other threads.
-        self.assertLess(max(duration_results_ms) / nr_threads, MAX_CALL_DURATION_MS)
+        self.assertLess(
+            max(duration_results_ms) / nr_threads, single_thread_execution_time * 1.25
+        )
+
+        # report the performance if we remove the compute lock around casacore, to detect
+        # on which systems it matters.
+
+        with mock.patch.object(delays_threading, "Lock") as m_lock:
+            # compare against multi-threaded performance
+            duration_results_ms = []
+            threads = [
+                threading.Thread(target=run_delays_bulk) for _ in range(nr_threads)
+            ]
+            [t.start() for t in threads]
+            [t.join() for t in threads]
+
+            logging.error(
+                f"delays bulk multi-threaded averages WITHOUT LOCK are {[int(d) for d in duration_results_ms]} ms per call to convert 488 directions for 96 antennas {count} times using {nr_threads} threads."
+            )