Parallelism
MATLAB is, by default, a single-threaded language at the script level: a user’s .m file runs in one thread. The standard library, however, is heavily multi-threaded under the hood — matrix multiplication, FFTs, element-wise functions, and most BLAS-backed operations dispatch to native code that uses every CPU core. The result is that vectorised MATLAB code is implicitly parallel and is, for many workloads, as fast as explicit parallel code would be. Explicit parallelism — multiple MATLAB workers running in separate processes — is provided by the Parallel Computing Toolbox through parfor, parfeval, spmd, and the parallel.Pool API. GPU parallelism is supported through gpuArray; cluster parallelism is supported through MATLAB Parallel Server.
Implicit parallelism
The MATLAB interpreter delegates expensive numerical primitives to multi-threaded libraries. The user writes:
A = rand(5000);
B = rand(5000);
C = A * B; % multi-threaded BLAS underneath
sin(rand(1e7, 1)); % multi-threaded element-wise
fft(rand(1e6, 1)); % multi-threaded FFT
On a modern multi-core machine, each of these statements uses all the cores; the user sees only the result. The number of threads is controlled by maxNumCompThreads(n) and defaults to the number of physical cores on the host. The mechanism is invisible and is the single most important reason that idiomatic, vectorised MATLAB code is fast.
A consequence: the gain from explicit parallelism on a single machine is often small, because the inner kernels are already parallel. The clear wins are workflows with many independent iterations of moderately expensive serial work — Monte Carlo simulations, hyper-parameter searches, ensemble training — where the granularity of an iteration is too small to benefit from BLAS multi-threading but the iterations are independent.
parfor
The Parallel Computing Toolbox (PCT) provides parfor, a for loop whose iterations are dispatched to worker processes:
N = 100;
results = zeros(N, 1);
parfor k = 1:N
results(k) = expensiveSimulation(k);
end
The first parfor of a session starts a parallel pool — a set of MATLAB worker processes — using parpool('local'). The pool size defaults to the host’s physical core count; subsequent parfor and parfeval calls reuse the pool. The pool is closed automatically after a configurable idle timeout or explicitly with delete(gcp).
parfor has strict requirements on the loop variables:
| Variable kind | Definition | Notes |
|---|---|---|
| Loop variable | The iteration index | Must be a single increasing integer range. |
| Sliced variable | Read or written only at the index of the current iteration | results(k) = ... is sliced on results. |
| Broadcast variable | Read, never written | Same value broadcast to every worker. |
| Reduction variable | Combined across iterations with an associative-commutative op | s = s + x(k) is a reduction on s. |
| Temporary variable | Assigned in the body, not used after the loop | Worker-local; discarded. |
A loop body that does not conform to these patterns fails to parse as parfor — for example, results(k+1) = ... is not sliced (the index is not the loop variable) and is rejected. The discipline is the price of parallelism without locks.
parfeval and futures
For task-parallel workloads, the function parfeval(@f, nout, args...) submits a function call to a worker and returns a Future object that the caller can poll or wait on:
N = 100;
futures(N) = parallel.FevalFuture;
for k = 1:N
futures(k) = parfeval(@expensiveSimulation, 1, k);
end
% Collect results as they complete
for k = 1:N
[idx, val] = fetchNext(futures);
results(idx) = val;
end
fetchNext blocks until any future completes and returns the index and the result. The mechanism is the canonical way to overlap work and processing — particularly useful in interactive workflows that update a UI as results arrive.
parfeval(backgroundPool, ...) (since R2021b) dispatches to a thread-based pool — lighter weight than the process pool and suitable for non-blocking I/O — but with restrictions on which functions are supported (no figures, no MEX files, no parfor inside the call).
spmd
The spmd (single program, multiple data) construct runs the same block on every worker, with each worker having a distinct labindex:
spmd
chunkSize = floor(N / numlabs);
chunkStart = (labindex - 1) * chunkSize + 1;
chunkEnd = chunkStart + chunkSize - 1;
localResult = compute(data(chunkStart:chunkEnd));
end
The spmd block is the mechanism for problems that need explicit communication between workers — barrier synchronisation, point-to-point messages (labSend, labReceive), reductions (gop). It is less common in user code than parfor; the typical use is in PCT examples that demonstrate distributed-array operations.
The associated Composite class is a worker-indexed container: a single variable that holds a different value on each worker. c = Composite(); c{1} = 'alpha'; c{2} = 'beta'; ... collects per-worker values.
GPU computing
The PCT provides the gpuArray class: an array stored on a CUDA-capable GPU. Most arithmetic and linear-algebra operations are overloaded to dispatch to the GPU implementation:
A = gpuArray(rand(2000));
B = gpuArray(rand(2000));
C = A * B; % executed on the GPU
D = gather(C); % bring back to host memory
The gather function transfers data back; the inverse gpuArray transfers to the GPU. Round-trip transfers are expensive; the recommended idiom is to do as much work as possible on the GPU before gathering.
arrayfun and bsxfun accept GPU arrays and element-wise functions of GPU arrays, dispatching to GPU kernels generated at call time. The mechanism is the basis of much GPU-accelerated MATLAB code that does not call BLAS or FFT directly.
For finer control, CUDA kernels can be loaded with parallel.gpu.CUDAKernel; for the deep-learning case, dlnetwork-based training automatically uses the GPU when one is present.
Clusters
For larger workloads, MATLAB Parallel Server provides cluster execution: the same parfor and parfeval constructs, but the pool runs on a remote cluster (HPC, AWS, Azure, GCP). Configuration is through the Cluster Profile Manager; once a cluster profile is in place, parpool('myCluster') opens a remote pool and code submitted to it executes on the cluster’s nodes. The interface is otherwise identical to local parallelism.
Asynchronicity outside the PCT
Without the PCT, MATLAB still has limited mechanisms for asynchrony:
timerobjects fire at intervals or at scheduled times, executing a callback on the main thread.backgroundPool(R2021b+, included in base MATLAB) is a thread-based pool used byparfeval(backgroundPool, ...)for lightweight asynchrony, primarily for I/O-bound work.- Asynchronous data acquisition — through the Data Acquisition Toolbox — runs in background threads and delivers data via callbacks.
The mechanisms are sufficient for UI responsiveness but are not a substitute for the full PCT for CPU-bound workloads.
Shared memory and locking
MATLAB workers are separate processes; they do not share memory by default. Communication is by message (labSend/labReceive inside spmd) or by file. The absence of shared memory is the reason parfor can guarantee that sliced variables are safe without explicit locking. The cost is that any large variable used by every iteration is broadcast — copied to each worker — at the start of the loop.
For genuinely shared data, the mechanisms are parallel.pool.Constant (a value broadcast once to the pool and held for the pool’s lifetime) and DataQueue (a thread-safe queue that workers can send to and the client can read from).
When parallelism pays
The pragmatic rules:
- Use vectorised, BLAS-backed operations whenever possible — they are implicitly parallel and are often as fast as explicit parallel code.
- Use
parforwhen iterations are independent and individually take more than tens of milliseconds — the overhead of dispatch dominates for shorter iterations. - Use
parfevalwhen the workload is irregular (some iterations are much longer than others) or when you want to start using results before all are ready. - Use
gpuArrayfor dense numerical work over arrays of significant size — the transfer overhead means small arrays do not pay back. - Avoid
parforover the outer loop if the inner loop is already BLAS-multi-threaded — you will create thread contention and lose performance.
The principal mistake is reaching for parfor too eagerly; on a modern CPU with a well-vectorised inner kernel, serial code is often within a factor of two of optimal.