Incrementally update flattend OHLC data
After much effort (and exhaustion) but failure to get a view into our `numpy` OHLC struct-array, this instead allocates an in-thread-memory array which is updated with flattened data every flow update cycle. I need to report what I think is a bug to `numpy` core about the whole view thing not working but, more or less this gets the same behaviour and minimizes work to flatten the sampled data for line-graphics drawing thus improving refresh latency when drawing large downsampled curves. TL;DR: - add `ShmArray.ustruct()` to return a **copy of** (since a view doesn't work..) the (field filtered) shm array which is the same index-length as the source data. - update the OHLC ds curve with view aware data sliced out from the pre-allocated and incrementally updated data (we had to add a last index var `._iflat` to track appends - this should be moved into a renderer eventually?).incr_update_backup
							parent
							
								
									b4c7d02fcb
								
							
						
					
					
						commit
						6f5bb9cbe0
					
				|  | @ -22,7 +22,6 @@ from __future__ import annotations | ||||||
| from sys import byteorder | from sys import byteorder | ||||||
| from typing import Optional | from typing import Optional | ||||||
| from multiprocessing.shared_memory import SharedMemory, _USE_POSIX | from multiprocessing.shared_memory import SharedMemory, _USE_POSIX | ||||||
| from multiprocessing import resource_tracker as mantracker |  | ||||||
| 
 | 
 | ||||||
| if _USE_POSIX: | if _USE_POSIX: | ||||||
|     from _posixshmem import shm_unlink |     from _posixshmem import shm_unlink | ||||||
|  | @ -30,6 +29,7 @@ if _USE_POSIX: | ||||||
| import tractor | import tractor | ||||||
| import numpy as np | import numpy as np | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||||
|  | from numpy.lib import recfunctions as rfn | ||||||
| 
 | 
 | ||||||
| from ..log import get_logger | from ..log import get_logger | ||||||
| from ._source import base_iohlc_dtype | from ._source import base_iohlc_dtype | ||||||
|  | @ -46,6 +46,10 @@ _default_size = 10 * _secs_in_day | ||||||
| _rt_buffer_start = int(9*_secs_in_day) | _rt_buffer_start = int(9*_secs_in_day) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def cuckoff_mantracker(): | ||||||
|  | 
 | ||||||
|  |     from multiprocessing import resource_tracker as mantracker | ||||||
|  | 
 | ||||||
|     # Tell the "resource tracker" thing to fuck off. |     # Tell the "resource tracker" thing to fuck off. | ||||||
|     class ManTracker(mantracker.ResourceTracker): |     class ManTracker(mantracker.ResourceTracker): | ||||||
|         def register(self, name, rtype): |         def register(self, name, rtype): | ||||||
|  | @ -68,6 +72,9 @@ mantracker.unregister = mantracker._resource_tracker.unregister | ||||||
|     mantracker.getfd = mantracker._resource_tracker.getfd |     mantracker.getfd = mantracker._resource_tracker.getfd | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | cuckoff_mantracker() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class SharedInt: | class SharedInt: | ||||||
|     """Wrapper around a single entry shared memory array which |     """Wrapper around a single entry shared memory array which | ||||||
|     holds an ``int`` value used as an index counter. |     holds an ``int`` value used as an index counter. | ||||||
|  | @ -191,7 +198,11 @@ class ShmArray: | ||||||
|         self._post_init: bool = False |         self._post_init: bool = False | ||||||
| 
 | 
 | ||||||
|         # pushing data does not write the index (aka primary key) |         # pushing data does not write the index (aka primary key) | ||||||
|  |         dtype = shmarr.dtype | ||||||
|  |         if dtype.fields: | ||||||
|             self._write_fields = list(shmarr.dtype.fields.keys())[1:] |             self._write_fields = list(shmarr.dtype.fields.keys())[1:] | ||||||
|  |         else: | ||||||
|  |             self._write_fields = None | ||||||
| 
 | 
 | ||||||
|     # TODO: ringbuf api? |     # TODO: ringbuf api? | ||||||
| 
 | 
 | ||||||
|  | @ -237,6 +248,48 @@ class ShmArray: | ||||||
| 
 | 
 | ||||||
|         return a |         return a | ||||||
| 
 | 
 | ||||||
|  |     def ustruct( | ||||||
|  |         self, | ||||||
|  |         fields: Optional[list[str]] = None, | ||||||
|  | 
 | ||||||
|  |         # type that all field values will be cast to | ||||||
|  |         # in the returned view. | ||||||
|  |         common_dtype: np.dtype = np.float, | ||||||
|  | 
 | ||||||
|  |     ) -> np.ndarray: | ||||||
|  | 
 | ||||||
|  |         array = self._array | ||||||
|  | 
 | ||||||
|  |         if fields: | ||||||
|  |             selection = array[fields] | ||||||
|  |             fcount = len(fields) | ||||||
|  |         else: | ||||||
|  |             selection = array | ||||||
|  |             fcount = len(array.dtype.fields) | ||||||
|  | 
 | ||||||
|  |         # XXX: manual ``.view()`` attempt that also doesn't work. | ||||||
|  |         # uview = selection.view( | ||||||
|  |         #     dtype='<f16', | ||||||
|  |         # ).reshape(-1, 4, order='A') | ||||||
|  | 
 | ||||||
|  |         # assert len(selection) == len(uview) | ||||||
|  | 
 | ||||||
|  |         u = rfn.structured_to_unstructured( | ||||||
|  |             selection, | ||||||
|  |             # dtype=float, | ||||||
|  |             copy=True, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # unstruct = np.ndarray(u.shape, dtype=a.dtype, buffer=shm.buf) | ||||||
|  |         # array[:] = a[:] | ||||||
|  |         return u | ||||||
|  |         # return ShmArray( | ||||||
|  |         #     shmarr=u, | ||||||
|  |         #     first=self._first, | ||||||
|  |         #     last=self._last, | ||||||
|  |         #     shm=self._shm | ||||||
|  |         # ) | ||||||
|  | 
 | ||||||
|     def last( |     def last( | ||||||
|         self, |         self, | ||||||
|         length: int = 1, |         length: int = 1, | ||||||
|  | @ -386,7 +439,11 @@ def open_shm_array( | ||||||
|         create=True, |         create=True, | ||||||
|         size=a.nbytes |         size=a.nbytes | ||||||
|     ) |     ) | ||||||
|     array = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf) |     array = np.ndarray( | ||||||
|  |         a.shape, | ||||||
|  |         dtype=a.dtype, | ||||||
|  |         buffer=shm.buf | ||||||
|  |     ) | ||||||
|     array[:] = a[:] |     array[:] = a[:] | ||||||
|     array.setflags(write=int(not readonly)) |     array.setflags(write=int(not readonly)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ incremental update. | ||||||
| ''' | ''' | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
| from functools import partial | from functools import partial | ||||||
| import time |  | ||||||
| from typing import ( | from typing import ( | ||||||
|     Optional, |     Optional, | ||||||
|     Callable, |     Callable, | ||||||
|  | @ -38,7 +37,7 @@ from PyQt5.QtGui import QPainterPath | ||||||
| 
 | 
 | ||||||
| from ..data._sharedmem import ( | from ..data._sharedmem import ( | ||||||
|     ShmArray, |     ShmArray, | ||||||
|     # attach_shm_array |     open_shm_array, | ||||||
| ) | ) | ||||||
| from .._profile import pg_profile_enabled, ms_slower_then | from .._profile import pg_profile_enabled, ms_slower_then | ||||||
| from ._ohlc import ( | from ._ohlc import ( | ||||||
|  | @ -152,6 +151,7 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
|     render: bool = True  # toggle for display loop |     render: bool = True  # toggle for display loop | ||||||
|     flat: Optional[ShmArray] = None |     flat: Optional[ShmArray] = None | ||||||
|     x_basis: Optional[np.ndarray] = None |     x_basis: Optional[np.ndarray] = None | ||||||
|  |     _iflat: int = 0 | ||||||
| 
 | 
 | ||||||
|     _last_uppx: float = 0 |     _last_uppx: float = 0 | ||||||
|     _in_ds: bool = False |     _in_ds: bool = False | ||||||
|  | @ -344,6 +344,7 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
|         graphics = self.graphics |         graphics = self.graphics | ||||||
|         if isinstance(graphics, BarItems): |         if isinstance(graphics, BarItems): | ||||||
| 
 | 
 | ||||||
|  |             fields = ['open', 'high', 'low', 'close'] | ||||||
|             # if no source data renderer exists create one. |             # if no source data renderer exists create one. | ||||||
|             r = self._src_r |             r = self._src_r | ||||||
|             if not r: |             if not r: | ||||||
|  | @ -357,17 +358,37 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
| 
 | 
 | ||||||
|                 # create a flattened view onto the OHLC array |                 # create a flattened view onto the OHLC array | ||||||
|                 # which can be read as a line-style format |                 # which can be read as a line-style format | ||||||
|                 # shm = self.shm |                 shm = self.shm | ||||||
|                 # self.flat = shm.unstruct_view(['open', 'high', 'low', 'close']) | 
 | ||||||
|  |                 # flat = self.flat = self.shm.unstruct_view(fields) | ||||||
|  |                 self.flat = self.shm.ustruct(fields) | ||||||
|  |                 self._iflat = self.shm._last.value | ||||||
|  | 
 | ||||||
|                 # import pdbpp |                 # import pdbpp | ||||||
|                 # pdbpp.set_trace() |                 # pdbpp.set_trace() | ||||||
|                 # x = self.x_basis = ( |                 # assert len(flat._array) == len(self.shm._array[fields]) | ||||||
|                 #     np.broadcast_to( | 
 | ||||||
|                 #         shm._array['index'][:, None], |                 x = self.x_basis = ( | ||||||
|                 #         # self.flat._array.shape, |                     np.broadcast_to( | ||||||
|                 #         self.flat.shape, |                         shm._array['index'][:, None], | ||||||
|                 #     ) + np.array([-0.5, 0, 0, 0.5]) |                         ( | ||||||
|  |                             shm._array.size, | ||||||
|  |                             # 4,  # only ohlc | ||||||
|  |                             self.flat.shape[1], | ||||||
|  |                         ), | ||||||
|  |                     ) + np.array([-0.5, 0, 0, 0.5]) | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |                 # fshm = self.flat = open_shm_array( | ||||||
|  |                 #     f'{self.name}_flat', | ||||||
|  |                 #     dtype=flattened.dtype, | ||||||
|  |                 #     size=flattened.size, | ||||||
|                 # ) |                 # ) | ||||||
|  |                 # fshm.push(flattened) | ||||||
|  | 
 | ||||||
|  |                 # print(f'unstruct diff: {time.time() - start}') | ||||||
|  |                 # profiler('read unstr view bars to line') | ||||||
|  |                 # start = self.flat._first.value | ||||||
| 
 | 
 | ||||||
|                 ds_curve_r = Renderer( |                 ds_curve_r = Renderer( | ||||||
|                     flow=self, |                     flow=self, | ||||||
|  | @ -407,7 +428,7 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
|             # - if we're **not** downsampling then we simply want to |             # - if we're **not** downsampling then we simply want to | ||||||
|             #   render the bars graphics curve and update.. |             #   render the bars graphics curve and update.. | ||||||
|             # - if insteam we are in a downsamplig state then we to |             # - if insteam we are in a downsamplig state then we to | ||||||
|             x_gt = 8 |             x_gt = 6 | ||||||
|             uppx = curve.x_uppx() |             uppx = curve.x_uppx() | ||||||
|             in_line = should_line = curve.isVisible() |             in_line = should_line = curve.isVisible() | ||||||
|             if ( |             if ( | ||||||
|  | @ -426,37 +447,43 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
| 
 | 
 | ||||||
|             # do graphics updates |             # do graphics updates | ||||||
|             if should_line: |             if should_line: | ||||||
|                 # start = time.time() |  | ||||||
|                 # y = self.shm.unstruct_view( |  | ||||||
|                 #     ['open', 'high', 'low', 'close'], |  | ||||||
|                 # ) |  | ||||||
|                 # print(f'unstruct diff: {time.time() - start}') |  | ||||||
|                 # profiler('read unstr view bars to line') |  | ||||||
|                 # # start = self.flat._first.value |  | ||||||
| 
 | 
 | ||||||
|                 # x = self.x_basis[:y.size].flatten() |                 # update flatted ohlc copy | ||||||
|                 # y = y.flatten() |                 iflat, ishm = self._iflat, self.shm._last.value | ||||||
|                 # profiler('flattening bars to line') |                 to_update = rfn.structured_to_unstructured( | ||||||
|                 # path, last = dsc_r.render(read) |                     self.shm._array[iflat:ishm][fields] | ||||||
|                 # x, flat = ohlc_flat_view( |                 ) | ||||||
|                 #     ohlc_shm=self.shm, |  | ||||||
|                 #     x_basis=x_basis, |  | ||||||
|                 # ) |  | ||||||
|                 # y = y.flatten() |  | ||||||
|                 # y_iv = y[ivl:ivr].flatten() |  | ||||||
|                 # x_iv = x[ivl:ivr].flatten() |  | ||||||
|                 # assert y.size == x.size |  | ||||||
| 
 | 
 | ||||||
|                 x, y = self.flat = ohlc_flatten(array) |                 # print(to_update) | ||||||
|                 x_iv, y_iv = ohlc_flatten(in_view) |                 self.flat[iflat:ishm][:] = to_update | ||||||
|                 profiler('flattened OHLC data') |                 profiler('updated ustruct OHLC data') | ||||||
|  | 
 | ||||||
|  |                 y_flat = self.flat[:ishm] | ||||||
|  |                 x_flat = self.x_basis[:ishm] | ||||||
|  | 
 | ||||||
|  |                 self._iflat = ishm | ||||||
|  | 
 | ||||||
|  |                 y = y_flat.reshape(-1) | ||||||
|  |                 x = x_flat.reshape(-1) | ||||||
|  |                 profiler('flattened ustruct OHLC data') | ||||||
|  | 
 | ||||||
|  |                 y_iv_flat = y_flat[ivl:ivr] | ||||||
|  |                 x_iv_flat = x_flat[ivl:ivr] | ||||||
|  | 
 | ||||||
|  |                 y_iv = y_iv_flat.reshape(-1) | ||||||
|  |                 x_iv = x_iv_flat.reshape(-1) | ||||||
|  |                 profiler('flattened ustruct in-view OHLC data') | ||||||
|  | 
 | ||||||
|  |                 # x, y = ohlc_flatten(array) | ||||||
|  |                 # x_iv, y_iv = ohlc_flatten(in_view) | ||||||
|  |                 # profiler('flattened OHLC data') | ||||||
| 
 | 
 | ||||||
|                 curve.update_from_array( |                 curve.update_from_array( | ||||||
|                     x, |                     x, | ||||||
|                     y, |                     y, | ||||||
|                     x_iv=x_iv, |                     x_iv=x_iv, | ||||||
|                     y_iv=y_iv, |                     y_iv=y_iv, | ||||||
|                     view_range=None,  # hack |                     view_range=(ivl, ivr),  # hack | ||||||
|                     profiler=profiler, |                     profiler=profiler, | ||||||
|                 ) |                 ) | ||||||
|                 profiler('updated ds curve') |                 profiler('updated ds curve') | ||||||
|  | @ -477,7 +504,7 @@ class Flow(msgspec.Struct):  # , frozen=True): | ||||||
|                 # graphics.setCacheMode(QtWidgets.QGraphicsItem.NoCache) |                 # graphics.setCacheMode(QtWidgets.QGraphicsItem.NoCache) | ||||||
|                 # graphics.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) |                 # graphics.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) | ||||||
| 
 | 
 | ||||||
|                 graphics.prepareGeometryChange() |                 # graphics.prepareGeometryChange() | ||||||
|                 graphics.update() |                 graphics.update() | ||||||
| 
 | 
 | ||||||
|             if ( |             if ( | ||||||
|  | @ -632,7 +659,7 @@ class Renderer(msgspec.Struct): | ||||||
| 
 | 
 | ||||||
|         ''' |         ''' | ||||||
|         # do full source data render to path |         # do full source data render to path | ||||||
|         last_read = ( |         ( | ||||||
|             xfirst, xlast, array, |             xfirst, xlast, array, | ||||||
|             ivl, ivr, in_view, |             ivl, ivr, in_view, | ||||||
|         ) = self.last_read |         ) = self.last_read | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue