Commit 53cb0cb4 authored by Michael Krause's avatar Michael Krause 🎉
Browse files

matlab: spm, refine compile step

parent 54336709
Pipeline #300 passed with stages
in 47 seconds
......@@ -11,8 +11,8 @@ A workaround is to "compile" a script and create a standalone redistribution
environment, which does not require a license to run.
Different Matlab versions are available via environment modules. You can list
them with :program:`module avail matlab` and activate a specific version with :program:`module
load matlab/<version>`.
them with :program:`module avail matlab` and activate a specific version with
:program:`module load matlab/<version>`.
Regular sessions
----------------
......@@ -63,7 +63,7 @@ Compiling
---------
Once you leave the testing stage and would like to spawn an arbitrary number of
matlab jobs/processes you have to compile your script with :program:`mcc`.
Matlab jobs/processes you have to compile your script with :program:`mcc`.
A reliable pattern is to create a main file :file:`project.m` that contains
a function with the same name and expects some arguments you would like to loop
over. A little like this maybe:
......@@ -176,13 +176,16 @@ SPM
---
SPM already comes as a pre-compiled version and can, identical to the examples
above, be started with :program:`run_spm8.sh` or :program:`run_spm12.sh`. Usually users are
exporting a number of batch files with the spm gui on their local machine,
change the paths to reflect the names on the tardis and then call
:program:`run_spm12.sh` with the **run** parameter for each batch file.
above, be started with :program:`run_spm8.sh` or :program:`run_spm12.sh`.
Usually users are exporting a number of batch files with the spm gui on their
local machine, change the paths to reflect the names on the tardis and then
call :program:`run_spm12.sh` with the **run** parameter for each batch file.
Example: segmentation for a number of nifti images. The file batch.template contains the string :`%%IMAGE%%` as a placeholder so we can easily replace it with the current image path and create a number of new batches from a single template:
Example: segmentation for a number of nifti images. The file batch.template
contains the string :`%%IMAGE%%` as a placeholder so we can easily replace it
with the current image path and create a number of new batches from a single
template:
.. code-block:: bash
......@@ -216,7 +219,9 @@ toolboxes to SPM (e.g. cat12).
For product information, visit www.mathworks.com.
>> addpath(genpath('/home/mpib/krause/matlab/tools/spm12'))
>> %addpath(genpath('/home/mpib/krause/matlab/tools/spm12')) % overkill
>> addpath('/home/mpib/krause/matlab/tools/spm12')
>> addpath('/home/mpib/krause/matlab/tools/spm12/config')
>> spm_make_standalone()
[... lot's of output and warnings ...]
Processing /opt/matlab/R2012a/toolbox/matlab/mcc.enc
......@@ -261,4 +266,171 @@ functions.
mcc('-m', 'main.m')
Efficient Saving
----------------
We have noticed a couple of times now that Matlab's :program:`save()` function
can lead to undesirable performance issues. This occurs especially when a large
number of jobs try to save big objects at the same time. This is a bit of
a complex issue and I will try to go through them in this section using some
examples. You are free to adapt some of the things highlighted here in your
code, but you don't have to.
There are two aspects to consider when saving larger objects with Matlab:
1. shared Input/Output
Your home directories are connected via a network file system (NFS). While
NFS bandwidth, storage size and IO performance is matched to our cluster
size, its capacity can be quickly saturated. We recommend to carefully watch
your saving and loading operations in your scripts, especially when there
are a large number of jobs accessing files at the same time. An indication
for bad IO performance is a job cpu efficiency value considerably less than
1.0 (check the `Tardis Website`_ for that value).
The worst case for a networked file system are a huge number of tiny file
operations, like reading or writing a few bytes or constantly checking for
changed file attributes (size, modification time and so on). Try to avoid
these situations by only writing large chunks of data at a time.
2. Compression
Sometimes it's much faster to compress large chunks of data **before**
sending it over the wire (i.e. saving an object). Also, storage size is
a valuable resource and you should therefore consider compressing data with
Matlab.
With objects larger than 2GB unfortunately, people usually resort to saving
with the Matlab File format v7.3. The default behaviour of Matlab's
:programe:`save(.., '-v7.3')` is suboptimal and you might want to save your
objects differently. The following examples highlight why this is necessary and
also why it's not trivial to just recommend a better alternative.
Consider two extreme variants of data, a truly random matrix and a matrix full
of zeros. Both in size 5GB:
.. code-block:: bash
R = randn(128,1024,1024,5);
Z = zeros(128,1024,1024,5);
On a drained tardis saving these two objects naively takes **forever**
.. code-block:: bash
tic; save('~/test.mat', ['R'], '-v7.3'); toc;
Elapsed time is 346.156656 seconds.
tic; save('~/test.mat', ['Z'], '-v7.3'); toc;
Elapsed time is 145.100863 seconds
There are two reasons for this. Firstly, the compression algorithm Matlab uses
appears to be quite slow. And even with perfectly compressible data (Z can be
easily expressed with 5 bytes), Matlab needs more than two minutes to save the
object, while **still** creating a considerable amount of IO operations.
There is an option to disable compression (which is generally not advisable
anyway). But even then saving the "R" object takes more than 3 minutes:
.. code-block:: bash
tic; save('~/test.mat', ['R'], '-v7.3', '-nocompression'); toc
Elapsed time is 220.320252 seconds.
With version 7.3 Matlab changed the binary format for object serialization to
something based on HDF5. Luckily, the lower level functions for HDF5 file
manipulations are available to you. For this simple case, a matrix of numbers
saving directly to HDF5 could look like this:
.. code-block:: bash
>> h5create('test.h5', '/R', size(R), 'ChunkSize', [128,1024,1024,1]);
>> tic; h5write('test.h5', '/R', R); toc;
Elapsed time is 15.225184 seconds.
In comparison to the naive ::program:`save()`, we are fast by a factor of 22.
But this is not the whole story unfortunately. One downside to this approach is
connected to the internal structure of HDF5 objects. Saving a struct with
multiple, nested objects of different types (think string annotations, integer
arrays and float matrices) is much more tedious. Timothy E. Holy wrote
a wrapper script, that kind of automatically creates the necessary structures
and published the function ::program:`savefast` on `Matlab's filexchange`_. It
has a similar interface to the original save and can be used as a drop-in
replacement in many cases. You need to add the function to your search path of
course.
.. code-block:: bash
>> tic; savefast('test.mat', 'R'); toc
Elapsed time is 16.242634 seconds.
Unfortunately, we can't just stop here, because savefast by default is not
compressing anything and the whole point of using HDF5 is because we need to
store **large** matrices, bigger than 2GB. Blindly storing them to storage will
waste storage space and saturates the disk arrays with only 4 concurrent jobs
(based on the 15s benchmark above).
In other words, using some kind of compression is necessary - unless you
**know** that you generated random or close to random data. To highlight the
differences that come with compression level, let's look at the 5GB of zeros
stored in Z again. Matlab is a bit fast in compressing and saving with
::program:`save()`. But it still takes 145 seconds. You might be tempted to
combine the savefast() approach and simply compress with something fast like
::program:`gzip` afterwards. This will actually speed things up _and_ save
a lot of space:
.. code-block:: bash
>> Z = zeros(128,1024,1024,5);
>> tic; save('~/test.mat', ['Z'], '-v7.3'); toc;
Elapsed time is 145.100863 seconds.
>> tic; savefast('~/test2.mat', ['Z']); system('gzip test2.mat'); toc;
Elapsed time is 53.527696 seconds.
-rw-r--r-- 1 krause domain users 31M May 16 17:57 test.mat
-rw-r--r-- 1 krause domain users 5.0M May 16 18:00 test2.mat.gz
On a first glance this appears to be faster and more efficient. But this
approach does not scale well to multiple jobs, as it saves the whole 5GB
uncompressed, then reads it all in again (from NFS cache, but still over the
network) and then, after compression, saves it back to disk.
Using HDF5's low level function you can fine tune the compression level from
0 (lowest and fastest) to 9 (highest and slowest). If you set the compression
level however, you also need to set a chunk size, probably because compression
is done chunk-wise. Recommending a generic compression level is hard and
depends very much on your data. Of course you don't want to waste time by
maximizing compression ratio, gaining only a couple of megabytes, but you also
don't want to waste bandwidth by saving overhead data. Consider our zeros again:
..code-block:: bash
>> h5create('test.h5', '/Z', size(Z), 'ChunkSize', [128,1024,1024,1], 'Deflate', 9);
>> tic; h5write('test.h5', '/Z', Z); toc;
Elapsed time is 35.333514 seconds.
>> ls -lh test.h5
-rw-r--r-- 1 krause domain users 5.0M May 16 18:35 test.h5
>> h5create('test.h5', '/Z', size(Z), 'ChunkSize', [128,1024,1024,1], 'Deflate', 6);
>> tic; h5write('test.h5', '/Z', Z); toc;
Elapsed time is 36.646509 seconds.
>> ls -lh test.h5
-rw-r--r-- 1 krause domain users 5.0M May 16 18:36 test.h5
>> h5create('test.h5', '/Z', size(Z), 'ChunkSize', [128,1024,1024,1], 'Deflate', 3);
>> tic; h5write('test.h5', '/Z', Z); toc;
Elapsed time is 20.002455 seconds.
>> ls -lh test.h5
-rw-r--r-- 1 krause domain users 23M May 16 18:37 test.h5
>> h5create('test.h5', '/Z', size(Z), 'ChunkSize', [128,1024,1024,1], 'Deflate', 0);
>> tic; h5write('test.h5', '/Z', Z); toc;
Elapsed time is 50.847998 seconds.
>> ls -lh test.h5
-rw-r--r-- 1 krause domain users 5.1G May 16 18:38 test.h5
Here i picked a chunk size of 1GB, compressing with levels 9,6,3, and 0. Not
surprisingly the optimal value in this group is 3, as it only takes 20seconds
to save the data, while still reducing the 5GB file to 23MB.
.. _`MATLAB Distributed Computing Server`: http://de.mathworks.com/help/mdce/index.html
.. _`Tardis Website`: https://tardis.mpib-berlin.mpg.de/nodes
.. _`Matlabs' fileexchange`: https://de.mathworks.com/matlabcentral/fileexchange/39721-save-mat-files-more-quickly
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment