New in MATLAB: Single precision sparse matrices
Sparse matrices have been in MATLAB for a long time but, up until now, the only types of sparse matrices you could create were double or logical sparse matrices.
This is no longer the case and one of the thousands of new features in MATLAB R2025a is the fact that you can now create and use single precision sparse matrices. For example, here is how you create a random single precision sparse matrix of size 50 x 100 with density 0.001.
mySparseSingleMat = sprand(50,100,0.001,"single")
The "single" argument is new and is also available for several other sparse matrix creation functions such as sparse, speye, spalloc, sprandn and sprandsym. As with previous versions, if you don't specify any type then the default is double but you can now also be explicit if you prefer.
mySpeyeSingleMat = speye(5,"double")
Support for single precision sparse matrices has been on many people's wish-lists for a long time and its great news that we now have them. In the remainder of this post I'm going to dig into some of the details about why single precision sparse matrices are useful, why it was a lot of work to add them to MATLAB and a couple of tips about best practice when using them.
Single precision sparse matrices save memory
A double precision number requires 64-bits (8 bytes) to store it while a single precision number uses 32-bits (4 bytes). So, the first benefit of using a single sparse matrix is that it requires less memory compared to the double version but at the cost of reduced precision. Given this information, your first guess might be that a single sparse matrix requires half the space of a double sparse matrix, which is the case for dense matrices. If so, your first guess is wrong
mySparseDoubleMat = sprand(50,100,0.001,"double")
whos mySparseDoubleMat
Let's now create a single sparse matrix with the same size and with the same number of non-zero elements
mySparseSingleMat = sprand(50,100,0.001,"single")
Again, we show how much memory this is using
whos mySparseSingleMat
The double sparse matrix is using 888 bytes while single sparse is using 868 bytes -- a saving of only 20 bytes! The reason for this is that along with the actual values of the non-zeros, sparse matrices have to store their locations. The memory required for location storage of the non-zeros is the same no matter what precision the actual values are.
In the two examples given above, the memory required for the storage of the locations of the non-zeros is identical. The difference between them is simply that the 5 non-zeros are stored as double in the first case (and hence taking up 40 bytes) and single in the second case (and hence using 20 bytes) which gives us the 20 byte difference between the two.
Both are significantly better than the 50 x 100 x 8 = 40,000 bytes for the dense double matrix or 50 x 100 x 4 = 20,000 bytes for the dense single matrix. For dense matrices, a zero takes up just as much memory space as any other number. Sparse matrices can save a lot of memory and single sparse matrices can save even more, particularly when dealing with very large matrices with a large number of non-zero elements. For example, consider this sparse double matrix
largeDoubleSparse = sprand(10000,10000,0.1,"double");
fprintf("This double matrix has %d non-zeros\n",nnz(largeDoubleSparse))
Let's see how much memory this matrix is using
whos largeDoubleSparse
A single precision version of the same matrix is about 25% smaller in memory, saving about 36 Megabytes in this instance.
largeSingleSparse = single(largeDoubleSparse);
whos largeSingleSparse
Operations with single sparse matrices can be faster
Roughly speaking, since we have fewer bytes to compute when operating on single precision numbers, we can expect to compute things more quickly compared to double precision. Iterative computations are often faster too since we have fewer decimal places of precision to worry about.
As an example, let's create a large double sparse random matrix and time how long it takes to find the largest 6 singular values using svds
rng(5); % Seed the random number generator for reproducibility
randDoubleSparse = sprand(40000,10000,0.0005,"double");
tic
svds(randDoubleSparse,6,"largest")
doubleSvdsTime = toc
Now I'll cast the large random matrix to single precision so that the only difference between these two matrices is the precision.
randSingleSparse = single(randDoubleSparse);
I perform the svds calculation on the result
tic
svds(randSingleSparse,6,"largest")
singleSvdsTime = toc
Which is a very nice speed-up!
fprintf("The speed-up gained from using single precision was %f times\n",doubleSvdsTime/singleSvdsTime)
The primary reason for this is that the tolerance is reduced for single inputs. svds sets the default tolerance to 1e-5 instead of 1e-10 because 1e-10 is not reachable in single-precision. This is something you should always bear in mind when using single precision for anything; yes, it can be faster but you always pay the cost of reduced precision.
The reduction in precision isn't the only reason why this operation is faster though. Let's request the exact same tolerance in both double and single cases
tic; svds(randDoubleSparse,6,"largest",Tolerance=1e-4); toc
single is still faster.
tic; svds(randSingleSparse,6,"largest",Tolerance=1e-4); toc
Several other iterative solvers such as eigs, pcg, minres and gmres might also be faster when using single sparse matrices for similar reasons but I haven't investigated.
Matrix-vector products are faster too
An example of a non-iterative algorithm where moving to single sparse gives a performance boost is multiplying a sparse matrix by a dense vector. Even for large matrices, this is a fast operation so I'll do it 100 times and time that.
First in double precision
randDoubleSparse = sprand(80000,80000,0.0006,"double");
randDoubleVector = rand(80000,1,"double");
tic
for repeats = 1:100
singleSquared = randDoubleSparse * randDoubleVector;
end
toc
and now in single precision
randSingleSparse = sprand(80000,80000,0.0006,"single");
randSingleVector = rand(80000,1,"single");
tic
for repeats = 1:100
singleSquared = randSingleSparse * randSingleVector;
end
toc
Again, this is another nice speed-up.
Speaking of speed....
Single Sparse works on GPUs and distributed arrays as well
As part of a cross-team collaboration, the Parallel Computing Toolbox team ensured that you can use single precision sparse matrices on GPUs too. You can transfer single sparse matrices created earlier from main memory to the GPU using gpuArray
gpuSparse = gpuArray(mySparseSingleMat)
or you can create them directly in GPU memory
R = gpuArray.sprand(50,100,0.001,"single")
All GPU functions that can compute with double sparse have also had single sparse enabled.
Let's transfer those 80,000 x 80,000 sparse matrices I created earlier onto the GPU
randDoubleSparseGpu = gpuArray(randDoubleSparse);
randSingleSparseGpu = gpuArray(randSingleSparse);
and time how long it takes to multiply them together using gputimeit. I'll also time how long it takes to do this on the CPU for comparison
singleCPUTime = timeit(@() randSingleSparse * randSingleSparse);
doubleCPUTime = timeit(@() randDoubleSparse * randDoubleSparse);
singleGpuTime = gputimeit(@() randSingleSparseGpu * randSingleSparseGpu);
doubleGpuTime = gputimeit(@() randDoubleSparseGpu * randDoubleSparseGpu);
fprintf("The CPU did the sparse double matrix multiplication in %.3fs\n" + ...
"The CPU did the sparse single matrix multiplication in %.3fs\n\n" + ...
"The GPU did the sparse double matrix multiplication in %.3fs\n" + ...
"The GPU did the sparse single matrix multiplication in %.3fs\n" ...
,doubleCPUTime, singleCPUTime ,doubleGpuTime,singleGpuTime);
Not only is the GPU version of this computation much faster than the CPU version on my machine but you also see a speed-up when moving from double to single as you'd expect.
Similarly, the team has added supported for Distributed Arrays so now you can create and use single precision sparse matrices that are so big you need to distribute them over multiple nodes of a High Performance Computing (HPC) cluster.
Is single sparse one new feature or many new features?
While putting together this blog post, I found 4 entries in the Release Notes related to single sparse matrices in R2025a. The first entry was in MATLAB itself and referred to the fact that we now have them. Two entries were for the GPU and Distributed Array support discussed above from Parallel Computing Toolbox and one entry referred to calling MATLAB from C++ since single-precision sparse matrices work there too now!
That one entry in the MATLAB release notes mentioned the 6 creation functions that have been updated: sparse, speye, spalloc, sprand, sprandn and sprandsym. If I were the lead developer of this tranche of work I'd be arguing that I had at least 6 new features and not one.
Actually, If I were the lead developer of this, I'd be arguing that this is dozens of new features because all functions in MATLAB that support double-precision sparse matrices including elementary math, linear algebra, and sparse matrix functions now also support single precision sparse matrices. All of those functions had to be modified, tested and get updated documentation.
It's an astonishing amount of work.
I think that this is also an illustration of why different people in MathWorks can give different feature counts when talking about new releases. Like all the truths we cling to, it depends on your point of view.
More informative display for sparse matrices
Another feature that hasn't even been mentioned in the release notes is the improved display of sparse matrices. In R2024a and earlier, it looked like this
sparse([0 0;0 0.2])
ans =
(2,2) 0.2000
compared to the more informative
sparse([0 0;0 0.2])
This was actually done in R2024b but it's even more meaningful now that single precision sparse matrices are possible.
What happens when we open MAT files containing single sparse matrices in old versions of MATLAB
If you save a single-precision sparse matrix to a MAT file, you can load it into an earlier release of MATLAB. However, how the data is loaded depends on the release of MATLAB:
- MATLAB R2021b through R2024b — MATLAB warns that the data has an unsupported data type and loads it as a double-precision sparse matrix.
% This line was run in R2024b
load("from25a.mat")
- MATLAB R2021a and earlier — MATLAB loads the data as a single-precision sparse matrix, but you cannot use the matrix because its data type is unsupported. Attempting to use the matrix can result in undefined behavior.
Style tip: Use "single" and not 'single' when creating single sparse matrices
MATLAB old-timers like me are often guilty of using character arrays such as 'single' when we really should get with the times and use strings, i.e. "single". We seem to get away with it too
smallSparse = sprand(50,100,0.001,'single') % Don't use 'single' like this
We really should be using strings though!
smallSparse = sprand(50,100,0.001,"single") % Using "single" is much better
One good reason for this is so that the operation will fail more gracefully in older versions of MATLAB. Obviously, specifying the type of a sparse will fail in older versions of MATLAB since it wasn't supported but which type of failure would you prefer here?
% Run this line in R2024b
smallSparse = sprand(50,100,0.001,"single")
We get an error message. Admittedly its a little confusing but since we couldn't go back in time and change this to something like "Choosing the data type for sparse is not supported" its a lot better than the alternative:
%Run this line in R2024b
smallSparse = sprand(50,100,0.001,'single')
When this first happened to me, it took a little bit of thought to figure out what was going on. When I say 'thought' I mean 'looking at the documentation' which explains the situation. There's a version of sprand that accepts 4 input arguments:
R = sprand(m,n,density,rc) creates a matrix that also has reciprocal condition number approximately equal to rc.
The matrix R is constructed from a sum of matrices of rank one.
If you give MATLAB a character array where it is expecting a numerical array, it converts it to its ASCII numerical equivalent:
double('single')
This then gets used as the 4th input argument to sprand and weirdness ensues.
The fact that using "single" instead of 'single' is useful here was not a design decision. It's just a concrete reason I discovered while writing this post to prefer the newer syntax.
My advice is always to use strings instead of character arrays whenever you can. It's newer and tends to be where development focuses its attention on these days.
Will you be using single precision sparse matrices?
Many users have been asking for this functionality for a long time. Are you one of them? Will you be using these features? What is your application? Tell us all about it in the comments section.
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.