Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart graphics with drawing speedups #109

Open
goodboy opened this issue Aug 24, 2020 · 4 comments
Open

Chart graphics with drawing speedups #109

goodboy opened this issue Aug 24, 2020 · 4 comments
Assignees
Labels
data-layer real-time and historical data processing and storage feature-request New feature or request graphics (charting related) geometry chops UI

Comments

@goodboy
Copy link
Contributor

goodboy commented Aug 24, 2020

Had a great chat with a collaborator discussing some options for squeezing performance out of pyqtgraph.
A lot was confirming ideas I had previous but a few nuggets are going to be particularly fun to experiment with.
Also note, there is no real latency problem yet but this is anticipation of updating multiple assets per plot.

This will all be more relevant once the first draft of #10 lands 🏄‍♂️ (soooon).

As part of that landing will come a naive charting update system using some well known slowly-ness like np.append()/np.concatenate() (which frankly aren't anywhere near being a bottleneck yet) but which we can of course improve upon.

The tip(s) received for improving rendering latency and general datum compacting include:

  • charts are resized much less frequently than interactions within them - allocate a numpy array 4x the size, and use numba to do the subsetting (aka downsampling in graphics land)
  • the general strategy for said downsampling (if drawing tick-like, event triggered lines) is something like:

    only 4 points per pixel
    you bin by the width of the chart, and put the entry val, min, max, exit in that bin
    then it will be as accurate looking as the full data

  • pyqtgraph has some similar implementations for lines in the code base that could be potentially improved with this:
    • the clipToView algo code
    • the downSampleMethod == 'peak' code
      • the peak method is closest but doesn't account for the entry and exit points of the bin and it's doing two passes to compute min/max, one thing is that since they can only use numpy they are doing things a bit sub-optimally
        with multiple passes instead of one

  • also there's a cache friendly version of binary search called galloping search (or exponential search), instead of jumping back and forth in the array you only go in one direction until the last bin, works better for large arrays (the final bin is done with binary search)

  • you can also build variations of the numba code to do stepped rendering, which looks better for financial data
    where the price is constant until the next tick


For updating the data layer instead of using the extremely naive np.append()/concatenate() (which is stupidly slow):

  • you need to treat it as a std::vector resize - basically allocate 1.25 the size of the old array and memcopy

  • numpy in this case just stands in for malloc to allocate a block of contiguous memory in python

  • numpy + numba gives you a bit of a systems lang (at least for numeric calcs)

  • one nice thing about numba is you can compile the code on the fly, so if you were setting up a streaming data calc, you could use a bit of python's meta facilites to do some loop unrolling and function call fusion and compile one combined execution kernel (if you built a system that was composable)
    and then at the start of the system it could compile down all your strategies to fairly optimized machine code

This last point gets into more ideas we've had surrounding making a fast FSP stream processing system as per #106, #102, #107. Ideally we move towards a small DSL for compiling numba chained routines that can be easily declared from UI components.

@goodboy goodboy changed the title Chart graphics, drawing speedups Chart graphics with drawing speedups Aug 24, 2020
@goodboy
Copy link
Contributor Author

goodboy commented Oct 26, 2020

Thanks to finding pyqtgraph/pyqtgraph#1418 most of the CPU usage and latency issues are actually completely solved now!
We'll likely depend on our own fork until that fix lands (which may be a while given the constraints with Qt4).

The latency measures I've been sampling on lines and cursor draw cycles still does scale approximately as

latency = X_bars/1000 * 1ms

So, an attempt at creating lines graphics segments (of say 500 bars per slice) would likely improve latency when the user is viewing smaller sets of data when watching real-time / shorter time frames.

I've already toyed with this idea where we are drawing separate pictures for history vs. the current bar to avoid calling QPicture.drawLines() more then necessary and with as little data as possible per update. Making BarsItems hold an array of QPictures which are draw based on the bars in view will likely give that little boost when viewing near term data that we're after.

@goodboy
Copy link
Contributor Author

goodboy commented Oct 26, 2020

Oh, also we've removed the np.append() stuff and now have a new shared mem subsystem coming in #112, so all that discussion can be marked solved.

@goodboy
Copy link
Contributor Author

goodboy commented Nov 24, 2020

Good news.

Got a massive massive latency fix with large(r) data sets by using pyqtgraphs functions.arrayToQPath() to generate a QPainterPath with gaps in it to make up bars graphics. Turns out this scales not only better for initial draws but also results in less QGraphicsObject.paint() latency when zoomed in (was ~1m / 1k bars but now is ~3ms / 15k bars).

The only outstanding is getting appends to the historical bars path to be fast; this is still ongoing.

goodboy added a commit that referenced this issue Dec 14, 2020
Pertains further to #109.

Instead of redrawing the entire `QPainterPath` every time there's
a historical bars update just use `.addPath()` to slap in latest
history. It seems to work and is fast. This also seems like it will be
a great strategy for filling in earlier data, woot!
goodboy added a commit that referenced this issue Dec 17, 2020
Pertains further to #109.

Instead of redrawing the entire `QPainterPath` every time there's
a historical bars update just use `.addPath()` to slap in latest
history. It seems to work and is fast. This also seems like it will be
a great strategy for filling in earlier data, woot!
goodboy added a commit that referenced this issue Dec 19, 2020
Pertains further to #109.

Instead of redrawing the entire `QPainterPath` every time there's
a historical bars update just use `.addPath()` to slap in latest
history. It seems to work and is fast. This also seems like it will be
a great strategy for filling in earlier data, woot!
@goodboy goodboy self-assigned this Feb 16, 2021
@goodboy goodboy added data-layer real-time and historical data processing and storage feature-request New feature or request UI viz labels Feb 16, 2021
goodboy added a commit that referenced this issue Jun 15, 2021
Adding binance's "hft" ws feeds has resulted in a lot of context
switching in our Qt charts, so much so it's chewin CPU and definitely
worth it to throttle to the detected display rate as per discussion in
issue #192.

This is a first very very naive attempt at throttling L1 tick feeds on
the `brokerd` end (producer side) using a constant and uniform delivery
rate by way of a `trio` task + mem chan.  The new func is
`data._sampling.uniform_rate_send()`. Basically if a client request
a feed and provides a throttle rate we just spawn a task and queue up
ticks until approximately the next display rate's worth period of time
has passed before forwarding. It's definitely nothing fancy but does
provide fodder and a start point for an up and coming queueing eng to
start digging into both #107 and #109 ;)
@goodboy goodboy added the graphics (charting related) geometry chops label Mar 9, 2022
goodboy added a commit that referenced this issue Mar 9, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Mar 11, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
@goodboy goodboy mentioned this issue Mar 11, 2022
4 tasks
goodboy added a commit that referenced this issue Mar 18, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 7, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 7, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 7, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 8, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 8, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 10, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 10, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 11, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 11, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 11, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 11, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 13, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 13, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 16, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 16, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 16, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 16, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
goodboy added a commit that referenced this issue Apr 30, 2022
In effort to start getting some graphics speedups as detailed in #109,
this adds a `FastAppendCurve`to every `BarItems` as a `._ds_line` which
is only displayed (instead of the normal mult-line bars curve) when the
"width" of a bar is indistinguishable on screen from a line -> so once
the view coordinates map to > 2 pixels on the display device.
`BarItems.maybe_paint_line()` takes care of this scaling detection logic and is
called by the associated view's `.sigXRangeChanged` signal handler.
goodboy added a commit that referenced this issue Apr 30, 2022
Obviously determining the x-range from indices was wrong and was the
reason for the incorrect (downsampled) output size XD. Instead correctly
determine the x range and start value from the *values of* the input
x-array. Pretty sure this makes the implementation nearly production
ready.

Relates to #109
@goodboy goodboy removed the viz label Mar 23, 2023
@goodboy
Copy link
Contributor Author

goodboy commented Mar 27, 2023

Linking some Qt core issues that have been introduced by pyqtgraph team:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data-layer real-time and historical data processing and storage feature-request New feature or request graphics (charting related) geometry chops UI
Projects
None yet
Development

No branches or pull requests

1 participant