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

[MRG] Improve DUL performance #651

Merged
merged 13 commits into from Dec 22, 2021

Conversation

mself
Copy link
Contributor

@mself mself commented Jun 21, 2021

Reference issue

Issue #650

What changed

Instead of sleeping before every event in run_reactor(), only sleep when there are no queued events to process.

This ensures that queued events are processed as fast as possible. The _run_loop_delay setting now only affects the case where there are no queued events (i.e. the system is idle).

After the performance optimization, pynetdicom C-STORE SCU is ~23x faster than before when transferring large DICOMs, such as the 2MB RTImageStorage.dcm (0.87 sec vs 19.66 sec):

Benchmark BEFORE:

1) C-STORE SCU transferred 100 total RTImageStorage.dcm datasets over 1 association in 19.66 s
9) C-STORE SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association in 20.85 s
10) C-STORE DCMTK SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association in 0.20 s

Benchmark AFTER:

1) C-STORE SCU transferred 100 total RTImageStorage.dcm datasets over 1 association in 0.87 s
9) C-STORE SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association in 1.41 s
10) C-STORE DCMTK SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association in 0.22 s

Now pynetdicom C-STORE SCU is only ~4x slower than DCMTK (0.87 sec vs. 0.22 sec) instead of ~100x for RTImageStorage.dcm.

The improvement is much more modest for small DICOMs, such as the 39KB CTImageStorage.dcm (only ~2x speedup).

The ultrasound images we typically see in our use case are ~200MB and we saw a ~30x speedup on C-STORE SCU with this change.

How it was tested

All unit tests pass. Some unit tests had to be updated to avoid timing-dependent failures.

Also, one unit test fails on master even before the changes:

  • pynetdicom/apps/tests/test_common.py:604: AssertionError: assert None == '' where None = getattr((300a, 00c0) Beam Number IS: None, 'BeamNumber')

Seems like there is an extra space in "Beam Number" somewhere?

I also tested the storescu.py app with a local instance of OsiriX Lite and it worked properly.

This PR is ready to merge.

- Added `nr_ds` to make it easy to configure the number of datasets to use in the benchmark
- Print the name of the DICOM file being used for the benchmark

Note that `pynetdicom` performance is worse for larger DICOM files, such as `RTImageStorage.dcm`:

```
9) C-STORE SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 20.88 s

10) C-STORE DCMTK SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 0.28 s
```

For `RTImageStorage.dcm`, `pynetdicom` is ~75x slower than DCMTK.
Instead of sleeping before every event, only sleep when there are no queued events to process.

This ensures that queued events are processed as fast as possible.  The `_run_loop_delay` setting now only affects the case where there are no queued events (i.e. the system is idle).

After the performance optimization, `pynetdicom` is 23x faster than before when transferring `RTImageStorage.dcm`:

New:
```
1) C-STORE SCU transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 0.85 s

9) C-STORE SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 1.43 s

10) C-STORE DCMTK SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 0.29 s
```

Old:
```
1) C-STORE SCU transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 19.69 s

9) C-STORE SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 20.88 s

10) C-STORE DCMTK SCU/SCP transferred 100 total RTImageStorage.dcm datasets over 1 association(s) in 0.28 s

```

Now `pynetdicom` is only ~5x slower than DCMTK instead of ~75x.
When single-stepping the reactor, sleep between events so that test code has time to run.
- Prompt to use large or small dataset
- Prompt for number of datasets to use (with default)
- Run multiple benchmarks in a single run
- Display write option in output of `receive_store()`
- Support running all tests, ranges of tests, or lists of tests
- Copy working `sleep()` timing pattern from `TestUPSFindServiceClass::test_scp_cancelled()` to 3 instances of `test_scp_cancelled()` in `test_service_qr.py`
@mself mself changed the title [WIP] Improve DUL performance [MRG] Improve DUL performance Jun 22, 2021
@codecov
Copy link

codecov bot commented Jun 23, 2021

Codecov Report

Merging #651 (c761804) into master (bc8fd99) will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##            master      #651   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           28        28           
  Lines         8609      8618    +9     
=========================================
+ Hits          8609      8618    +9     
Impacted Files Coverage Δ
pynetdicom/dul.py 100.00% <100.00%> (ø)
pynetdicom/events.py 100.00% <100.00%> (ø)

pynetdicom/events.py Outdated Show resolved Hide resolved
pynetdicom/events.py Outdated Show resolved Hide resolved
@scaramallion
Copy link
Member

Can you change these lines to:

for elem in iter(ds):
    if elem.VR == 'SQ':

To fix the typing error

@scaramallion
Copy link
Member

scaramallion commented Jun 23, 2021

You'll also need to add an explicit test for DULServiceProvider.stop_dul() (these lines) since it's harder to trigger now that the DUL reactor is looping so much faster.

@scaramallion
Copy link
Member

The failing test in test_common is due to an issue in the current pydicom, it's been fixed in master.

- Add unit testing for `NotificationEvent::__str__()`
- Add unit testing for `InterventionEvent::__str__()`
- Remove `Event::__str__()` since it wasn't reliable/complete
- Convert to f-strings in `benchmark_script.py`
- Fix type error in `dsutils.py::pretty_dataset()`
@mself
Copy link
Contributor Author

mself commented Jun 24, 2021

I don't understand the state machine well enough to create a new unit test for DULServiceProvider.stop_dul().

@scaramallion
Copy link
Member

I don't understand the state machine well enough to create a new unit test for DULServiceProvider.stop_dul().

OK, let me think about it a bit

Test that `stop_dul()` returns `True` when in `Sta1`
@mself
Copy link
Contributor Author

mself commented Jun 29, 2021

I added a unit test to verify that stop_dul() returns True when in Sta1. Other unit tests already cover the case where stop_dul() returns False when in other states. This should restore the code coverage to 100%.

@mself
Copy link
Contributor Author

mself commented Jul 2, 2021

@scaramallion Can you please kick off the workflow for this PR to verify that all of the checks now pass? Thanks!

@mself
Copy link
Contributor Author

mself commented Jul 8, 2021

@scaramallion Just checking in on this again :-)

@mself
Copy link
Contributor Author

mself commented Jul 29, 2021

Hi there, @scaramallion. Any update on this? It's been a month. Thanks.

@stefanoostwegel
Copy link

I'm really looking forward for this improvement, when can we expect this to be released?

@scaramallion scaramallion merged commit 735c72e into pydicom:master Dec 22, 2021
@scaramallion
Copy link
Member

Thanks!

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.

None yet

3 participants