Linear interpolation with interp1 is slow: How to improve run times?

83 views (last 30 days)
I wrote a MWE to compare the perforance of interp1 (MATLAB built-in) with my custom interp1_scal (for linear interpolation), using a simple dynamic programming problem solved with value function iteration
$$ V(k) = max_{k'} \log(k^\alpha-k')+\beta*V(k') $$
Question 1: I am a bit surprised that the custom interpolation routine I wrote is much faster than the built-in interp1 for linear interpolation (on my laptop with R2024b, it is 4 times faster, here (RUN button) the speed advantage is smaller but still significant). To find the location of the query point xi on the x grid, I am using binary search (see function locate), directly translated from an older Fortran/C routine.
Question 2: Am I using interp1 in the wrong way? would using griddedInterp or other Matlab routines significantly improve run times of this MWE?
Any help, suggestions or comments is greatly appreciated!
P.S. I am glad to provide additional info on the dynamic programming problem that this simple MWE is meant to caputure in few lines of code, if someone asks for.
Minimum Working Example here:
clear,clc,close all
%% Parameters
maxit = 50;
tol = 1e-5;
alpha = 0.33;
beta = 0.96;
k_ss = (alpha * beta)^(1 / (1 - alpha));
n_k = 300;
k_min = 0.5 * k_ss;
k_max = 1.5 * k_ss;
k_grid = linspace(k_min, k_max, n_k)';
% Precompute output y = k^alpha
y_grid = k_grid .^ alpha;
%% Method 1: value function iteration with Matlab built-in interpolation
tic
% Initialization
V0 = zeros(n_k, 1);
V1 = zeros(n_k, 1);
% Value Function Iteration
for iter=1:maxit
for i = 1:n_k
y_val = y_grid(i);
k_max_now = min(y_val, k_max); % upper bound for Brent minimization
% Objective function (negative value for minimization)
obj = @(kp) -(log(y_val-kp) + beta*interp1(k_grid,V0,kp,'linear'));
% Optimal policy
kp_opt = fminbnd(obj, k_min, k_max_now);
% Updated value function
V1(i) = -obj(kp_opt);
end
% Convergence check
err = max(abs(V1 - V0));
V0 = V1;
end %end iter
time_vfi1=toc;
%% Method 2: value function iteration with *custom* interpolation
tic
% Initialization
V0 = zeros(n_k, 1);
V2 = zeros(n_k, 1);
% Value Function Iteration
for iter=1:maxit
for i_k = 1:n_k
y_val = y_grid(i_k);
k_max_now = min(y_val, k_max);
% Objective function (negative value for minimization)
obj = @(kp) -( log(y_val-kp) + beta*interp1_scal(k_grid,V0,kp) );
% Optimal policy
kp_opt = fminbnd(obj, k_min, k_max_now);
% Updated value function
V2(i_k) = -obj(kp_opt);
end
% Convergence check
err = max(abs(V2 - V0));
V0 = V2;
end %end iter
time_vfi2=toc;
%% Compare vfi1 and vfi2
err_V = max(abs(V1-V2));
disp('RESULTS OF COMPARISON')
RESULTS OF COMPARISON
fprintf('||V1-V2|| = %f \n',err_V)
||V1-V2|| = 0.000000
fprintf('Run time of vfi1 with Matlab interp = %f \n',time_vfi1)
Run time of vfi1 with Matlab interp = 1.293248
fprintf('Run time of vfi2 with custom interp = %f \n',time_vfi2)
Run time of vfi2 with custom interp = 0.751512
fprintf('time vfi1 / time_vfi2 = %f \n',time_vfi1/time_vfi2)
time vfi1 / time_vfi2 = 1.720861
%% Plot value function
figure;
plot(k_grid, V1, 'LineWidth', 1.5);
hold on
plot(k_grid, V2, 'LineWidth', 1.5);
legend('V1','V2')
xlabel('Current capital');
grid on;
% Fast linear interpolation routine
% Usage:
% yi = interp1_scal(x,y,xi)
% where x and y are column vectors with n elements, xi is a scalar
% and yi is a scalar
% Input Arguments
% x - Sample points
% column vector
% Y - Sample data
% column vector
% xi - Query point
% scalar
function yi = interp1_scal(x,y,xi)
n = size(x,1);
j = locate(x,xi);
j = max(min(j,n-1),1);
slope = (y(j+1)-y(j))/(x(j+1)-x(j));
yi = y(j)+(xi-x(j))*slope;
end %end function interp1_scal
function jl = locate(xx,x)
%function jl = locate(xx,x)
%
% x is between xx(jl) and xx(jl+1)
%
% jl = 0 and jl = n means x is out of range
%
% xx is assumed to be monotone increasing
n = length(xx);
if x<xx(1)
jl = 0;
elseif x>xx(n)
jl = n;
else
jl = 1;
ju = n;
while (ju-jl>1)
jm = floor((ju+jl)/2);
if x>=xx(jm)
jl = jm;
else
ju=jm;
end
end
end
end %end function locate
  1 Comment
Stephen23
Stephen23 on 27 Dec 2025 at 19:19
Question 1: By avoiding the input checking, error handling, and handling of different input configurations it is often possible to write code for domain-specific code which is faster than MATLAB's generic inbuilt Mfiles. This is not unexpected.
Question 2: Your use of INTERP1 seems okay. I doubt that GRIDDEDINTERPOLANT would be faster, unless you can provide the sample points once and the query points in large arrays. You might be interested in trying INTERP1Q, which essentially does the same as your custom function:

Sign in to comment.

Accepted Answer

Paul
Paul on 27 Dec 2025 at 15:26
Hi Alessandro,
The base version of interp1 is an ordinary m-file, so you can see all of the extra processing it needs to do to account for all possible inputs, whereas interp1_scal only works for column vector sample points with linear interpolation and no extrapolation and a scalar query point.
To that last point, consider the comparison when one wants to interpolate lots of data points and we have to write a wrapper around interp1_scal to loop over the query points.
x = (0:.01:10).';
y = sin(x);
rng(100);
xq = rand(1e4,1)*x(end);
ntrials = 40;
[texec1,texec2] = deal(nan(ntrials,1));
for ii = 1:ntrials
tic,yi1 = interp1(x,y,xq); texec1(ii) = toc;
tic,yi2 = interp1_scal_vec(x,y,xq); texec2(ii) = toc;
end
figure
plot([texec1,texec2]),grid,legend('interp1','interp1_scal','Interpreter','none');
% results aren't identical, but very close
[max(abs(yi1-yi2)), norm(yi1-yi2)]
ans = 1×2
1.0e-14 * 0.0222 0.4965
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
I think timeit does not include the first call
timeit(@() interp1(x,y,xq))
ans = 4.5437e-05
timeit(@() interp1_scal_vec(x,y,xq))
ans = 0.0021
Calling with a scalar input, as in the question, does show an advantage for the specialized code, which might become more pronounced if converted to mex.
timeit(@() interp1(x,y,xq(1)))
ans = 7.5319e-06
timeit(@() interp1_scal(x,y,xq(1)))
ans = 2.2946e-06
function yi = interp1_scal_vec(x,y,xq)
yi = nan(size(xq));
for ii = 1:numel(xq)
yi(ii) = interp1_scal(x,y,xq(ii));
end
end
function yi = interp1_scal(x,y,xi)
n = size(x,1);
j = locate(x,xi);
j = max(min(j,n-1),1);
slope = (y(j+1)-y(j))/(x(j+1)-x(j));
yi = y(j)+(xi-x(j))*slope;
end %end function interp1_scal
function jl = locate(xx,x)
%function jl = locate(xx,x)
%
% x is between xx(jl) and xx(jl+1)
%
% jl = 0 and jl = n means x is out of range
%
% xx is assumed to be monotone increasing
n = length(xx);
if x<xx(1)
jl = 0;
elseif x>xx(n)
jl = n;
else
jl = 1;
ju = n;
while (ju-jl>1)
jm = floor((ju+jl)/2);
if x>=xx(jm)
jl = jm;
else
ju=jm;
end
end
end
end %end function locate
  5 Comments
Alessandro
Alessandro on 27 Dec 2025 at 21:25
Edited: Alessandro on 27 Dec 2025 at 21:26
I have compared also interp1 with interp1q (based on @Stephen23 suggestion) using @Paul data
clear,clc,close all
x = (0:.01:10).';
y = sin(x);
rng(100);
xq = rand(1e4,1)*x(end);
timeit(@() interp1(x,y,xq))
ans = 6.6625e-05
timeit(@() interp1q(x,y,xq))
ans = 6.5462e-04
But on my laptop interp1q is not faster. Here it seems that interp1 is the winner. Maybe matlab does not maintain anymore interp1q? I read on the doc that interp1q is NOT recommended.
John D'Errico
John D'Errico on 28 Dec 2025 at 1:19
A lookup table is easy enough to do.
x = cumsum(randi(3,[1,10000]))
x = 1×10000
1 2 3 6 7 8 10 12 13 15 18 19 20 21 23 26 29 32 35 37 39 40 43 45 48 50 51 53 56 57
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
[min(x),max(x)]
ans = 1×2
1 20109
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Now, suppose you will come into this with some random number in that interval? You could use bisection, but there are MANY intervals to search through. A bisection code can take roughly
log2(10000)
ans = 13.2877
tests. While that is finite, you might be better off if you precompute many possible results in advance, using a vectorized tool like discretize.
xb = x(1):1:x(end);
tic,xbindex = discretize(xb,x);toc
Elapsed time is 0.007563 seconds.
Yes, this will take some time, but as you can see, not a lot. But now for any value of xb, you already know which interval it lives in. That takes no more than an index into the vector xbindex.
If xi is a continuous variable, it may then require an extra step to refine the result, but you will be quite close.

Sign in to comment.

More Answers (0)

Products


Release

R2024b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!