Performance of log() is wildly different in two different contexts and machines for same data?
Show older comments
I am writing code to fit 2D Gaussian functions to white spots on a black image background for a research project. I recently got a new 16 GB RAM 2020 MacBook Pro and was comparing its performance to my old 8 GB RAM 2020 MacBook Pro. When profiling my code on the 16 GB machine, I saw that calling the log() function 10,000 times as part of
fminsearch
took approximately 1.5 seconds per data point. This came as a surprise to me, as running the same code on the 8 GB machine takes approximately 0.01 seconds per data point. The net result is that the overall code takes approximately 6 times longer to run on the 16 GB machine which is unacceptable for my purposes.
Next I checked the CPU load on both machines, reasoning that log() might not be parallelized for some reason on the 16 GB machine, but both machines have very similar CPU load while running the code. I also reinstalled MATLAB on the 16 GB machine with the same toolboxes to see if there was some issue in how I had transferred data from the 8 GB machine to the 16 GB machine, but that did not help.
Each data point is a 9 x 9 (edit: 7 x 7) array of doubles. To investigate further I moved the offending code into a new script on the 16 GB machine and saved a single data point in the workspace. I also added a for loop beneath where I took the log of the data in the matrix 10,000 times. This reproduced the results seen above. (Edit for clarity - I mean that taking the log of the data in the matrix in the for loop is taking the expected 0.01 seconds. It is only in the optimization routine on the 16 GB machine that I'm seeing 1.5 second/log() call runtimes.) So the performance of log() varies reproducibly even on the same machine.
The code in question is below. The installed toolboxes on both machines are identical, except that on the 8 GB machine I have installed the Symbolic Math and Computer Vision toolboxes, while on the 16 GB machine I have installed the Wavelet and Optimization toolboxes. Otherwise both machines have the Bioinformatics, Curve Fitting, Image Processing, and Stats/Machine Learning toolboxes. Both machines are running R2019a (I needed my code to be compatible with older versions of MATLAB and I haven't experienced any other issues so far). Might anyone know what the issue is, and how I could fix it?
Offending code:
x = 491;
y = 16;
circle_rad = 3;
peakdiam = 7;
mask = [15 12 13 13 14 13 11;
12 13 12 12 13 16 15;
15 16 15 14 16 17 13;
21 19 27 40 30 19 11;
15 16 26 31 19 19 14;
12 11 18 30 19 12 14;
13 14 15 14 13 13 13;]
MLE_initial_guess = [circle_rad + 1, circle_rad + 1, peakdiam, sum(mask(:)), min(mask(:))];
neg_log_likelihood_fxn = @(params) NegLogFxn(params, mask, peakdiam) ;
options = optimset ('MaxFunEvals', 10000, 'MaxIter', 10000, 'TolFun', 1e-5);
MLE_output = fminsearch(neg_log_likelihood_fxn, MLE_initial_guess, options);
pixel_offset = [y - circle_rad - 1, x - circle_rad - 1]; % -1 corrects for the +1 offset in the initial guess
MLE_output(1:2) = MLE_output(1:2) + pixel_offset;
fprintf('Y-COORDINATE;\nMLE: %f \nX-COORDINATE:\nMLE: %f \n\n', MLE_output(1), MLE_output(2))
NegLogFxn:
function neglog = NegLogFxn(params, data, array_size)
tdg = twoD_Gaussian(params, array_size);
% disp(tdg)
% neglog = -tdg;
field_of_view = data;
tdg_log = log(tdg);
field_of_view = field_of_view .* tdg_log;
field_of_view = field_of_view - tdg;
neglog = -1 * sum(field_of_view(:));
end
twoD_Gaussian:
function psf = twoD_Gaussian(params, array_size)
[x, y] = meshgrid(1:array_size, 1:array_size);
psf = params(4)/(2 * pi * params(3)^2);
psf = psf * exp(-((x - params(2)).^2 + (y - params(1)).^2)/(2 * params(3)^2));
psf = psf + params(5);
end
Thanks in advance!
17 Comments
Jan
on 25 Dec 2021
"So the performance of log() varies reproducibly even on the same machine." - Then the description of the different machine is not useful.
Please explain, how you observe the difference in the processing speed.
"I also added a for loop beneath where I took the log of the data in the matrix 10,000 times. This reproduced the results seen above." - please post the corresponding code.
Lakshay Sood
on 26 Dec 2021
Matt J
on 26 Dec 2021
What timing results do you get from the following on the different machines?
tic
for i = 1:10000
log(mask);
end
toc
T=randi(1e6,1);
tic
log(T);
toc
Lakshay Sood
on 26 Dec 2021
Lakshay Sood
on 26 Dec 2021
Edited: Lakshay Sood
on 26 Dec 2021
Jan
on 26 Dec 2021
Then I come to my initial question again: If you see the difference in the processing speed of log() only, if it is called inside the function to be optimized, how do you know this? Why are you sure, that it is the log() function, which makes the difference?
It is possible, that your observation has another reason. The output of the profiler is not as clear as some tic/tocs: The profiler diesables some optimizations by Matlab's JIT accelerator, e.g. the reordering of the commands. In an expensive for-loop the profiler can claim, that the closing end consumes an important amount of time.
"reasoning that log() might not be parallelized" - please explain, why you assume this.
Currently I still assume, that the difference of the speed has another reason.
Matt J
on 27 Dec 2021
@Lakshay Sood Your fitting routine seems to be based on a Poisson image noise model. Since you have the Optimization Toobox on your 16 GB machine, and if you are happy to accept a Gaussian noise model, at least temporarily, you could try gaussfitn,
If gaussfitn proves to be faster, this might at least let you progress better until the issue with log() is better understood.
Lakshay Sood
on 27 Dec 2021
Edited: Lakshay Sood
on 27 Dec 2021
Matt J
on 27 Dec 2021
It still might be worth using the solution from a Gaussian model to initialize the Poisson model optimization in the interest of reducing the number of fminsearch iterations required. fminsearch is not built for speed. For that matter, it would be worth reimplementing the Poisson optimization with fmincon, with SpecifyObjectiveGradient set to 'true'.
Lakshay Sood
on 29 Dec 2021
Jan
on 29 Dec 2021
The efficiency of
tic
for i = 1:1:10000
log(mask);
end
toc
can be caused by a dead-code-elimination of Matlab's JIT acceleration. Try this:
function cuteTest
mask = rand(16, 16);
tic
for i = 1:10000
log(mask);
end
toc
mask = rand(16, 16);
tic
for i = 1:10000
mask(1) = mask(1) + 1;
q = log(mask);
end
toc
end
In Matlab R2018b the speed is equal, but modern Matlab version might optimize the code more aggressively.
Have you checked how many iterations fminsearch is executing on each machine? More generally, have you compared the "output" argument returned on each machine,
[x,fval,exitflag,output] = fminsearch(___)
to see if both give the same termination conditions. Maybe, due to some numerical subtlety, the optimization is just running for more iterations, or doing more function evaluations, on the 16 GB machine.
In any case, could you post screenshots of your profiling results?
Lakshay Sood
on 30 Dec 2021
Edited: Lakshay Sood
on 30 Dec 2021
@Lakshay Sood If you look again at your original post, you will see that I have edited it and applied the Run button to your code. The code throws an error message, and does not run to completion. So, whatever tests results you have been showing us, it is for different code. It would be helpful to have the actual code.
Lakshay Sood
on 30 Dec 2021
Lakshay Sood
on 30 Dec 2021
Accepted Answer
More Answers (1)
Jan
on 30 Dec 2021
Summary:
On the 16 GB M1 MacBook, the lines
tic
tdg_log = log(tdg);
toc
displays, that the processing needs 1.5 seconds. Using the same data, a loop with 10'000 iterations takes 0.008 seconds:
tic
for i = 1:1:10000
log(mask);
end
toc
This means, that the 8GB Intel MacBook is 1'875'000 times faster for this operation.
There are some reasonable ideas to explain this:
- The Matlab version you use has a massive problem with the M1 CPU.
- The profiler is completely confused. (But the total run-time seems to exclude this option)
- You do not run the same code. Matt J found out, that the code posted here in the forum does not run at all, but stops with an error.
A speed difference of the factor of almost 2 million is extremely unlikely and cannot be caused by a problem with the multithreading. For tiny 9x9 matrices starting mutliple threads would be a waste of time in all cases, so maybe the log() function does apply a multithreading on the M1 chip, but this would be strange. The Matlab functions of 2019 do not include special adjustments for the M1 chip, which was invented later.
By the way, the function twoD_Gaussian2 can be improved:
function psf = twoD_Gaussian2(params, array_size)
x = 1:array_size;
k = 2 * params(3)^2;
psf = params(4) / (k * pi) * ...
exp(-(x - params(2)).^2 / k) .* exp(-(x.' - params(1)).^2 / k) + ...
params(5);
end
Using meshgrid to create an NxN matrix requires to calculate N*N expensive exp() functions. Using the vectors instead and the relation: exp(a+b) = exp(a)*exp(b) is cheaper. For a 9x9 matrix this is 4 times faster and the benefit grows quadratically with the array size.
2 Comments
Lakshay Sood
on 30 Dec 2021
Jan
on 30 Dec 2021
I was confused by the names "8GB MacBook Pro 2020" and "16GB MacBook Pro M1".
Categories
Find more on MATLAB in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!