Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

API for optimized access order#453

Merged
mgeplf merged 9 commits intomasterfrom
1uc/optimized-access-order-api
May 31, 2023
Merged

API for optimized access order#453
mgeplf merged 9 commits intomasterfrom
1uc/optimized-access-order-api

Conversation

@1uc
Copy link
Copy Markdown
Contributor

@1uc 1uc commented May 22, 2023

This MR proposes an API which will enable us to optimize morphology loading for certain reorderable loops. The target are loops which know upfront the names of the morphologies they need to load, and don't require them to be loaded in a particular order.

The types of optimization this unlocks are:

  • Reordering the loop to reduces large strides in the file.
  • Loading morphologies in (small) batches.

1uc added 3 commits May 22, 2023 10:36
This commit introduces two helper classes that reduce the verbosity
of `std::enable_if<...>` constructs.
@1uc 1uc force-pushed the 1uc/optimized-access-order-api branch from 280d929 to 83bcfb1 Compare May 22, 2023 08:37
@1uc 1uc marked this pull request as ready for review May 22, 2023 09:43
Copy link
Copy Markdown
Member

@matz-e matz-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. load_unordered seems a fitting name, I guess something like load_optimized or just bulk_load would be obfuscating the biggest caveat (not loading the list as specified).

@1uc 1uc marked this pull request as draft May 23, 2023 11:29
@1uc
Copy link
Copy Markdown
Contributor Author

1uc commented May 23, 2023

Back to draft, because the internals can be simplified.

1uc added 2 commits May 23, 2023 15:19
The point of this API is to enable collections to load morphologies
in any order they see fit. For HDF5 containers tests have shown that the
access pattern matters a lot in terms of performance.

The idea is a generic API that allows optimizing the iteration order for
reorderable loops, e.g.,

    for k, morph_name in enumerate(morphology_names):
        morph = collection.load(morph_name)
        f(k, morph)

can be replaced with

    for k, morph in collection.load_unordered(morphology_names):
        assert collection.load(morphology_names[k]) == morph
        f(k, morph)

This commit only adds the minimum required for this to work. In
particular, it doesn't optimize the access pattern.
This commit implement the optimized access order for merged containers.
@1uc 1uc force-pushed the 1uc/optimized-access-order-api branch from ab7c787 to 1891e88 Compare May 23, 2023 13:19
@1uc
Copy link
Copy Markdown
Contributor Author

1uc commented May 23, 2023

This PR now also includes an optimized version for merged containers.

@1uc 1uc marked this pull request as ready for review May 23, 2023 14:11
Comment thread binds/python/bind_misc.cpp
Comment thread binds/python/bind_misc.cpp
Comment thread include/morphio/collection.h Outdated
template <class U = M>
typename enable_if_mutable<U, std::pair<size_t, M>>::type operator*() const;

void operator++() const;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prob. not important, but I tend to like the symmetry of having operator++(int)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then we'd probably better also change the return type to const Iterator& and Iterator (or whatever the proper return types are), otherwise the distinction makes little sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've had to redo the LoadUnordered<M>::Iterator, it had multiple bugs related to unintended shallow copying. Tests have been added to check for this type of issue.

1uc added 2 commits May 24, 2023 17:27
Also this restructures the file such that we declare in the header, then
in the source file first we define; and then explicitly instantiate.
@1uc
Copy link
Copy Markdown
Contributor Author

1uc commented May 30, 2023

Should we allow random access? This would matter in loops which look like this:

std::vector<std::string> morphology_names = ...;
auto load_unordered = collection.load_unordered(morphology_names);

// Add some mechanism which doesn't guarantee the order in which
// chunks are accessed. For example
#pragma omp parallel for
for(size_t chunk = 0; chunk < n_chunks; ++chunk) {
    size_t offset = chunk*chunk_size;
    process_chunk(chunk_size, load_unordered.begin()+offset);
}

void process_chunk(size_t chunk_size, LoadUnordered<Morphology>::Iterator it) {
    for(size_t i = 0; i < chunk_size; ++i) {
        auto [k, morph] = *(it++);
        // computations  
    }
}

But technically the above doesn't traverse the morphologies in the optimal order. However, if we make the iterator shared, then we have more thread-safety concerns.

What would be more useful in TD would be access to the internal argsort:

auto morphology_names = ...;
auto collection = ...;

auto access_order = morphio::argsort(collection, morphology_names);
for(size_t i : access_order) {
    auto morph_name = morphology_names[i];
    process(morph_name);
}

@matz-e
Copy link
Copy Markdown
Member

matz-e commented May 30, 2023

Seams reasonable? Although, for "user-friendliness", maybe just sorting the morphology names without a round-robin trip through indices on the user side would be nicer?

@1uc
Copy link
Copy Markdown
Contributor Author

1uc commented May 30, 2023

Sorting the morphology names directly loses valuable information, e.g. it prevents one from looking up auxiliary information if things are stored in vectors of equal size, with the convention that index i can be used to retrieve data for that morphology, e.g. morphology_names[i], metadata[i], etc.

More concretely in TD we have the case that we store stuff in a big std::vector<MetaData> then we need to fish stuff out of it by passing the indices of the morphologies we want to load, which then grabs the morphology name and uses (a wrapper of) morphio::Collection::load to load the morphology. I don't see us injecting the iterator into that setup easily, because the step of selecting (which requires the optimized order) and loading the morphology are separated by several function calls in TD. However, they always happen together with the iterator approach. Note that knowing only the order of the names of the morphologies to be loaded is insufficient (or inefficient) in TD.

@1uc 1uc force-pushed the 1uc/optimized-access-order-api branch 3 times, most recently from 527fc9d to d2cd472 Compare May 31, 2023 09:11
This commit allows users to perform an argsort of the morphology names they
want to load. This can be useful in situations where it's hard to pass the
iterator, e.g. through multiple layers of function calls.
@1uc 1uc force-pushed the 1uc/optimized-access-order-api branch from d2cd472 to 83b91f3 Compare May 31, 2023 09:30
@1uc 1uc force-pushed the 1uc/optimized-access-order-api branch from 78def01 to 89a76ea Compare May 31, 2023 11:17
@mgeplf mgeplf merged commit d4b026e into master May 31, 2023
@mgeplf mgeplf deleted the 1uc/optimized-access-order-api branch May 31, 2023 12:24
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants