У меня есть два цикла for для очень больших массивов (10k x 10k) или больше. Очевидно, что эта часть программы является огромным узким местом и требует очень много времени.

Есть 4 массива: vm(10000,1), va(10000,1), yr(10000,10000) и yi(10000,10000)

for i = 1: 10000
    psum = 0;
    for j = 1: 10000
    psum = psum + vm(i)*vm(j)*(yr(i,j)*cos(va(i)-va(j)) + yi(i,j)*sin(va(i)-va(j)));
    end
pcal(i) = psum;
end
1
Jose Vivas 6 Сен 2016 в 03:39

3 ответа

Лучший ответ

В вашем случае подсчитать сумму за один раз просто. По сути, вы создаете массивы, элементы которых являются соответствующими произведениями и различиями vm и va, соответственно (с использованием bsxfun), с последующим поэлементным умножением и суммированием по строкам.

pcal = sum(bsxfun(@times,vm,vm') .* (...
    yr.*cos(bsxfun(@minus,va,va')) + ...
    yi.*sin(bsxfun(@minus,va,va'))),2);

Обратите внимание, что при векоризации вы склонны торговать памятью против циклов процессора. Если у вас недостаточно оперативной памяти, вы можете закончить разбиение на страницы, что замедлит сканирование векторизованного решения.

1
Jonas 6 Сен 2016 в 07:53

Вы можете переформулировать уравнение в соответствии с тезисами тригонометрических тождеств:

sin(a-b) = sin a cos b - cos a sin b;
cos(a-b) = cos a cos b + sin a sin b;

Поэтому предварительно вычислите синус и косинус и используйте их в цикле или bsxfun. это версия цикла:

yr = rand(10000);
yi = rand(10000);
va = rand(1,10000);
vm = rand(1,10000);
sin_va = sin(va);
cos_va = cos(va);
for i = 1: 10000
    pcal(i) =  sum(vm(i)*vm.*(yr(i,:).*(cos_va(i) * cos_va + sin_va(i) * sin_va) + yi(i,:).*(sin_va(i) * cos_va - cos_va(i) * sin_va)));
end
0
rahnema1 6 Сен 2016 в 22:01

Самым быстрым вариантом будет ответ @Joans. Однако, если вы столкнетесь с проблемами памяти, вот вариант полу-векторизации (только один цикл):

pcal = zeros(N,1); % N is the 10000 in your example
for m = 1: N
    va_va = va(m)-va(1:N);
    pcal(m) = sum(vm(m)*vm(1:N).*(yr(m,1:N).'.*cos(va_va)+yi(m,1:N).'.*sin(va_va)));
end

А вот сравнительный анализ этого метода вместе с вашим и @Joans, а также другим методом с использованием ndgrid:

function sum_time
N = 10000;
vm = rand(N,1);
va = rand(N,1);
yr = rand(N);
yi = rand(N);

loop_time = timeit(@() loop(N,vm,va,yr,yi))
loop2_time = timeit(@() loop2(N,vm,va,yr,yi))
bsx_time = timeit(@() bsx(vm,va,yr,yi))
ndg_time = timeit(@() ndg(N,vm,va,yr,yi))
end

function pcal = loop(N,vm,va,yr,yi)
pcal = zeros(N,1);
for m = 1: N
    psum = 0;
    for n = 1: N
        psum = psum + vm(m)*vm(n)*(yr(m,n)*cos(va(m)-va(n)) +...
            yi(m,n)*sin(va(m)-va(n)));
    end
    pcal(m) = psum;
end
end

function pcal = loop2(N,vm,va,yr,yi)
pcal = zeros(N,1);
for m = 1: N
    va_va = va(m)-va(1:N); % to avoid calculating twice
    pcal(m) = sum(vm(m)*vm(1:N).*(yr(m,1:N).'.*cos(va_va)+yi(m,1:N).'.*sin(va_va)));
end
end

function pcal = bsx(vm,va,yr,yi)
pcal = sum(bsxfun(@times,vm,vm') .* (...
    yr.*cos(bsxfun(@minus,va,va')) + ...
    yi.*sin(bsxfun(@minus,va,va'))),2);
end

function pcal = ndg(N,vm,va,yr,yi)
[n,m] = ndgrid((1:N).',1:N);
yr_t = yr.';
yi_t = yi.';
va_va = va(m(:))-va(n(:));
vmt = vm(m(:)).*vm(n(:));
psum = vmt.*(yr_t(1:N^2).'.*cos(va_va)+yi_t(1:N^2).'.*sin(va_va));
pcal = sum(reshape(psum,N,N)).';
end

И результаты (для N = 10000):

loop_time =
       7.0296
loop2_time =
       3.3722
bsx_time =
       1.2716
ndg_time =
       6.3568

Таким образом, выход за один цикл экономит ~ 50% времени.

0
EBH 6 Сен 2016 в 11:21