Memory layout of ndarray

A particularly interesting attribute of the ndarray object is flags. Type the following code:

In [12]: x.flags 

It should produce something like this:

Out[12]: 
  C_CONTIGUOUS : True 
  F_CONTIGUOUS : False 
  OWNDATA : True 
  WRITEABLE : True 
  ALIGNED : True 
  UPDATEIFCOPY : False 

The flags attribute holds information about the memory layout of the array. The C_CONTIGUOUS field in the output indicates whether the array was a C-style array. This means that the indexing of this array is done like a C array. This is also called row-major indexing in the case of 2D arrays. This means that, when moving through the array, the row index is incremented first, and then the column index is incremented. In the case of a multidimensional C-style array, the last dimension is incremented first, followed by the last but one, and so on.

Similarly, the F_CONTIGUOUS attribute indicates whether the array is a Fortran-style array. Such an array is said to have column-major indexing (R, Julia, and MATLAB use column-major arrays). This means that, when moving through the array, the first index (along the column) is incremented first.

Knowing the difference between indexing styles is important, especially for large arrays, because operations on arrays can be significantly sped up if the indexing is applied in the right way. Let's demonstrate this with an exercise.

Declare an array, as follows:

In [13]: c_array = np.random.rand(10000, 10000) 

This will produce a variable called c_array, which is a 2D array with a hundred million random numbers as its elements. (We used the rand function from the random submodule in NumPy, which we will deal with in a later section). Next, create a Fortran-styled array from c_array, as follows:

In [14]: f_array = np.asfortranarray(c_array) 

You can check whether c_array and f_array are indeed C and Fortran-styled, respectively, by reading their flags attributes. Next, we define the following two functions:

In [15]: def sum_row(x):
         '''
         Given an array `x`, return the sum of its zeroth row.
         '''
         return np.sum(x[0, :])
In [16]: def sum_col(x):
         '''
         Given an array `x`, return the sum of its zeroth column.
         '''
         return np.sum(x[:, 0])

Now, let's test the performance of the two functions on both the arrays using IPython's %timeit magic function:

Note

There are a handful of magic functions that IPython provides to help us understand the code better; for further details, refer to: http://ipython.readthedocs.org/en/stable/interactive/magics.html?highlight=magic.

In [17]: %timeit sum_row(c_array) 
10000 loops, best of 3: 21.2 µs per loop 
 
In [18]: %timeit sum_row(f_array) 
10000 loops, best of 3: 157 µs per loop 
 
In [19]: %timeit sum_col(c_array) 
10000 loops, best of 3: 162 µs per loop 
 
In [20]: %timeit sum_col(f_array) 
10000 loops, best of 3: 21.4 µs per loop 

As we can see, summing up the row of a C array is much faster than summing up its column. This is because, in a C array, elements in a row are laid out in successive memory locations. The opposite is true for a Fortran array, where the elements of a column are laid out in consecutive memory locations.

Tip

Note that the exact figures may vary depending on the operating system, RAM, and the Python distribution being used, but the relative order between the execution times should remain the same.

This is an important distinction and allows you to suitably arrange your data in an array, depending on the kind of algorithm or operation you are performing. Knowing this distinction can help you speed up your code by orders of magnitude.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.239.234