Skip to content

perform arrayToQPath in chunks#1965

Merged
j9ac9k merged 6 commits intopyqtgraph:masterfrom
pijyoi:qpath_chunks
Aug 13, 2021
Merged

perform arrayToQPath in chunks#1965
j9ac9k merged 6 commits intopyqtgraph:masterfrom
pijyoi:qpath_chunks

Conversation

@pijyoi
Copy link
Copy Markdown
Contributor

@pijyoi pijyoi commented Aug 12, 2021

This PR optimizes arrayToQPath() with the following techniques:

  1. creation of path in chunks
    • this reduces the amount of temporary memory being occupied by QPolygonF
  2. reserving memory used by QPainterPath.
    • internally, elements are appended into QPainterPath one-by-one.
    • reserving avoids memory re-allocation and the resultant copying
  3. deferring patching of non-finite values
    • this avoids creation of new copies of input data x and y by patching directly into the final buffer

The code was restructured and refactored to allow easier reasoning and to prevent changes in one mode from affecting another mode. Despite the larger code, I believe that the code is easier to follow now than before.

master (PySide6 / Windows)

[ 75.00%] ··· arrayToQPath.TimeSuiteAllFinite.time_test
[ 75.00%] ··· ========= ============= ============ ============= ============
              --                                param2
              --------- -----------------------------------------------------
                param1       all         finite        pairs        array
              ========= ============= ============ ============= ============
                10000     90.7±0.3μs   95.7±0.5μs   1.03±0.01ms    1.06±0ms
                100000   2.35±0.02ms   2.50±0.1ms   11.9±0.09ms   12.0±0.1ms
               1000000    26.0±0.3ms   26.8±0.1ms    121±0.8ms    119±0.7ms
              ========= ============= ============ ============= ============

[100.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                                          ok
[100.00%] ··· ========= ============= ============= ============ =============
              --                                param2
              --------- ------------------------------------------------------
                param1       all          finite       pairs         array
              ========= ============= ============= ============ =============
                10000      448±2μs       181±1μs      1.52±0ms      1.54±0ms
                100000   6.49±0.02ms   3.06±0.01ms   17.5±0.1ms   17.6±0.08ms
               1000000    65.2±0.3ms    33.1±0.2ms   175±0.5ms     174±0.3ms
              ========= ============= ============= ============ =============

this PR (PySide6 / Windows)

[ 75.00%] ··· arrayToQPath.TimeSuiteAllFinite.time_test                                                              ok
[ 75.00%] ··· ========= ============= ============= ============= =============
              --                                 param2
              --------- -------------------------------------------------------
                param1       all          finite        pairs         array
              ========= ============= ============= ============= =============
                10000     81.4±0.2μs    91.1±0.4μs   1.06±0.01ms   1.10±0.03ms
                100000   1.31±0.02ms   1.41±0.03ms   10.9±0.04ms   11.1±0.04ms
               1000000   13.4±0.06ms   14.1±0.07ms    112±0.6ms     110±0.3ms
              ========= ============= ============= ============= =============

[100.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                                          ok
[100.00%] ··· ========= ============= ============= ============= =============
              --                                 param2
              --------- -------------------------------------------------------
                param1       all          finite        pairs         array
              ========= ============= ============= ============= =============
                10000      179±5μs      115±0.2μs    1.16±0.03ms   1.18±0.01ms
                100000   2.59±0.03ms   1.37±0.01ms    12.2±0.1ms   12.3±0.09ms
               1000000    21.9±0.1ms    14.6±0.2ms    125±0.4ms     122±0.2ms
              ========= ============= ============= ============= =============

pijyoi added 4 commits August 10, 2021 11:57
"all-finite" includes the case where user requests to skip finiteCheck.
this restructuring removes inter-dependencies between the various modes.
this makes it easier to reason about and modify the various codepaths.
this allows us to defer the backfilling
@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 12, 2021

@pijyoi the diff looks good, on macOS I'm not seeing any significant difference in performance, which is somewhat odd. I'll test on my windows machine as well.

For the sake of my record, after cherry-picking the benchmark file, and running on master, these are the results I got:

EDIT: removed bogus results

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Aug 12, 2021

You may want to run it on Qt6.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 12, 2021

You may want to run it on Qt6.

oh you know the issue is, I was using asv run which doesn't run on your current active branch, it runs on the master branch it would appear...

EDIT: confirmed, it ran on master, and if I copied + pasted one more line from my console output, that would have been obvious 🤦🏻

I'm re-running now.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 12, 2021

this PR

pyqtgraph-asv ❯ asv run -b arrayToQPath
· Creating environments
· Discovering benchmarks
·· Uninstalling from virtualenv-py3.8-numpy-pyqt6.
·· Building 44e32f00 <qpath_chunks> for virtualenv-py3.8-numpy-pyqt6.
·· Installing 44e32f00 <qpath_chunks> into virtualenv-py3.8-numpy-pyqt6.
· Running 8 total benchmarks (2 commits * 2 environments * 2 benchmarks)
[  0.00%] · For pyqtgraph commit 44e32f00 <qpath_chunks>:
[  0.00%] ·· Building for virtualenv-py3.8-numpy-pyqt6
[  0.00%] ·· Benchmarking virtualenv-py3.8-numpy-pyqt6
[  6.25%] ··· Running (arrayToQPath.TimeSuiteAllFinite.time_test--)..
[ 18.75%] ··· arrayToQPath.TimeSuiteAllFinite.time_test                                              ok
[ 18.75%] ··· ========= ============= ============ ============ ============
              --                               param2
              --------- ----------------------------------------------------
                param1       all         finite       pairs        array
              ========= ============= ============ ============ ============
                10000      147±4μs      149±10μs     921±40μs     922±30μs
                100000   1.33±0.02ms   1.53±0.2ms   8.99±0.3ms   9.11±0.3ms
               1000000    17.2±0.3ms   18.0±0.5ms    130±30ms     103±2ms
              ========= ============= ============ ============ ============

[ 25.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                          ok
[ 25.00%] ··· ========= ============= ============= ============= ============
              --                                param2
              --------- ------------------------------------------------------
                param1       all          finite        pairs        array
              ========= ============= ============= ============= ============
                10000      221±10μs      191±4μs     1.08±0.06ms    977±20μs
                100000   2.75±0.07ms   1.78±0.07ms    9.49±0.2ms   10.1±0.2ms
               1000000    31.3±0.5ms    18.5±0.5ms     144±50ms     103±1ms
              ========= ============= ============= ============= ============

[ 25.00%] ·· Building for virtualenv-py3.8-numpy-pyside6....
[ 25.00%] ·· Benchmarking virtualenv-py3.8-numpy-pyside6
[ 31.25%] ··· Running (arrayToQPath.TimeSuiteAllFinite.time_test--)..
[ 43.75%] ··· arrayToQPath.TimeSuiteAllFinite.time_test                                              ok
[ 43.75%] ··· ========= ============= ============= ============ ============
              --                                param2
              --------- -----------------------------------------------------
                param1       all          finite       pairs        array
              ========= ============= ============= ============ ============
                10000      145±7μs       159±9μs      903±30μs     915±70μs
                100000   1.34±0.02ms   1.43±0.03ms   8.79±0.4ms   8.77±0.2ms
               1000000    17.4±0.9ms    17.8±0.3ms    95.4±4ms    95.0±0.7ms
              ========= ============= ============= ============ ============

[ 50.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                          ok
[ 50.00%] ··· ========= ============ ============= ============ ============
              --                               param2
              --------- ----------------------------------------------------
                param1      all          finite       pairs        array
              ========= ============ ============= ============ ============
                10000     218±5μs       187±7μs      983±30μs     987±30μs
                100000   2.78±0.2ms    1.54±0.2ms   9.48±0.3ms   9.61±0.5ms
               1000000   31.1±0.2ms   17.1±0.02ms    101±1ms     101±0.6ms
              ========= ============ ============= ============ ============

master branch

[ 50.00%] · For pyqtgraph commit a537bc2b <master>:
[ 50.00%] ·· Building for virtualenv-py3.8-numpy-pyqt6..
[ 50.00%] ·· Benchmarking virtualenv-py3.8-numpy-pyqt6
[ 56.25%] ··· Running (arrayToQPath.TimeSuiteAllFinite.time_test--)..
[ 68.75%] ··· arrayToQPath.TimeSuiteAllFinite.time_test                                              ok
[ 68.75%] ··· ========= ============ ============= ============ ============
              --                               param2
              --------- ----------------------------------------------------
                param1      all          finite       pairs        array
              ========= ============ ============= ============ ============
                10000     167±20μs      159±5μs      939±40μs     914±20μs
                100000   3.03±0.2ms   2.98±0.07ms   10.6±0.5ms   10.2±0.1ms
               1000000    31.2±1ms      33.9±1ms     111±6ms      110±2ms
              ========= ============ ============= ============ ============

[ 75.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                          ok
[ 75.00%] ··· ========= ============ ============ ============= =============
              --                                param2
              --------- -----------------------------------------------------
                param1      all         finite        pairs         array
              ========= ============ ============ ============= =============
                10000     581±30μs     289±20μs    1.54±0.08ms   1.56±0.08ms
                100000   7.46±0.1ms   4.19±0.1ms    16.7±0.3ms    16.6±0.4ms
               1000000    77.7±2ms    42.3±0.9ms     169±6ms       169±2ms
              ========= ============ ============ ============= =============

[ 75.00%] ·· Building for virtualenv-py3.8-numpy-pyside6..
[ 75.00%] ·· Benchmarking virtualenv-py3.8-numpy-pyside6
[ 81.25%] ··· Running (arrayToQPath.TimeSuiteAllFinite.time_test--)..
[ 93.75%] ··· arrayToQPath.TimeSuiteAllFinite.time_test                                              ok
[ 93.75%] ··· ========= ============= ============ ============= ============
              --                                param2
              --------- -----------------------------------------------------
                param1       all         finite        pairs        array
              ========= ============= ============ ============= ============
                10000      174±2μs      185±2μs     1.00±0.02ms    995±20μs
                100000   3.35±0.08ms   2.72±0.7ms    11.8±0.3ms   12.1±0.3ms
               1000000     36.8±1ms     35.3±1ms      118±2ms      114±1ms
              ========= ============= ============ ============= ============

[100.00%] ··· arrayToQPath.TimeSuiteWithNonFinite.time_test                                          ok
[100.00%] ··· ========= ============ ============ ============= ============
              --                               param2
              --------- ----------------------------------------------------
                param1      all         finite        pairs        array
              ========= ============ ============ ============= ============
                10000     644±20μs     334±20μs    1.69±0.08ms   1.83±0.1ms
                100000   8.42±0.3ms   4.64±0.1ms    17.6±0.3ms    18.1±2ms
               1000000   77.2±0.4ms    41.5±1ms      173±2ms      174±4ms
              ========= ============ ============ ============= ============

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 12, 2021

@pijyoi I was about to point out that I'm not noticing much difference on the speed test results, but after cranking up the samples on PlotSpeedTest.py (love that use of parameter tree btw, thanks for that!) you can notice a difference in FPS between master and this PR.

Looking a bit more closely at the results, you seem to have narrowed the gap significantly in terms of performance difference between PyQt6 and PySide6, to the point they're indistinguishable.

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Aug 12, 2021

Your results show "pairs" to be markedly slower on PyQt6 (at 1e6 samples) despite no binding-specific code.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 12, 2021

Your results show "pairs" to be markedly slower on PyQt6 (at 1e6 samples) despite no binding-specific code.

saw that too, but look at the +/- values, there looks to be some uncertainty about those test results.

for the fun of it, I ran PlotSpeedTest.py with 1e6 samples and profiled the results after 100 updates; attached is the image of the time spent; I won't bother including the image for master but needless to say the overall time spent in arrayToQPath is definitely lower. Nice work!

I'm going to leave this open for a bit to give anyone else an opportunity to comment 👍🏻

Uploading output_qpath_chunks_1e6.png…

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Aug 13, 2021

I am not sure why, but the CI failure logs for Windows / Python 3.7 / {PyQt, PySide} show that matplotlib-3.4.3.tar.gz got downloaded and built...

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 13, 2021

Looks like matplotlib just released that version, possible the relevant wheels weren't available when the CI ran. I'll rerun when I hop on a computer.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 13, 2021

image

nice recognition that it's downloading the source distribution; not a wheel... wonder why that's happening, clearly the wheel exists for python 3.7/windows.

Anyway let's not let this failure hold up this PR; I'll try and take a closer look later.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 13, 2021

Thanks for the PR @pijyoi this LGTM, merging. I didn't think there was room to make arrayToQPath faster, but clearly what do I know 😆

If we want to speed up line-plots any more, I suspect the next place to do it is in the downsample methods in PlotDataItem.py ...but that has its own issues.

Thanks again!

@j9ac9k j9ac9k merged commit e752336 into pyqtgraph:master Aug 13, 2021
@pijyoi pijyoi deleted the qpath_chunks branch August 14, 2021 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants