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

Commit d4b026e

Browse files
authored
API for optimized access order in Collection (#453)
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)
1 parent d186bc2 commit d4b026e

8 files changed

Lines changed: 611 additions & 33 deletions

File tree

3rdparty/HighFive

Submodule HighFive updated 113 files

binds/python/bind_misc.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,87 @@ void bind_misc(py::module& m) {
323323
"options"_a = morphio::enums::Option::NO_MODIFIER,
324324
"mutable"_a = false,
325325
"Load the morphology named 'morph_name' form the collection.")
326+
.def(
327+
"load_unordered",
328+
[](morphio::Collection* collection,
329+
std::vector<std::string> morphology_names,
330+
unsigned int options,
331+
bool is_mutable) -> py::object {
332+
if (is_mutable) {
333+
return py::cast(
334+
collection->load_unordered<morphio::mut::Morphology>(morphology_names,
335+
options));
336+
} else {
337+
return py::cast(
338+
collection->load_unordered<morphio::Morphology>(morphology_names, options));
339+
}
340+
},
341+
"morphology_names"_a,
342+
"options"_a = morphio::enums::Option::NO_MODIFIER,
343+
"mutable"_a = false,
344+
R"(Create an iterable of loop index and morphology.
345+
346+
When reading from containers, the order in which morphologies are read can
347+
have a large impact on the overall time to load those morphologies.
348+
349+
This iterator provides means of reordering loops to optimize the access
350+
pattern. Loops such as the following
351+
352+
for k, morph_name in enumerate(morphology_names):
353+
morph = collection.load(morphology_names[k])
354+
f(k, morph)
355+
356+
can be replaced with
357+
358+
for k, morph in collection.load_unordered(morphology_names):
359+
assert collection.load(morphology_names[k]) == morph
360+
f(k, morph)
361+
362+
The order in which the morphologies are returned in unspecified, but the
363+
loop index `k` can be used to retrieve the correct state corresponding to
364+
iteration `k` of the original loop.
365+
366+
The iterable returned by `Collection.load_unordered` should only be used while
367+
`collection` is valid, e.g. within its context or before calling
368+
`Collection.close`.
369+
370+
Note: This API is 'experimental', meaning it might change in the future.
371+
)")
372+
373+
.def("argsort",
374+
&morphio::Collection::argsort,
375+
"morphology_names"_a,
376+
R"(Argsort `morphology_names` by optimal access order.
377+
378+
Note: This API is 'experimental', meaning it might change in the future.
379+
)")
326380
.def("__enter__", [](morphio::Collection* collection) { return collection; })
327381
.def("__exit__",
328382
[](morphio::Collection* collection,
329383
const py::object&,
330384
const py::object&,
331385
const py::object&) { collection->close(); })
332386
.def("close", &morphio::Collection::close);
387+
388+
py::class_<morphio::LoadUnordered<morphio::Morphology>>(
389+
m, "LoadImmutableUnordered", "An iterable of immutable morphologies.")
390+
.def(
391+
"__iter__",
392+
[](const morphio::LoadUnordered<morphio::Morphology>& iterable) {
393+
return py::make_iterator(iterable.begin(), iterable.end());
394+
},
395+
// Bind the lifetime of the `morphio::LoadUnordered` (1) to the
396+
// lifetime of the returned iterator (0).
397+
py::keep_alive<0, 1>());
398+
399+
py::class_<morphio::LoadUnordered<morphio::mut::Morphology>>(
400+
m, "LoadMutableUnordered", "An iterable of mutable morphologies.")
401+
.def(
402+
"__iter__",
403+
[](const morphio::LoadUnordered<morphio::mut::Morphology>& iterable) {
404+
return py::make_iterator(iterable.begin(), iterable.end());
405+
},
406+
// Bind the lifetime of the `morphio::LoadUnordered` (1) to the
407+
// lifetime of the returned iterator (0).
408+
py::keep_alive<0, 1>());
333409
}

include/morphio/collection.h

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ namespace morphio {
1010

1111
class CollectionImpl;
1212

13+
template <class M>
14+
class LoadUnordered;
15+
16+
/**
17+
* Enable if `T` is a immutable morphology.
18+
*/
19+
template <class T, class U = void>
20+
struct enable_if_immutable: public std::enable_if<std::is_same<T, Morphology>::value, U> {};
21+
22+
/**
23+
* Enable if `T` is a mutable morphology.
24+
*/
25+
template <class T, class U = void>
26+
struct enable_if_mutable: public std::enable_if<std::is_same<T, mut::Morphology>::value, U> {};
27+
1328
class Collection
1429
{
1530
public:
@@ -34,15 +49,34 @@ class Collection
3449
* Load the morphology as an immutable morphology.
3550
*/
3651
template <class M>
37-
typename std::enable_if<std::is_same<M, Morphology>::value, M>::type load(
38-
const std::string& morph_name, unsigned int options = NO_MODIFIER) const;
52+
typename enable_if_mutable<M, M>::type load(const std::string& morph_name,
53+
unsigned int options = NO_MODIFIER) const;
3954

4055
/**
4156
* Load the morphology as a mutable morphology.
4257
*/
4358
template <class M>
44-
typename std::enable_if<std::is_same<M, mut::Morphology>::value, M>::type load(
45-
const std::string& morph_name, unsigned int options = NO_MODIFIER) const;
59+
typename enable_if_immutable<M, M>::type load(const std::string& morph_name,
60+
unsigned int options = NO_MODIFIER) const;
61+
62+
/**
63+
* Returns an iterable of loop index, morphology pairs.
64+
*
65+
* See `LoadUnordered` for details.
66+
*/
67+
template <class M>
68+
LoadUnordered<M> load_unordered(std::vector<std::string> morphology_names,
69+
unsigned int options = NO_MODIFIER) const;
70+
71+
/**
72+
* Returns the reordered loop indices.
73+
*
74+
* This is the suggested order in which one should load the morphologies to
75+
* minimize seeking within the file.
76+
*
77+
* Note: This API is 'experimental', meaning it might change in the future.
78+
*/
79+
std::vector<size_t> argsort(const std::vector<std::string>& morphology_names) const;
4680

4781
/**
4882
* Close the collection.
@@ -61,10 +95,99 @@ class Collection
6195
std::shared_ptr<CollectionImpl> _collection;
6296
};
6397

64-
extern template mut::Morphology Collection::load<mut::Morphology>(const std::string& morph_name,
65-
unsigned int options) const;
98+
class LoadUnorderedImpl;
99+
100+
/**
101+
* An iterable of loop index and morphologies.
102+
*
103+
* When reading from containers, the order in which morphologies are read can
104+
* have a large impact on the overall time to load those morphologies.
105+
*
106+
* This iterator provides means of reordering loops to optimize the access
107+
* pattern. Loops such as the following
108+
*
109+
* for(size_t k = 0; k < morphology_names.size; ++k) {
110+
* auto morph = collection.load<M>(morphology_names[k]);
111+
* f(k, morph);
112+
* }
113+
*
114+
* can be replaced with
115+
*
116+
* for(auto [k, morph] : collection.load_unordered<M>(morphology_names)) {
117+
* assert(collection.load<M>(morphology_names[k]) == morph);
118+
* f(k, morph);
119+
* }
120+
*
121+
* The order in which the morphologies are returned in unspecified, but the
122+
* loop index `k` can be used to retrieve the correct state corresponding to
123+
* iteration `k` of the original loop.
124+
*
125+
* Note, that it is safe for an `LoadUnordered` object to outlive its
126+
* `collection`. Internally a shallow copy of the original `collection` is
127+
* stored inside of and kept alive for the life time of the `LoadUnordered`
128+
* object.
129+
*
130+
* Note: This API is 'experimental', meaning it might change in the future.
131+
*/
132+
template <class M>
133+
class LoadUnordered
134+
{
135+
protected:
136+
class Iterator
137+
{
138+
public:
139+
Iterator(std::shared_ptr<LoadUnorderedImpl> load_unordered_impl, size_t k);
140+
141+
template <class U = M>
142+
typename enable_if_immutable<U, std::pair<size_t, M>>::type operator*() const;
143+
144+
template <class U = M>
145+
typename enable_if_mutable<U, std::pair<size_t, M>>::type operator*() const;
146+
147+
Iterator& operator++();
148+
Iterator operator++(int);
149+
150+
bool operator==(const Iterator& other) const;
151+
bool operator!=(const Iterator& other) const;
152+
153+
private:
154+
size_t _k;
155+
std::shared_ptr<LoadUnorderedImpl> _load_unordered_impl;
156+
};
157+
158+
public:
159+
LoadUnordered(std::shared_ptr<LoadUnorderedImpl> load_unordered_impl);
160+
161+
Iterator begin() const;
162+
Iterator end() const;
163+
164+
protected:
165+
std::shared_ptr<LoadUnorderedImpl> _load_unordered_impl;
166+
};
167+
168+
extern template class LoadUnordered<Morphology>;
169+
extern template class LoadUnordered<mut::Morphology>;
170+
171+
extern template class LoadUnordered<Morphology>::Iterator;
172+
extern template class LoadUnordered<mut::Morphology>::Iterator;
173+
174+
extern template typename enable_if_immutable<Morphology, std::pair<size_t, Morphology>>::type
175+
LoadUnordered<Morphology>::Iterator::operator*<Morphology>() const;
176+
177+
extern template
178+
typename enable_if_mutable<mut::Morphology, std::pair<size_t, mut::Morphology>>::type
179+
LoadUnordered<mut::Morphology>::Iterator::operator*<mut::Morphology>() const;
180+
181+
extern template typename enable_if_mutable<mut::Morphology, mut::Morphology>::type
182+
Collection::load<mut::Morphology>(const std::string& morph_name, unsigned int options) const;
183+
184+
extern template typename enable_if_immutable<Morphology, Morphology>::type
185+
Collection::load<Morphology>(const std::string& morph_name, unsigned int options) const;
186+
187+
extern template LoadUnordered<Morphology> Collection::load_unordered<Morphology>(
188+
std::vector<std::string> morphology_names, unsigned int options) const;
66189

67-
extern template Morphology Collection::load<Morphology>(const std::string& morph_name,
68-
unsigned int options) const;
190+
extern template LoadUnordered<mut::Morphology> Collection::load_unordered<mut::Morphology>(
191+
std::vector<std::string> morphology_names, unsigned int options) const;
69192

70193
} // namespace morphio

include/morphio/morphology.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Morphology
2222
public:
2323
virtual ~Morphology() = default;
2424

25-
Morphology(Morphology&) noexcept = default;
25+
Morphology(const Morphology&) noexcept = default;
2626
Morphology& operator=(const Morphology&) noexcept = default;
2727
Morphology(Morphology&&) noexcept = default;
2828
Morphology& operator=(Morphology&&) noexcept = default;

0 commit comments

Comments
 (0)