view_as function in C++ AMP

With C++ AMP, you can use array or array_view to work with data in a multi-dimensional way, which often reflects your algorithm more naturally. Sometimes, however, you might need to reshape the data to adapt data to an existing interface or to use data generated by a factory library in your algorithms. For example, you might want to take advantage of a library that only provides APIs for linear containers although your data sits in N-dimensional arrays or array_views. In this blog post, I will introduce you to the view_as function available on array and array_view, which is useful for these scenarios.

About the API

Following is the view_as signature.

template <typename T, int N>
class array
template <int K>
array_view<T,K> view_as(extent<K> viewExtent) restrict(cpu,amp)
template <int K>
array_view<const T,K> view_as(extent<K> viewExtent) const restrict(cpu,amp)

template <typename T>
class array_view<T,1>
template <int K>
array_view<T,K> view_as(extent<K> viewExtent) const restrict(cpu,amp);

template <typename T>
class array_view<const T,1>
template <int K>
array_view<const T,K> view_as(extent<K> viewExtent) const restrict(cpu,amp);

Note the following facts for the view_as function:

  • It is available for arrays of any rank, but only available for array_views of rank 1. This is because contiguous memory is necessary for well-defined and efficient data reshaping and in C++ AMP we only guarantee memory contiguity for arrays and the least-significant-dimension of array_views.
  • The array_view object returned preserves the constness of the original array or array_view objects, as you’d expect.
  • Its return type is array_view, not array. This is because arrays are direct data containers, i.e., different arrays always associate with different memory, but what we want with view_as is a different view over the original data instead of a new data copy. Since no data-copy is involved, the view_as operation is cheap.
  • The total element number of viewExtent should not be greater than the total element number of the original array or array_view object. Otherwise, a runtime_exception will be thrown with message “Invalid view creation”.

Example of view_as

Now let’s see a simple example that illustrates the usage of view_as to adapt data to an existing interface. For example, we have a library that provides the following API that fills the given array_view of rank 1 with random numbers:

void random_fill(array_view<float,1> in);

Now, if you have a 2-dimentional array with float elements, you could still use this API to do initialization via view_as like:

void fill(array<float,2> a) 

Another real world example would be to adapt 2D image data to a histogram calculator, which is usually a 1D algorithm.

This concludes my introduction to view_as. My next post will describe a somewhat related API: reinterpret_as. As always, you are welcome to ask questions and provide feedback below or on our MSDN forum.

Comments (2)

  1. Lingli,

    What is the best way to access the data after a library call? It appears that the only way I could do it was by reading an element from a temporary view which was passed to the API. I imagine, expectation for this kind of architecture would be that the original data will be discarded, hence your mentioning of histogram example.

    Thank you,


    void AmpExamples::RandomFill(array_view<float, 1> & view)


    parallel_for_each(view.extent, [=](index<1> idx) restrict(amp) {

    view[idx] = 1.f;



    void AmpExamples::ViewAs()


    cout << "nview_as example.n";

    const unsigned int width = 1920;

    const unsigned int height = 1080;

    concurrency::extent<2> ext(width, height);

    array<float, 2> image(ext);

    time_point<system_clock> start = system_clock::now();

    // Fill

    array_view<float, 1> tmpView = image.view_as<1>(concurrency::extent<1>(image.extent.size()));



    float x = tmpView[0];

    time_point<system_clock> stop = system_clock::now();

    long long us = duration_cast<microseconds>(stop – start).count();

    cout << "   Executed in: " << us << "us" << endl;

    //cout << "   tmpView(0,0) = " << tmpView(0) << endl;

    array_view<float, 2> view(image);

    index<2> idx(10, 15);

    cout << "   image(10,15)   = " << view[idx] << endl;


  2. LingliZhang says:

    Hi Alan,

    What you need to do after a library call should be defined as a contract between the caller and callee. The library function should specify the behavior and expectation of the output data it produces. For your specific example, RandomFill, it doesn't do explicit synchronization after parallel_for_each. This information should be communicated as part of this function's usage info. As a user of this function, you might do differently depending on your intention. For example, if you only care about the computation time on GPU, you could do wait() on the accelerator_view after the parallel_for_each. If you want to measure time spent on both computation and data synchronization, you can call synchronize() on the array_view object. "tmpView[0]" is effectively same as synchronize() if you only invoke it once. But synchronize() is better in terms of self-documented code, and if you access the array_view elements on CPU many times after parallel_for_each, it's more efficient to do synchronize() first, then get the data() pointer from the array_view object, and access through the pointer directly to avoid the overhead in array_view's [] operator that checks for necessity of synchronization.