-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
ARROW-602: [C++] Provide iterator access to primitive elements inside an Array #13009
Conversation
a2a4f06
to
1b98d31
Compare
8690f28
to
2941f70
Compare
cpp/src/arrow/stl_iterator.h
Outdated
int64_t index_in_chunk = | ||
chunk_index ? index_ + n - chunks_lengths_[chunk_index - 1] : index_ + n; | ||
return iterators_list_[chunk_index] | ||
[index_in_chunk - iterators_list_[chunk_index].index()]; |
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.
ChunkedArray
already has GetScalar(index)
which is efficient for accessing sequential scalars at particular logical indices. I suggest to use it for the iteration. There is no need to perform complex index calculations/checks in the iterator. This would be somewhat analogous to the Array
iterator.
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.
@edponce The GetScalar
suggestion doesn't seem right to me. The iterator should just yield C values, not boxed scalars which are much more expensive.
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.
Ok, well what about adding a GetView
method to ChunkedArray
which invokes the GetView
of the underlying Arrays
and use the ChunkedResolver
to resolve the logical index?
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.
Well, ok I see now that it is using the Array
iterators.
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.
Thank you for attempting this @AlvinJ15 . I think the first thing to do would be to simplify the implementation by relying on ChunkResolver
instead of doing chunk resolution yourself. You may want to add a friend
declaration inside ChunkedArray
if that would help.
cpp/src/arrow/stl_iterator.h
Outdated
int64_t GetChunkIndex(int64_t index) const { | ||
return static_cast<int64_t>( | ||
std::upper_bound(chunks_lengths_.begin(), chunks_lengths_.end(), index) - | ||
chunks_lengths_.begin()); |
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.
We have ChunkResolver
now which can be used without needing to reinvent it (and which should generally be faster).
cpp/src/arrow/stl_iterator.h
Outdated
chunks_lengths_.begin()); | ||
} | ||
|
||
void InitializeComponents(ChunkedArrayIterator<ArrayType>& chunked_array_iterator, |
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.
Maybe call this UpdateComponents
as it doesn't only initialize but is also used for updates.
cpp/src/arrow/stl_iterator.h
Outdated
} else { | ||
chunked_array_iterator.chunks_lengths_.push_back(chunk_length); | ||
} | ||
chunk_index++; |
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.
Nit: Factor out the push_back operation from the if-else
auto chunk_length = ...;
if (chunk_index) {
chunk_length += chunked_array_iterator.chunks_lengths_[chunk_index - 1]);
}
chunked_array_iterator.chunks_lengths_.push_back(chunk_length);
cpp/src/arrow/stl_iterator.h
Outdated
auto& current_iterator = | ||
chunked_array_iterator | ||
.iterators_list_[chunked_array_iterator.current_chunk_index_]; | ||
current_iterator -= current_iterator.index(); | ||
if (chunked_array_iterator.current_chunk_index_) | ||
current_iterator += | ||
chunked_array_iterator.index_ - | ||
chunked_array_iterator | ||
.chunks_lengths_[chunked_array_iterator.current_chunk_index_ - 1]; | ||
else | ||
current_iterator += chunked_array_iterator.index_; | ||
} |
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 sure if this is enforced in Arrow but I suggest to always use curly-braces for if-else
blocks.
Nit: This can be simplified to
auto& current_iterator = chunked_array_iterator.iterators_list_[chunked_array_iterator.current_chunk_index_] - current_iterator.index() + chunked_array_iterator.index_;
if (chunked_array_iterator.current_chunk_index_) {
current_iterator += chunked_array_iterator.chunks_lengths_[chunked_array_iterator.current_chunk_index_ - 1];
}
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 agree with this suggestion for the record. We don't enforce it but we try to maintain it.
2941f70
to
158e7c5
Compare
158e7c5
to
b4074bb
Compare
cpp/src/arrow/chunked_array.h
Outdated
@@ -141,6 +141,9 @@ class ARROW_EXPORT ChunkedArray { | |||
/// \brief Return the type of the chunked array | |||
const std::shared_ptr<DataType>& type() const { return type_; } | |||
|
|||
/// \brief Return chunk resolver of the chunked array | |||
const internal::ChunkResolver& GetChunkResolver() const { return chunk_resolver_; } |
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 exposing an internal facility as a public member API. I don't know what our API hygiene for this should be. @westonpace @xhochy @lidavidm Thoughts?
(we could also make this private and use a friend
declaration for the consuming class)
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.
At the very least it needs to be clearly marked off as an internal API, right now it looks like any other public API. Ideally it would be private with a friend declaration.
const internal::ChunkResolver& GetChunkResolver() const { return chunk_resolver_; } | |
const internal::ChunkResolver& chunk_resolver() const { return chunk_resolver_; } |
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 am in favor of using a friend
declaration.
cpp/src/arrow/stl_iterator.h
Outdated
std::vector<int64_t> chunks_lengths_; | ||
std::vector<ArrayIterator<ArrayType>> iterators_list_; |
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 a bit surprised, are these members still needed? Ideally, everything can be done using the ChunkResolver...
cpp/src/arrow/stl_iterator_test.cc
Outdated
auto chunk0 = ArrayFromJSON(int32(), "[4, 5, null]"); | ||
auto chunk1 = ArrayFromJSON(int32(), "[6]"); | ||
|
||
ASSERT_OK_AND_ASSIGN(auto result, ChunkedArray::Make({chunk0, chunk1}, int32())); |
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.
Note there's a ChunkedArrayFromJSON
.
Can we also test a chunked array with no chunks?
cpp/src/arrow/chunked_array.h
Outdated
@@ -141,6 +141,9 @@ class ARROW_EXPORT ChunkedArray { | |||
/// \brief Return the type of the chunked array | |||
const std::shared_ptr<DataType>& type() const { return type_; } | |||
|
|||
/// \brief Return chunk resolver of the chunked array | |||
const internal::ChunkResolver& GetChunkResolver() const { return chunk_resolver_; } |
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.
At the very least it needs to be clearly marked off as an internal API, right now it looks like any other public API. Ideally it would be private with a friend declaration.
const internal::ChunkResolver& GetChunkResolver() const { return chunk_resolver_; } | |
const internal::ChunkResolver& chunk_resolver() const { return chunk_resolver_; } |
Here are some observations on the
#include <iostream>
#include <vector>
struct A {
using T = int;
using vector_type = typename std::vector<T>;
using iterator_type = typename std::vector<const T>::iterator;
A(vector_type values) : values_(values) {}
// NOTE: We are disguising cbegin() via begin()
const iterator_type begin() const {
return std::cbegin(values_);
}
// NOTE: We are disguising cend() via end()
const iterator_type end() const {
return std::cend(values_);
}
private:
vector_type values_;
};
int main() {
auto a = A{{1, 2, 3, 4, 5}};
// Value is a copy so it can be mutated
for (auto v : a) {
v += 10;
std::cout << v << " ";
}
// Value is a non-const reference taken from a const iterator,
// so it cannot be mutated
for (auto& v : a) {
// v += 10;
std::cout << v << " ";
}
// Value is a const reference taken from a const iterator,
// so it cannot be mutated
for (const auto& v : a) {
// v += 10;
std::cout << v << " ";
}
}
|
That wouldn't work, because there are no specialized subclasses of ChunkedArray. We want the iterator to yield raw C values whose type is datatype-specific.
It's used by |
...I still think it can work for the
My mistake, did a grep and thought only declaration/definition were in chunked_array.h/cc |
Er, you simply can't implement
which is a little clumsy |
|
I am not fully convinced but no need on continuing this rant. I will wait for this PR to be merged and try something out. |
@pitrou @lidavidm I see now the issue with typing and
If we could add a |
How would this work? The value would vary at runtime, but we need it to be compile time. |
template <typename Type, typename ArrayType = typename TypeTraits<Type>::ArrayType> | ||
ChunkedArrayIterator<ArrayType> Iterate(const ChunkedArray& chunked_array) { | ||
return stl::ChunkedArrayIterator<ArrayType>(chunked_array); | ||
} |
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.
Do we need these Iterate
methods? One can simply create the corresponding iterator directly. Also, it is prone to error because it allows for the Type
parameter to be different from the actual Arrays
contained in the chunked_array
.
On the other hand, if we are to keep factory functions, the convention is to prefix them with Make
, so MakeIterator
would be more clear, as Iterate
is an action verb.
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 could add a condition for validate that and raise properly an error message, but I don't know if this is a good solution for that problem.
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.
@edponce Iterating is what this API is meant for. We have a separate Iterator
concept in Arrow C++ that we don't want to mix up with this.
A solution is to have the factory I am simply thinking out loud. |
ade6af7
to
df5b3e8
Compare
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.
Thanks for the update. This is looking better, but here are some more comments.
cpp/src/arrow/stl_iterator.h
Outdated
@@ -59,11 +62,12 @@ class ArrayIterator { | |||
|
|||
// Value access | |||
value_type operator*() const { | |||
return array_->IsNull(index_) ? value_type{} : array_->GetView(index_); | |||
return !array_ || array_->IsNull(index_) ? value_type{} : array_->GetView(index_); |
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 there any circumstance where array_
is null and you're still calling this operator? This doesn't seem right.
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.
When a ChunkedArray
is empty, I initialize an ArrayIterator
with the empty constructor which initialize the array_
with a NULLPTR
, and this extra condition is more feasible/short than adding more conditions on ChunkedArrayIterator for validate multiple cases.
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 don't think dereferencing such an iterator should be a valid operation, though, so the check should not be needed.
cpp/src/arrow/stl_iterator.h
Outdated
current_array_iterator_ = ArrayIterator<ArrayType>( | ||
*arrow::internal::checked_pointer_cast<ArrayType>( | ||
chunked_array_->chunk(static_cast<int>(chunk_location.chunk_index))), | ||
index_); |
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 seems more logical:
current_array_iterator_ = ArrayIterator<ArrayType>( | |
*arrow::internal::checked_pointer_cast<ArrayType>( | |
chunked_array_->chunk(static_cast<int>(chunk_location.chunk_index))), | |
index_); | |
current_array_iterator_ = ArrayIterator<ArrayType>( | |
*arrow::internal::checked_pointer_cast<ArrayType>( | |
chunked_array_->chunk(static_cast<int>(chunk_location.chunk_index))), | |
chunk_location.index_in_chunk); |
... and then you don't need the subtraction below.
cpp/src/arrow/stl_iterator.h
Outdated
|
||
value_type operator[](difference_type n) const { | ||
auto chunk_location = GetChunkLocation(index_ + n); | ||
if (current_chunk_index_ == chunk_location.chunk_index) { |
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 don't think this if
branch is needed, constructing a ArrayIterator
should be cheap.
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.
The arrow::internal::checked_pointer_cast<ArrayType>
when creating an ArrayIterator, is less expensive than int64_t
comparissons and a arithmetic operation?
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.
Well, first you should use checked_cast
as there's no reason to create a new shared_ptr
. Second, in release mode checked_cast
is a static cast that doesn't have any actual cost.
cpp/src/arrow/stl_iterator.h
Outdated
ChunkedArrayIterator& operator+=(difference_type n) { | ||
index_ += n; | ||
auto chunk_location = GetChunkLocation(index_); | ||
if (current_chunk_index_ == chunk_location.chunk_index) { |
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.
Again, I don't think this if
branch is needed as constructing an ArrayIterator
should be cheap.
ChunkedArrayIterator& operator++() { | ||
(*this) += 1; | ||
return *this; | ||
} | ||
ChunkedArrayIterator& operator--() { | ||
(*this) -= 1; | ||
return *this; | ||
} |
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 the one place where current_array_iterator_
could be useful for performance but you're not using it here?
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.
Extra conditions would have to be added to control overflows, so I better call the operator +=
which already has the necessary conditions, and also the ChunkResolver implementation managing a cache which avoids repeated searches when it is in the same chunk
ASSERT_EQ(it[1], 11); | ||
ASSERT_EQ(it[2], 13); | ||
} | ||
|
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.
Two things:
- can you add tests with an empty chunked array?
- can you add tests with some standard algorithms as done above for
ArrayIterator
?
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.
- At the beginning of the
EmptyChunks
it has and emptyChunkedArray
TEST(ChunkedArrayIterator, EmptyChunks) {
auto result = ChunkedArrayFromJSON(int32(), {});
auto it = Iterate<Int32Type>(*result);
ASSERT_EQ(*it, nullopt);
ASSERT_EQ(it[0], nullopt);
- Added
4b74c8e
to
883b51f
Compare
@AlvinJ15 Well, I think the error log explains it: |
Hmm, I think my /// Return an iterator to the beginning of the chunked array
template <typename Type, typename ArrayType = typename TypeTraits<Type>::ArrayType>
ChunkedArrayIterator<ArrayType> Begin(const ChunkedArray& chunked_array) {
return stl::ChunkedArrayIterator<ArrayType>(chunked_array);
}
/// Return an iterator to the end of the chunked array
template <typename Type, typename ArrayType = typename TypeTraits<Type>::ArrayType>
ChunkedArrayIterator<ArrayType> End(const ChunkedArray& chunked_array) {
return stl::ChunkedArrayIterator<ArrayType>(chunked_array, chunked_array.length());
}
template <typename ArrayType>
struct ChunkedArrayRange {
const ChunkedArray* chunked_array;
ChunkedArrayIterator<ArrayType> begin() {
return stl::ChunkedArrayIterator<ArrayType>(*chunked_array);
}
ChunkedArrayIterator<ArrayType> end() {
return stl::ChunkedArrayIterator<ArrayType>(*chunked_array, chunked_array.length());
}
};
/// Return an iterable range over the chunked array
///
/// This makes it easy to use a chunked array in a for-range construct.
template <typename Type, typename ArrayType = typename TypeTraits<Type>::ArrayType>
ChunkedArrayRange<ArrayType> Iterate(const ChunkedArray& chunked_array) {
return stl::ChunkedArrayRange<ArrayType>(&chunked_array);
} This way you can write: for (util::optional<int64_t> : Iterate<Int64Type>(chunked_array)) {
... as well as: // Find the first item that's non-null and at least 42
auto it = std::find_if(
Begin<Int64Type>(chunked_array), End<Int64Type>(chunked_array),
[util::optional<int64_t> item]() { return item.has_value() && *item >= 42}); |
3acf34f
to
68e1ee8
Compare
…e private attribute chunked_resolver_
68e1ee8
to
50dac46
Compare
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.
Thanks a lot for the update @AlvinJ15 ! This looked mostly good, I've pushed a couple very minor changes.
@westonpace @lidavidm Feel free to leave any comments on the API even after this is merged. |
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.
Thanks, some minor suggestions but looks good overall
cpp/src/arrow/chunked_array.h
Outdated
namespace stl { | ||
template <typename T, typename V> | ||
class ChunkedArrayIterator; | ||
} |
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.
weird, doesn't the linter usually require } // namespace stl
here?
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.
It should indeed.
cpp/src/arrow/stl_iterator.h
Outdated
// Value access | ||
value_type operator*() const { return *current_array_iterator_; } | ||
|
||
value_type operator[](difference_type n) const { |
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.
Should we DCHECK(chunked_array_) in most of these operations to guard against use of a default-initialized iterator?
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 added some assert
for check it.
cpp/src/arrow/stl_iterator.h
Outdated
} | ||
ChunkedArrayIterator& operator+=(difference_type n) { | ||
index_ += n; | ||
if (index_ != chunked_array_->length()) { |
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.
Maybe also DCHECK here and in the constructor that we aren't exceeding end()?
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.
DCHECKs are not allowed in public headers. We could perhaps use an assert
though.
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.
Ah right, I always forget that…
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 the check should be if (index_ < chunked_array_->length())
so that it does not include index values that exceed or equal the length. Also, here.
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.
@edponce Hmm, possibly. The C++ spec doesn't clearly say whether creating an out-of-bounds iterator is well-defined. However, the operational semantics involve incrementing/decrementing and it seems that incrementing/decrementing past extremities is undefined. @bkietz Do you have any thoughts on this?
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.
It seems that any value is valid as long as you can return to the initial value with its inverse operation. So the check !=
follows the operational semantics of a RandomAccessIterator.
Actually, the <
check would be insufficient because it allows negative indices (need absolute value of index).
The question now becomes, should Arrow iterators guard against out-of-bounds accesses?
From a performance perspective, they have basically the same cost (+ std::abs).
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.
Personally, my preference is to maximize the similarity of any random access iterators to indices/non-owning pointers. In this case I'd try to avoid accessing elements at all when incrementing the iterator, deferring the resolution of current_array_iterator_
until something is actually dereferenced. This would also move out-of-bounds/invalid current array checking into the dereferencing member functions where correct behavior is far more obvious.
The specific question of how to validate out-of-bounds iterators becomes easier to answer too: We can construct an iterator with whatever index and compare/increment/etc without worry since that operates only on the index.
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.
@edponce In https://en.cppreference.com/w/cpp/named_req/ForwardIterator, you can see that only dereferenceable iterators can be incremented. And all other operations are derived from incrementation.
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.
Additional observations on current operator+=
:
-
This dereference can be invalid because
chunk_index
is not validated and can be out-of-bounds. -
An Array and an Iterator are constructed for every access via
+=
. There are opportunities to reusecurrent_array_iterator_
.
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.
@bkietz Ok, I did that. It also turns out it's not very useful to memoize current_array_iterator_
currently.
// Arithmetic | ||
difference_type operator-(const ChunkedArrayIterator& other) const { | ||
return index_ - other.index_; | ||
} |
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 this method part of the public API of a standard iterator? or is it a helper method that can be private?
I couldn't find where it is being used.
Also, why is there not an equivalent method for addition?
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 used in the Test ChunkedArrayIterator.Arithmetic ASSERT_EQ(it - it2, -2);
There is a similar method in ArrayIterator, for calculate the difference between two iterators(like a distance), I think make a + operator would not be used
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.
@edponce See https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator for the requirements for a random access iterator.
template <typename Type, typename ArrayType = typename TypeTraits<Type>::ArrayType> | ||
ChunkedArrayIterator<ArrayType> End(const ChunkedArray& chunked_array) { | ||
return stl::ChunkedArrayIterator<ArrayType>(chunked_array, chunked_array.length()); | ||
} |
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.
Nit: Sometimes ChunkedArrayIterator<>
is used and other times stl::ChunkedArrayIterator<>
.
b94cf75
to
48c815c
Compare
Benchmark runs are scheduled for baseline = 0bae875 and contender = 9ae12a5. 9ae12a5 is a master commit associated with this PR. Results will be available as each benchmark for each run completes. |
['Python', 'R'] benchmarks have high level of regressions. |
Create Iterator method in stl for Array and ChunkedArray