Parallel Programming in Native Code

Parallel programming using C++ AMP, PPL and Agents libraries.

concurrency::array_view – data source lifetime

concurrency::array_view – data source lifetime

  • Comments 1

The previous posts in this series on C++ AMP array_view covered:

  1. Introduction to array_view and some of its key semantic aspects
  2. Implicit synchronization on destruction of array_views
  3. array_view discard_data function
  4. Caching and coherence policies underlying array_view implementation
  5. Using a staging array as an array_view’s data source

This post will talk about the lifetime management of an array_view’s data source.

array_view data source lifetime

An array_view is bound to a data source and the data source’s memory allocation must outlive the array_view. Any attempts to access an array_view after the data source memory has been de-allocated will result in undefined behavior. Remember to account for the implicit synchronization on destruction of the last array_view of a data source.

Guideline A: Ensure that the memory allocation corresponding to a data source outlives all array_views referencing that data source.

array_view<float> GenerateRandomNumbers(float *seeds, int count)
{
    array_view<const float> seedView(count, seeds);
 
    std::vector<float> outRandVec(count);
    array_view<float> outRandView(outRandVec.size(), outRandVec);
    outRandView.discard_data();
 
    parallel_for_each(outRandView.extent, [seedView, outRandView](index<1> idx) restrict(amp) {
        ...
    });
 
    // Guideline A violation: Returning an array_view that uses a local std::vector as its data
    // source which will be destructed at the end of this function. Accessing the array_view after the
    // std::vector data source is destructed has undefined behavior.
    return outRandView;
}

 

An exception in this regard is the use of a concurrency::array container as the data source of an array_view. The memory allocation underlying a concurrency::array is reference counted and lives as long as there is a live array or array_view reference to it. Hence if you are using a concurrency::array as a data source for an array_view, you need not worry about having to keep the source array object live – it is fine for any array_views created from an array to outlive the array itself. In fact, this is a useful technique if an array_view needs to be encapsulated within another user-defined type. In such a scenario, it is often desirable that the type encapsulating the array_view also encapsulates the data source of the array_view so that their lifetimes are managed together. However, if an object of this user-defined type has to be captured in a parallel_for_each kernel, the data source itself cannot be a data member of this user-defined type (since a concurrency::array, a CPU pointer or an STL container cannot be captured by value in a parallel_for_each kernel per the restrict(amp) restrictions). Creating an array_view over a temporary array solves this problem – the buffer allocation underlying the array/array_view being reference counted, lives even after the temporary array is destructed until the array_view itself is destructed.

template <typename value_type>
class Matrix
{
public:
    // The Matrix type's array_view data member can be constructed from 
    // a temporary array without the array itself being a data member of the type
    // Memory underlying the array is freed when the array_view member is 
    // destructed in the Matrix destructor
    Matrix(int height, int width)
    : _M_data_view(array<value_type, 2>(height, width))
    {
    }
 
    array_view<value_type> GetRow(int rowNumber) restrict(cpu, amp)
    {
        return _M_data_view[rowNumber];
    }
 
    value_type& operator(int i, int j) restrict(cpu, amp) 
    {
        return _M_data_view(i, j);
    }
 
private:
    array_view<value_type, 2> _M_data_view;
};
 
Matrix<float> mA(M, W);
 
// The Matrix object can be captured in the parallel_for_each by value
// as it only contains an array_view data member. If the type has a concurrency::array
// data member, it would have been illegal to capture a Matrix object by value
// in the parallel_for_each kernel
parallel_for_each(mA.extent, [=](index<2> idx) restrict(amp) {
    mA(idx) = fast_math::sqrt(mA(idx));
});

 

In closing

In this post we looked at the importance of ensuring the right lifetime for an array_view’s data source.

If there are other array_view topics that are not covered on our blog, please let me know so I can address them too. I would love to hear your feedback, comments and questions below or in our MSDN forum.

Blog - Comment List MSDN TechNet
  • Loading...
Leave a Comment
  • Please add 7 and 7 and type the answer here:
  • Post