-
Notifications
You must be signed in to change notification settings - Fork 68
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
Reimplement managed raster class in C++ #1597
base: feature/routing-refactor
Are you sure you want to change the base?
Reimplement managed raster class in C++ #1597
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really great @emlys ! I had a few questions, but I think they're all pretty minor. Happy to talk through anything that would be helpful!
setup.py
Outdated
if platform.system() == 'Darwin': | ||
compiler_and_linker_args = ['-stdlib=libc++'] | ||
compiler_args = [] | ||
compiler_and_linker_args = ['-stdlib=libc++', '-std=c++20'] | ||
library_dirs = [f'{os.environ["CONDA_PREFIX"]}/lib'] | ||
else: | ||
compiler_args = ['/std:c++20'] | ||
library_dirs = [f'{os.environ["CONDA_PREFIX"]}/Library/lib'] | ||
include_dirs.append(f'{os.environ["CONDA_PREFIX"]}/Library/include') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's possible that someone would compile on Linux, would the /srd:c++20
flag for with GNU? Or do we need to handle the linux possibility separately?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we been supporting Linux compilation previously? I think this would not work, but if it didn't work before, maybe that's a separate question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's never been a priority in this repository, but we do offer linux builds via conda-forge so I don't think we should completely ignore the use case. Linux support has historically been kind of informal in this repo.
raster_x_size = dataset->GetRasterXSize(); | ||
raster_y_size = dataset->GetRasterYSize(); | ||
|
||
if (band_id < 1 or band_id > dataset->GetRasterCount()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fascinating, I wasn't aware that or
was a keyword here in C++! Looks like their alternative keywords can be pretty cryptic, but this one is quite readable.
closed = 0; | ||
} | ||
|
||
void set(long xi, long yi, double value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I remember Rich using the inline
keyword here as well to try and eke out some minor speedup since the function was so small. Is there any noticeable speedup here by using void inline set(...
? There might not be, and the function is long enough where there probably isn't a noticeable speedup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing a measurable speedup on the SDR sample data from inlining this (or the other function below). As a guideline, the Google C++ style guide recommends against inlining functions over about 10 lines, so these are on the high end of that. But since they were originally inlined in the Cython version, I don't see any harm in making them inlined here!
-> Caveat that I'm not at all confident that my speed testing will detect relatively small changes. I get a lot of variation in times.
} | ||
} | ||
|
||
double get(long xi, long yi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here about whether there's any speedup from adding the inline
keyword.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above
cdef ManagedRaster stream_raster = ManagedRaster( | ||
stream_path.encode('utf-8'), 1, False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine once we have the python C interface for logging figured out that we might be able to do this string encoding as well!
src/natcap/invest/ndr/ndr_core.pyx
Outdated
neighbor = up_iterator.next() | ||
while neighbor.direction < 8: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember one of the things we talked about last week was the need to be careful to cover all of the iteration cases (e.g. continue
, end of loop block) with calling up_iterator.next()
. What if we just handled that up here at the top of the loop? I suppose there's a slight cost to holding 2 neighbor objects in memory, but maybe it would be easier to maintain? I'm thinking of something like the below (github wouldn't allow me to suggest something on the whole loop block)
neighbor = up_iterator.next() | |
while neighbor.direction < 8: | |
neighbor = up_iterator.next() | |
next_neighbor = neighbor | |
while neighbor.direction < 8: | |
neighbor = next_neighbor | |
next_neighbor = up_iterator.next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@phargogh I think I've found an even better solution which is to properly implement the iterators to meet the C++ iterator specification. That way we don't need to manually call next()
at all. I was stumped on that before but I think I've worked it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo! That is super exciting. Well done and I can't wait to see the iterator!
src/natcap/invest/sdr/sdr_core.pyx
Outdated
up_iterator = UpslopeNeighborIterator( | ||
mfd_flow_direction_raster, global_col, global_row) | ||
neighbor = up_iterator.next() | ||
while neighbor.direction < 8: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here about whether it'd make sense to track a neighbor/next neighbor object in the loop to make it easier on us to maintain and avoid infinite loops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember we talked about this file, but I forget if it's intended to continue to be in the PR. Should this file be removed as a part of the PR or should it be retained for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, it's meant to be removed!
for i, et0_path in enumerate(et0_path_list): | ||
et0_m_rasters.push_back(ManagedRaster(et0_path.encode('utf-8'), 1, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the enumeration still needed? It looks like there are a few more enumerations in the next 30 lines or so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed!
Co-authored-by: James Douglass <[email protected]>
Description
This PR reimplements the
ManagedRaster
class in C++ for better performance. Tested withsdr.calculate_sediment_deposition
and the SDR sample data, it's now just slightly faster than the original implementation onmain
(0.59 vs 0.61 seconds), whereas the python implementation onfeature/routing-refactor
takes nearly twice as long.I'm a beginner at C++ so I'm sure I'm not always using the right conventions or design patterns, but this seems like an okay starting point. I took some guidance from the Google C++ style guide. Ignoring some guidelines that don't seem to make sense in this context. For instance, I have all the code in a header file because splitting it out into
ManagedRaster.h
andManagedRaster.cpp
significantly slowed it down.Happy to discuss and walk through this PR on a zoom call as well.
Checklist