эффективный способ взять степени вектора

Я написал код, который численно использует полиномы Лежандра до некоторого высокого n-го порядка. Например:

....
case 8 
p = (6435*x.^8-12012*x.^6+6930*x.^4-1260*x.^2+35)/128; return
case 9 
...

Если vectorx длинный, это может стать медленным. Я увидел разницу в производительности между, скажем, x.^4 и x.*x.*x.*x, и подумал, что могу использовать это для улучшения своего кода. Я использовал timeit и нашел это для:

x=linspace(0,10,1e6);
f1= @() power(x,4)
f2= @() x.4;
f3= @() x.^2.^2
f4= @() x.*x.*x.*x

f4 быстрее в факторе 2, чем остальные. Однако, когда я перехожу к x.^6, разница между (x.*x.*x).^2 и x.*x.*x.*x.*x.*x очень мала (в то время как все остальные варианты медленнее).

Можно ли сказать, какой будет наиболее эффективный способ получить степень вектора? Можете ли вы объяснить, почему такая большая разница в производительности?


person Community    schedule 24.09.2013    source источник


Ответы (3)


Это не совсем ответ на ваш вопрос, но он может решить вашу проблему:

x2 = x.*x; % or x.^2 or power(x,2), whichever is most efficient
p = ((((6435*x2-12012)*x2+6930)*x2-1260)*x2+35)/128

Таким образом, вы выполняете степень только один раз и только с показателем степени 2. Этот трюк можно применить ко всем полиномам Лежандра (в полиномах нечетной степени один x2 заменяется x).

person Luis Mendo    schedule 24.09.2013

Вот некоторые мысли:

power(x,4) и x.^4 эквивалентны (просто прочитайте документ).

x.*x.*x.*x, вероятно, оптимизирован для чего-то вроде x.^2.^2


x.^2.^2, вероятно, оценивается как: возьмем квадрат каждого элемента (быстро) и снова возьмем квадрат этого (снова быстро).

x.^4, вероятно, напрямую оценивается как: Возьмем четвертую степень каждого элемента (медленно).

Не так уж и странно видеть, что 2 быстрые операции занимают меньше времени, чем 1 медленная операция. Очень жаль, что оптимизация не выполняется в случае мощности 4, но, возможно, она не всегда будет работать или будет стоить денег (проверка ввода, память?).


По поводу таймингов: На самом деле разница гораздо больше, чем фактор 2!

Теперь, когда вы вызываете их в функции, накладные расходы функции добавляются в каждом случае, уменьшая относительные различия:

y=x;tic,power(x,4);toc
y=x;tic,x.^4;toc
y=x;tic,x.^2.^2;toc
y=x;tic,x.*x.*x.*x;toc

дам:

Elapsed time is 0.034826 seconds.
Elapsed time is 0.029186 seconds.
Elapsed time is 0.003891 seconds.
Elapsed time is 0.003840 seconds.

Таким образом, разница почти в 10 раз. Однако обратите внимание, что разница во времени в секундах все еще незначительна, поэтому для большинства практических приложений я бы просто использовал простой синтаксис.

person Dennis Jaheruddin    schedule 25.09.2013
comment
Оптимизация, предположительно сделанная на x.*x.*x.*x, ведет себя странно. Я пробовал x.*.x.* ... .*x с различными числами x от 2 до 8, и время увеличивается более или менее линейно. Я ожидал ударов; например, случай 8 (=> x.^2.^2.^2: три операции мощности) должен занимать меньше времени, чем 7 (=> больше операций мощности) - person Luis Mendo; 25.09.2013
comment
@LuisMendo Я не знаю, как проверить, но могу представить, что он выполняет только 1 шаг (без вложенной оптимизации). Затем для 7 оно уменьшится до чего-то вроде: x.^2*x.^2*x.^2.*x, что будет не медленнее, чем x.^2*x.^2*x.^2.*x.^2 для 8. Если бы выполнение 8 было быстрее, чем выполнение 7 таким образом, Mathworks, вероятно, реализовал бы такого рода оптимизацию в функции степени. - person Dennis Jaheruddin; 25.09.2013
comment
Да, это может быть объяснением: нет вложенности - person Luis Mendo; 25.09.2013
comment
@DennisJaheruddin, я думаю, ты прав. Посмотрите мой ответ (который я сочинял, когда вы ответили) - вложение на удивление в 2 раза медленнее для 16-й степени. - person mbauman; 25.09.2013

Кажется, что Mathworks имеет специальные квадраты в своей степенной функции (к сожалению, все это встроено в закрытый исходный код, который мы не можем видеть). В моем тестировании на R2013b оказалось, что .^, power и realpow используют один и тот же алгоритм. Я полагаю, что для квадратов в специальном регистре это x.*x.

1.0x (4.4ms):   @()x.^2
1.0x (4.4ms):   @()power(x,2)
1.0x (4.5ms):   @()x.*x
1.0x (4.5ms):   @()realpow(x,2)
6.1x (27.1ms):  @()exp(2*log(x))

С кубами дело обстоит иначе. Они больше не имеют специального регистра. Опять же, .^, power и realpow все похожи, но на этот раз намного медленнее:

1.0x (4.5ms):   @()x.*x.*x
1.0x (4.6ms):   @()x.*x.^2
5.9x (26.9ms):  @()exp(3*log(x))
13.8x (62.3ms): @()power(x,3)
14.0x (63.2ms): @()x.^3
14.1x (63.7ms): @()realpow(x,3)

Давайте перейдем к 16-й степени, чтобы увидеть, как масштабируются эти алгоритмы:

1.0x (8.1ms):   @()x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x
2.2x (17.4ms):  @()x.^2.^2.^2.^2
3.5x (27.9ms):  @()exp(16*log(x))
7.9x (63.8ms):  @()power(x,16)
7.9x (63.9ms):  @()realpow(x,16)
8.3x (66.9ms):  @()x.^16

Итак: .^, power и realpow все выполняются за постоянное время в отношении экспоненты, если только она не была в особом регистре (-1 также, похоже, была в особом регистре). Использование трюка exp(n*log(x)) также обеспечивает постоянное время в отношении экспоненты и быстрее. Единственный результат, я не совсем понимаю, почему повторное возведение в квадрат выполняется медленнее, чем умножение.

Как и ожидалось, увеличение размера x в 100 раз увеличивает время одинаково для всех алгоритмов.

Итак, мораль истории? При использовании скалярных целочисленных показателей всегда выполняйте умножение самостоятельно. В power и его друзьях много хитростей (экспонента может быть с плавающей запятой, вектором и т. д.). Единственными исключениями являются случаи, когда Mathworks выполнил оптимизацию за вас. В 2013b, кажется, x^2 и x^(-1). Надеюсь, со временем они добавят еще. Но в целом возведение в степень сложно, а умножение легко. В коде, чувствительном к производительности, я не думаю, что вы ошибетесь, всегда вводя x.*x.*x.*x. (Конечно, в вашем случае следуйте совету Луиса и используйте промежуточные результаты в каждом семестре!)

function powerTest(x)

f{1} = @() x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x.*x;
f{2} = @() x.^2.^2.^2.^2;
f{3} = @() exp(16.*log(x));
f{4} = @() x.^16;
f{5} = @() power(x,16);
f{6} = @() realpow(x,16);

for i = 1:length(f)
    t(i) = timeit(f{i});
end

[t,idxs] = sort(t);
fcns = f(idxs);

for i = 1:length(fcns)
    fprintf('%.1fx (%.1fms):\t%s\n',t(i)/t(1),t(i)*1e3,func2str(fcns{i}));
end
person mbauman    schedule 25.09.2013