Layouts

What is an AML layout ?

In AML, a layout is an abstraction over the indexing of a contiguous virtual address space (buffer). In other words, it describes how data is stored in memory and needs to be dereferenced. A layout does not necessarily own the backing memory buffer, and multiple layouts can be used to provide different views into the data.

Let’s take the example of a 1 dimensional array numbered sequentially:

double array[size];
for (size_t i = 0; i < size; i++)
      array[i] = i;

And let’s look at how different 2d (matrix) layouts provide a view into this array.

In Fortran, the elements of a matrix are stored by encasing the last dimension into the previous one and so on. This means that the elements of the last dimension will be contiguous in memory, i.e. the elements of one column will be stored contiguously.

In C, it is the reverse, the elements of the first dimension are contiguous in memory, which means for a 2d matrix that the elements of one row would be contiguous in memory.

The layouts in AML allow you to choose the order in which the program iterates over the dimensions. This order can be specified when creating a layout.

Creating an AML layout

First, you need to have the right headers.

#include <aml.h>
#include <aml/layout/dense.h>

You need to have already have allocated some memory space for your data, for instance with a AML area.

double *array;
struct aml_area *area = &aml_area_linux;
array = (double *)aml_area_mmap(area, sizeof(double) * size, NULL);

Then you can declare and create a layout that should be iterated like in C (last dimension moves the fastest). Let’s use x and y for the 2d dimensions (with x * y = size):

struct aml_layout *layout_c;
size_t dims[2] = { x, y };
aml_layout_dense_create(&layout_c, array, AML_LAYOUT_ORDER_C, sizeof(double), 2, dims, NULL, NULL);

We have just created a layout with elements of type double, with two dimensions, the fist dimension containing x elements, and the second dimension containing y elements.

In the same way, you can create your layout to be iterated like in Fortran (first dimension moves the fastest).

struct aml_layout *layout_f;
aml_layout_dense_create(&layout_f, array, AML_LAYOUT_ORDER_FORTRAN, sizeof(double), 2, dims, NULL, NULL);

We have just created a second layout with elements of type double, with two dimensions, the fist dimension containing x elements, and the second dimension containing y elements.

Note that the two layouts have been defined over the same memory location.

The two arguments set to NULL in the above creation function are meant for layouts created on data not contiguous in memory, allowing to set strides and pitches to describe exactly the storage situation.

Getting the order of an AML layout

You can see the order of a layout by using the following function from the AML Layout API:

aml_layout_order(layout_c));
aml_layout_order(layout_f));

The order of the first layout is AML_LAYOUT_ORDER_C, which in AML is represented by the value 1, and the order of the second layout is AML_LAYOUT_ORDER_FORTRAN, and the above function would return 0.

Destroying an AML layout

In the same way you need to free the memory allocated for an array, you need to destroy your layout when you’re done using it.

aml_layout_destroy(&layout_c);
aml_layout_destroy(&layout_f);

Note that destroy is one of many operations that are generic to the layout type.

Generic operations on an AML layout

Several operations on an AML layout are defined in the AML Layout generic API. Let’s assume here that we have successful created a layout called layout in this part.

We can get the number of dimensions of this layout:

size_t ndims = aml_layout_ndims(layout);

In the previous examples, the number of dimensions of the layouts would be 2.

Once you’ve got the number of dimensions of the layout, you can get the size of each dimension in an array:

size_t dims[ndims];
int err = aml_layout_dims(layout, dims);

This function will return a non-zero integer if there is an error, and 0 if everything is fine. In our previous layouts, the dimensions returned would be {x, y} for both layout_c, and layout_f.

You can also get the size of one element of the layout:

size_t element_size = aml_layout_element_size(layout);

This would have returned the sizeof(double) for our previous layouts.

Accessing elements of a layout

You can access any element stored in the layout by using its coordinates in the system indexed by the layout’s dimensions dims. Here is an example of going through each dimension of the layout, and reading each element with the function aml_layout_deref.

size_t coords[ndims];
double *a;

for (size_t i = 0; i < dims[0]; i++) {
   for (size_t j = 0; j < dims[1]; j++) {
      coords[0] = i;
      coords[1] = j;
      a = aml_layout_deref(layout, coords);
      printf("%f ", *a);
   }
   printf("\n");
}

You can find this code in doc/tutorials/layouts/0_dense_layout.c. Try to predict what the output will be for each layout used. Experiment with different layouts.

Changing the shape of a layout

You can also change the shape of your layout, creating a new layout with a different number of dimensions.

Let’s take the previous layout layout_f, that had two dimensions {x, y}, and create a new layout with three dimensions, basically splitting the first dimension in two:

size_t new_dims[3] = { x/2, x/2, y };
struct aml_layout *reshape_layout;
aml_layout_reshape(layout_f, &reshape_layout, 3, new_dims));

The new layout is ordered in the same order as the previous layout, in this case AML_LAYOUT_ORDER_FORTRAN.

You can also want to mix up the order of the dimensions of your layout. This cannot be done with the reshape function. You need to allocate a new memory area, create another layout with the right dimensions, and copy the elements from one layout to the other.

Let’s say we have a three-dimensional layout layout_3 and we want to run a permutation on the dimensions and get a layout new_layout with the first dimension of layout_3 in third place, the second one in first and the last one in second place. After allocating the correct memory area and creating new_layout with the right dimensions, we would use aml_copy_layout_transform_generic

struct aml_area *area = &aml_area_linux;
array_1 = (double *)aml_area_mmap(area, sizeof(double) * size_0 * size_1 * size_2, NULL);
array_2 = (double *)aml_area_mmap(area, sizeof(double) * size_0 * size_1 * size_2, NULL);

struct aml_layout *layout_3, *new_layout;
aml_layout_dense_create(&layout_3, array_1, AML_LAYOUT_ORDER_FORTRAN, sizeof(size_t), 3, (size_t[]){size_0, size_1, size_2}, NULL, NULL));

aml_layout_dense_create(&new_layout, array_2, AML_LAYOUT_ORDER_FORTRAN, sizeof(size_t), 3, (size_t[]){size_1, size_2, size_0}, NULL, NULL));

aml_copy_layout_transform_generic(new_layout, layout_3, (size_t[]){1, 2, 0}));

Exercise

Let’s look at an example of when you could use layouts.

Let particle be a data structure with several attributes:

struct particle {
   size_t id;
   size_t position_x;
   size_t position_y;
   double energy;
};

Let particles be a two-dimensional array of particles.

We basically have an array of structures, which is hard to manipulate. We want to use the AML layouts to transform this into a structure of arrays, that could allow more performance on some platforms.

Based on the above layout, a straightforward layout on this array could be:

struct aml_layout *layout_part;
aml_layout_dense_create(&layout_part, particles, AML_LAYOUT_ORDER_FORTRAN, sizeof(struct particle), 2, (size_t[]){size_1, size_2}, NULL, NULL));

The issue with this layout is that it does not give us a fine enough control on the attributes of the particles. We need to be able to index each field of the structure independently. This can be done by adding an extra dimension (because all fields have the same storage size here):

struct aml_layout *layout_elements;

aml_layout_dense_create(&layout_elements, particles, AML_LAYOUT_ORDER_FORTRAN, sizeof(size_t), 3, (size_t[]){4, size_1, size_2}, NULL, NULL));

Now we can create another layout, with a similar granularity, but with the dimensions flipped so that we have one array for each attribute of all the particles. Then we need to copy the elements of the first layout in the second layout in the right order, using the function aml_copy_layout_transform_generic.

Solution

Click Here to Show/Hide Code

/*******************************************************************************
 * Copyright 2019 UChicago Argonne, LLC.
 * (c.f. AUTHORS, LICENSE)
 *
 * This file is part of the AML project.
 * For more info, see https://github.com/anlsys/aml
 *
 * SPDX-License-Identifier: BSD-3-Clause
 ******************************************************************************/

#include "aml.h"
#include "aml/area/linux.h"
#include "aml/layout/dense.h"
#include <stdio.h>

struct particle {
	size_t id;
	size_t position_x;
	size_t position_y;
	double energy;
};

int main(int argc, char **argv)
{
	const size_t size_1 = (1 << 9);
	const size_t size_2 = (1 << 7);
	size_t size = size_1 * size_2;

	if (aml_init(&argc, &argv) != AML_SUCCESS)
		return 1;

	struct particle *particles;

	// Allocate memory for the array of struct through an area
	struct aml_area *area = &aml_area_linux;

	particles = (struct particle *)
		aml_area_mmap(area, sizeof(struct particle) * size, NULL);

	// Initializing our array of particles
	for (size_t i = 0; i < size; i++) {
		particles[i].id = i;
		particles[i].position_x = i;
		particles[i].position_y = i;
		particles[i].energy = (double) 2*i;
	}
	assert(sizeof(struct particle) == sizeof(size_t) * 4);

	fprintf(stderr, "Creating layouts...\n");
	struct aml_layout *lay_part, *layout_elements, *new_layout;

	// We start with a straighforward layout
	assert(!aml_layout_dense_create(&lay_part, particles,
					AML_LAYOUT_ORDER_COLUMN_MAJOR, //FORTRAN
					sizeof(struct particle), 2,
					(size_t[]){size_1, size_2}, NULL,
					NULL));

	assert(lay_part != NULL);

	// We need a finer layout
	assert(!aml_layout_dense_create(&layout_elements, particles,
					AML_LAYOUT_ORDER_COLUMN_MAJOR, //FORTRAN
					sizeof(size_t), 3,
					(size_t[]){4, size_1, size_2},
					NULL, NULL));

	// Let's take a look at it
	for (size_t i = 0; i < 10; i++) {
		fprintf(stderr, "%ld ",
			*(size_t *)aml_layout_deref(layout_elements,
						    (size_t[]){0, i, 0}));
		fprintf(stderr, "%ld ",
			*(size_t *)aml_layout_deref(layout_elements,
						    (size_t[]){1, i, 0}));
		fprintf(stderr, "%ld ",
			*(size_t *)aml_layout_deref(layout_elements,
						    (size_t[]){2, i, 0}));
		fprintf(stderr, "%lf ",
			*(double *)aml_layout_deref(layout_elements,
						    (size_t[]){3, i, 0}));
	}
	fprintf(stderr, "\n");

	fprintf(stderr, "Changing the shape of the layout...\n");
	size_t *array_coords;

	// We get a new memory allocation for this new layout
	array_coords = malloc(sizeof(struct particle) * size);

	assert(!aml_layout_dense_create(&new_layout, array_coords,
					AML_LAYOUT_ORDER_COLUMN_MAJOR, //FORTRAN
					sizeof(size_t), 3,
					(size_t[]){size_1, size_2, 4},
					NULL, NULL));

	assert(!aml_copy_layout_transform_generic(new_layout, layout_elements,
						  (size_t[]){1, 2, 0}));

	/* Let's check we now have a struct of arrays by looking at the first
	 * elements */
	for (size_t i = 0; i < 10; i++)
		fprintf(stderr, "%ld ",
			*(size_t *)aml_layout_deref(new_layout,
						    (size_t[]){i, 0, 0}));
	fprintf(stderr, "\n");

	/* Getting only the energy of the particles */
	size_t offsets[3] = {0, 0, 3};
	struct aml_layout *layout_energy;

	fprintf(stderr, "Looking only at the energy now...\n");

	/* This is done by slicing the layout, keeping only the dimensions we
	 * want */
	assert(!aml_layout_slice(new_layout, &layout_energy,
				 offsets, (size_t[]){size_1, size_2, 1},
				 NULL));

	for (size_t i = 0; i < 10; i++)
		fprintf(stderr, "%lf ",
			*(double *)aml_layout_deref(layout_energy,
						    (size_t[]){i, 0, 0}));
	fprintf(stderr, "\n");

	aml_layout_destroy(&lay_part);
	aml_layout_destroy(&layout_elements);
	aml_layout_destroy(&new_layout);
	aml_layout_destroy(&layout_energy);
	aml_area_munmap(area, particles, sizeof(struct particle) * size);
	free(array_coords);

	aml_finalize();

	return 0;
}

You can find this solution in doc/tutorials/layouts.