apximhd: (Default)
[personal profile] apximhd
Подходит к концу моя студенческая лицензия на Mathematica 9 и я задумался о приобретении собственной персональной лицензии на один из программных продуктов. Выбор профессиональных инструментов не особенно велик. Собственно, всего три варианта:

Maple Personal edition — $299.00
Mathematica Home Edition — $295.00
Matlab + Image Processing Toolbox + Symbolic Math Toolbox +
Partial Differential Equation Toolbox — $135 + $39 + $39 +$39 = $252

В этом отношении Matlab выглядит привлекательнее по цене, и остаётся запас для приобретения ещё одного тулбокса, правда, несколько смущает тот факт, что за ту же цену MapleSoft и Wolfram предлагают гораздо более полный набор инструментов, хотя что мне до них, если большую их часть я всё равно не буду использовать.
Поэтому следующим вопросом стал вопрос удобства использования и скорости работы.



Для начала я написал коротенький тест на скорость умножения матриц. Здесь matlab откровенно порадовал, оставив всех далеко позади.

Matlab 2015a:
% File: TestSpeed.m
N = 5000;
tic              % Запускаем "таймер"
A = rand (N, N); % Создаём матрицу nxn и заполняем случайными числами
AM=A'*A;         % Произведение исходной и транспонированной матриц
toc              % Останавливаем "таймер"
Время вычислений: 4.3 с.

Mathematica 9:
(* File: TestSpeed.nb *)
n = 5000;
Start = AbsoluteTime[]; (* Запускаем "таймер" *)
A = Table[RandomReal[], {i, n}, {j, n}];(* Создаём матрицу nxn
                                          и заполняем случайными числами *)
AT = Transpose[A]; (* Транспонируем матрицу *)
AM = AT.A; (* Вычисляем произведение исходной и транспонированной матриц *)
AbsoluteTime[] - Start (* Останавливаем "таймер" *)
Время вычислений: 21.9 с.

Gfortran 4.9.1:
! File: TestSpeed.f90
program main
  integer, parameter :: n = 5000
  real a(n,n),at(n,n),am (n,n),ss ! Три матрицы nxn и переменная "таймера"
  ss = secnds(0.)       ! Запускаем "таймер"
  call random_number(a) ! Заполняем матрицу случайными числами
  at = transpose(a)     ! Транспонируем матрицу
  am = matmul(at,a)     ! Произведение исходной и транспонированной матриц
  ss = secnds(0.) - ss  ! Останавливаем "таймер"
  write(6,"('Processing time', f8.3, ' sec')"), ss
end program main
Время вычислений: 82.3 с.

Maple 18:
restart : with(LinearAlgebra) :
start := time() :
A := RandomMatrix(1000, 1000) :
MatrixMatrixMultiply(Transpose(A), A) :
time()-start
Время вычислений: 91.2 с.
Увы, дождаться же перемножения матриц 5000х5000 мне не удалось.

Результат Maple не лезет ни в какие ворота, с ним ещё предстоит разбираться отдельно, а вот фортран откровенно огорчил, но тут, видимо, проблема не в самом фортране, а в неоптимальной реализации библиотечных процедур работы с матрицами. Очевидно, что переписав их самому, можно достичь такого же, если не лучшего результата, что и в Matlab.
Однако мне в реальной жизни не нужно перемножать такие матрицы, а если и нужно, то не больше, чем 4х4. Основные численные расчеты, с которыми я работаю, относятся к решению дифференциальных уравнений. Поэтому для следующего теста я выбрал классическую задачу двух тел, в первую очередь, потому что для неё известно точное аналитическое решение.
И вот, что получилось...

На этот раз начну с фортрана, поскольку на нем у меня давно написаны все рабочие алгоритмы. Задача решалась модифицированным методом Рунге-Кутты-Фельберга (Runge–Kutta–Fehlberg), более известного как RK45, с переменным шагом интегрирования.
Я привожу программу целиком:

Gfortran 4.9.1:
! File: orbit.f90
Program RK45F
  real*8 RK45 ! Объявление имени функции
  real*8 y(8), T, Tmax, eps, M1, M2, V, dt
  real*8 prec
  common M1, M2, dt
  M1 = 50. ! Масса первого тела
  M2 = 10. ! Масса второго тела
  V = 4.   ! Начальная скорость первого тела
  y(1) = 0. ! Начальная координата x первого тела
  y(2) = 0. ! Начальная координата y первого тела
  y(3) = 5. ! Начальная координата x второго тела
  y(4) = 0. ! Начальная координата y второго тела
  y(5) = 0. ! Начальный компонент скорости Vx первого тела
  y(6) = -V*M2/M1 ! Начальный компонент скорости Vy первого тела
  y(7) = 0. ! Начальный компонент скорости Vx второго тела
  y(8) = V ! Начальный компонент скорости Vy второго тела
  T=0. ! Начальный момент времени
  Tmax = 401*1000. ! 1000 периодов обращения двойной системы
  prec=0.05 ! Точность интегрирования
  dt = 1.  ! Начальный шаг интегрирования
  open (1, file = 'orbit.dat') ! Открываем файл для записи данных
  TOTAL = SECNDS(0.) ! Запускаем "таймер"
  do while (T<=Tmax) ! Главный цикл
    eps = RK45(y)    ! Возвращаем максимальное из ускорений
    dt = prec/eps    ! Чем больше ускорение, тем меньше шаг
    write (1,*), (y(i), i=1,4),eps ! Пишем данные в файл
    T = T + dt       ! Приращение времени
  end do             ! 
  TOTAL = SECNDS(0.0) - TOTAL ! Останавливаем "таймер"
  WRITE(6,1) Total
1 FORMAT('FORTRAN: Total time = ', F6.2, ' sec')
  call system ('gnuplot orbit.plt')
! Вызов внешней программы gnuplot, которая в качестве параметра
! получает файл orbit.plt с инструкциями:
! 
! set terminal windows
! set grid
! set linetype 1 lc rgb "red" lw 1 pt 0
! set linetype 2 lc rgb "blue" lw 1 pt 7
! set title "Orbits" textcolor lt 1
! plot 'orbit.dat' using 1:2 with lines lw 1 title "M1", 'orbit.dat' using 3:4 with lines lw 1 title "M2"
! pause 1000
!
! При запуске под Windows необходимо создать в каталоге командный файл
! gnuplot.cmd, в котором прописать полный путь к программе wgnuplot.exe.
! Например, у меня этот файл состоит из единственной строки:
!
! "C:\Program Files (x86)\Maxima-5.31.2\gnuplot\bin\wgnuplot.exe" %1 %2 %3
!
! При запуске под Linux командный файл создавать не надо, командный
! процессор сам найдет программу gnuplot (если она установлена), но
! из файла orbit.plt необходимо удалить первую и последнюю строки
! (set terminal windows и pause 1000).
  close(1)
End program RK45F

Module Derivatives
! Система дифференциальных уравнений. 
! Функция возвращает максимальное значение модуля вторых производных.
Implicit NONE
Contains
real*8 Function gr(f,y,FLAG)
! y(8) — входной массив
! f(8) — выходной массив
real*8 M1, M2, r12, f(8), y(8), dt
common M1, M2, dt
integer FLAG ! Флаг: вычислять или нет максимальный модуль ускорения

  r12 = sqrt((y(1)-y(3))**2+(y(2)-y(4))**2) ! Расстояние между компонентами
  r12=1.0/(r12**3) ! Оптимизация: замена четырёх делений
                   ! одним делением и четырьмя умножениями.

  f(1)=y(5) ! Компонент скорости Vx первого тела
  f(2)=y(6) ! Компонент скорости Vy первого тела
  f(3)=y(7) ! Компонент скорости Vx второго тела
  f(4)=y(8) ! Компонент скорости Vy второго тела
  f(5)=M2*(y(3)-y(1))*r12 ! Компонент ускорения ax первого тела
  f(6)=M2*(y(4)-y(2))*r12 ! Компонент ускорения ay первого тела
  f(7)=M1*(y(1)-y(3))*r12 ! Компонент ускорения ax второго тела
  f(8)=M1*(y(2)-y(4))*r12 ! Компонент ускорения ay второго тела

if (FLAG.eq.1) then
  gr = dmax1(abs(f(5)),abs(f(6)),abs(f(7)),abs(f(8)))
  ! Максимальный модуль компонента ускорения
else
  gr=0
endif
End Function gr
End Module Derivatives

real*8 Function RK45 (y) ! Реализация модифицированного алгоритма RK45.
                         ! Вычисление погрешности на каждом шаге не
                         ! используется, вместо этого в основной программе
                         ! производится автоматическое изменение шага. 
  use Derivatives
  real*8 M1, M2, dt
  common M1, M2, dt
  real*8 k1(8), k2(8), k3(8), k4(8), k5(8), y(8), y1(8), f(8)
  real*8 eps
  real*8, parameter :: c21=1./4.
  real*8, parameter :: c31=3./32., c32=9./32.
  real*8, parameter :: c41=1932./2197., c42=-7200./2197., c43=7296./2197.
  real*8, parameter :: c51=439./216.,c52=-8.,c53=3680./513.,c54=-845./4104.
  real*8, parameter :: a1=25./216.,a3=1408./2565.,a4=2197./4104.,a5=-1./5.
  integer i
        eps = gr(f,y,0)
        do i=1,8
          k1(i) = dt*f(i)
          y1(i) = y(i)+c21*k1(i)
        end do
        eps = gr(f,y1,0)
        do i=1,8
          k2(i) = dt*f(i)
          y1(i) = y(i)+c31*k1(i)+c32*k2(i)
        end do
        eps = gr(f,y1,0)
        do i=1,8
          k3(i) = dt*f(i)
          y1(i) = y(i)+c41*k1(i)+c42*k2(i)+c43*k3(i)
        end do
        eps = gr(f,y1,0)
        do i=1,8
          k4(i) = dt*f(i)
          y1(i) = y(i)+c51*k1(i)+c52*k2(i)+c53*k3(i)+c54*k4(i)
        end do
        eps = gr(f,y1,1) ! Модуль ускорения вычисляется только
                         ! при последнем обращении к функции
        do i=1,8
          k5(i) = dt*f(i)
          y(i) = y(i)+a1*k1(i)+a3*k3(i)+a4*k4(i)+a5*k5(i)
        end do
        RK45 = eps
End function RK45
Время работы фортрановской программы: 7.6 с.

Фортран как честная числомолотилка ожидаемо занял первое место.
Результат работы приведен на следующем рисунке:



Теперь переходим к Mathematica 9
(* File orbit.nb *)
M1 = 50; M2 = 10; V = 4.0; T = 1000; Start = AbsoluteTime[];
System = {
  x1'[t]==vx1[t],
  vx1'[t]==M2(x2[t]-x1[t])/((x2[t]-x1[t])^2 + (y2[t]-y1[t])^2)^(3/2),
  y1'[t]==vy1[t],
  vy1'[t]==M2(y2[t]-y1[t])/((x2[t]-x1[t])^2 + (y2[t]-y1[t])^2)^(3/2),
  x2'[t]==vx2[t],
  vx2'[t]==M1(x1[t]-x2[t])/((x1[t]-x2[t])^2 + (y1[t]-y2[t])^2)^(3/2),
  y2'[t]==vy2[t],
  vy2'[t]==M1(y1[t]-y2[t])/((x1[t]-x2[t])^2 + (y1[t]-y2[t])^2)^(3/2)
  };
Solution = NDSolve[{System,
    x1[0] == 0, y1[0] == 0,
    vx1[0] == 0, vy1[0] == -V*M2/M1,
    x2[0] == 5, y2[0] == 0,
    vx2[0] == 0, vy2[0] == V},
   {x1, vx1, y1, vy1, x2, vx2, y2, vy2},
   {t, 0, 401*T}, MaxSteps -> 1000000];
ParametricPlot[{{{x1[t], y1[t]} /. Solution}, {{x2[t], y2[t]} /.Solution}},
               {t, 0, 401*T}, PlotPoints -> 10000]
AbsoluteTime[] - Start
Время вычислений: 16.9 с.

Результат приведен на следующем рисунке:



Для Maple 18 я задачу двух тел писать не стал, потому что до этого запускал на нём задачу трёх тел, и она считалась за точно такое же время, как и в Mathematica. Поэтому я предполагаю, что время расчёта в Maple должно составить около 17 секунд.

И наконец, Matlab 2015a:
% File: orbit.m
function orbit()
global M1;
global M2;
M1=50.0; M2=10.0; V = 4.0;
tic
[t,h]=ode45(@body2,[0,401*1000],[0,0,5,0,0,-V*M2/M1,0,V]);
x1=h(:,1); y1=h(:,2);
x2=h(:,3); y2=h(:,4);
plot(x1,y1,x2,y2);
toc

function f=body2(t,x)
global M1;
global M2;
    r12=sqrt((x(1)-x(3))^2+(x(2)-x(4))^2);
    r12=1.0/r12^3;
    f=[x(5);x(6);x(7);x(8);...
        M2*(x(3)-x(1))*r12;...
        M2*(x(4)-x(2))*r12;...
        M1*(x(1)-x(3))*r12;...
        M1*(x(2)-x(4))*r12];
Время работы: 8.4 с.

Производительность матлаба оказалась, на первый взгляд, на высоте, но результат несколько обескураживает:



Очевидно, что функция ode45 в Matlab требует дополнительной настройки. Для того, чтобы получить такой же результат, который дают Mathematica и фортрановская программа, пришлось несколько модифицировать матлабовский сценарий (добаленный текст выделен полужирным):

% File: orbit.m
function orbit()
global M1;
global M2;
M1=50.0; M2=10.0; V = 4.0;
tic
options=odeset('RelTol',0.0000001,'AbsTol',0.0000001,...
               'InitialStep',1.0, 'MaxStep', 1000000);
[t,h]=ode45(@body2,[0,401*1000],[0,0,5,0,0,-V*M2/M1,0,V],options);
x1=h(:,1); y1=h(:,2);
x2=h(:,3); y2=h(:,4);
plot(x1,y1,x2,y2);
toc

function f=body2(t,x)
global M1;
global M2;
    r12=sqrt((x(1)-x(3))^2+(x(2)-x(4))^2);
    r12=1.0/r12^3;
    f=[x(5);x(6);x(7);x(8);...
        M2*(x(3)-x(1))*r12;...
        M2*(x(4)-x(2))*r12;...
        M1*(x(1)-x(3))*r12;...
        M1*(x(2)-x(4))*r12];
Время расчета увеличилось до 22.6 с

Это примерно соответствует времени Mathematica и Maple. Но, по крайней мере, результат стал похож на правду:



Несколько неожиданным оказался тот факт, что функция численного решения дифференциальных уравнений требует в Matlab дополнительных настроек, в то время как в Mathematica и Maple всё корректно считается с первого раза с настройками по умолчанию. Очевидно, что при численном моделировании желательно иметь достаточно точное представление о том, что именно делает та или иная функция, и с этой точки зрения мне, пожалуй, больше подойдёт фортрановская программа, полную реализацию которой я вижу перед глазами. Да и время работы ее в три раза меньше, чем время работы матлабовского скрипта.

Ради очистки совести я запустил оба приведенных здесь матлабовских скрипта на Octave 4.0.0 (в скрипт для решения задачи двух тел в начало функции orbit() необходимо добавить строку: pkg load odepkg).

Результаты получились следующие:
Умножение матриц 5000x5000: 7.0 с.
Задача двух тел (1000 периодов): 327.3 с

Последний результат, к сожалению, делает Octave совершенно неприемлемым для моих задач. Если кто-нибудь знает, как ускорить odepkg в octave, буду благодарен за совет.

С другой стороны, при запуске первого варианта скрипта для задачи двух тел Octave сразу же выдал мне предупреждение:

warning: Option "RelTol" not set, new value 0.000001 is used
warning: called from
  ode45 at line 113 column 5
  orbit at line 10 column 5
warning: Option "AbsTol" not set, new value 0.000001 is used
warning: Option "InitialStep" not set, new value 40100.000000 is used
warning: Option "MaxStep" not set, new value 40100.000000 is used

С подставленными им значениями по умолчанию скрипт посчитался за 160 секунд и выдал, хотя и не вполне корректный, но, по крайней мере, похожий на правду результат:



Теперь я сведу все результаты в одну табличку:

              Умножение матриц    Задача двух тел
GFortran           82,3 с              7,6 с
Maple              N/A               ~17,0 c
Mathematica        21,9 с             16,9 с
Matlab              4,3 с             22,6 с
Octave              7,0 с            327,3 с

Я, конечно, ещё поиграюсь с матлабом до истечения триального периода, но пока склоняюсь к тому, чтобы вообще отказаться от проприетарных систем компьютерной алгебры; для решения численных задач небесной механики продолжить использовать фортран, для работы с матрицами — Octave, а для символьной математики — Maxima.

(no subject)

Date: 2015-07-07 05:15 pm (UTC)
From: [identity profile] evgeniirudnyi.livejournal.com
Мне в свое время (9 лет назад, когда она у меня была) нравилась Математика. В ней было хорошее совмещение символьных и численных вычислений. Кстати, в Математике должны быть функции с использованием LAPACK. По всей видимости это происходит не автоматически, по всей видимости надо преобразовать матрицу в какое-нибудь другое представление.

Из бесплатных программ могу рекомендовать numpy и scipy в Python. У меня кстати есть тест с перемножением матриц Matrix Multiplication на matrixprogramming com.

(no subject)

Date: 2015-07-07 06:01 pm (UTC)
From: [identity profile] al-pas.livejournal.com
Мне тоже нравится Mathematica, и я её достаточно давно использую. Но встал вопрос о продлении лицензии за собственный счёт, и я задался вопросом: если я буду отдавать свои кровные деньги, то продолжить ли использовать Mathematica или имеет смысл отдать деньги за что-нибудь другое. Нужна символьная математика, числодробилка и презентационные возможности в одном флаконе. Вот как-то пока альтернативы тому, что я использую сейчас, то есть, связке Mathematica + Fortran не видится, но Mathematica + Fortran это два флакона.

(no subject)

Date: 2015-07-07 08:05 pm (UTC)
From: [identity profile] evgeniirudnyi.livejournal.com
В Mathematica можно вызывать внешние подпрограммы через Mathlink. У меня есть пример для вызова quadpack. Также webMathematica была неплоха. Далее можно писать книги с красивыми формулами.

Однако вопрос денег как всегда не простой. Сложно сказать.

(no subject)

Date: 2015-07-20 09:58 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
Интересно.

По первому тесту, с матрицами, ВЕРОЯТНО, вмешивается нежелание Фортрана использовать SSE, и для всех вариантов, кроме matlaba, работа в один поток.

Для проверки, ниже текст первого теста, переписанный на C++ с использованием Eigen3, хорошо работающей с матрицами и векторизацией.

Используемые компиляторы:
C++ gcc version 5.1.1 20150618 (ALT Linux 5.1.1-alt2))
gfortran (gcc version 4.9.2 20150212 (ALT Linux 4.9.2-alt4)):

опции комиляции: -Ofast -mtune=native

на выполнение запускались командой time ./a.out

Результат (однопоточный вариант):
C++ : 18 секунд (стоит принять, 17.5 сек, учитывая время "печати")
F90 : 34.5 секунды

Надо будет остальные тесты тоже прогнать

#include
[Error: Irreparable invalid markup ('<eigen3/eigen/dense>') in entry. Owner must fix manually. Raw contents below.]

Интересно.

По первому тесту, с матрицами, ВЕРОЯТНО, вмешивается нежелание Фортрана использовать SSE, и для всех вариантов, кроме matlaba, работа в один поток.

Для проверки, ниже текст первого теста, переписанный на C++ с использованием Eigen3, хорошо работающей с матрицами и векторизацией.

Используемые компиляторы:
C++ gcc version 5.1.1 20150618 (ALT Linux 5.1.1-alt2))
gfortran (gcc version 4.9.2 20150212 (ALT Linux 4.9.2-alt4)):

опции комиляции: -Ofast -mtune=native

на выполнение запускались командой time ./a.out

Результат (однопоточный вариант):
C++ : 18 секунд (стоит принять, 17.5 сек, учитывая время "печати")
F90 : 34.5 секунды

Надо будет остальные тесты тоже прогнать

#include <eigen3/Eigen/Dense> // интерфейс к удобным векторам -- матрицам
#include <cmath> // интерфейс к общим мат функциям, определённым Стандартом языка

#include <iostream>

using namespace Eigen;

int main(int argc, char **argv) {

// как и в оригинале, память выделяею динамически
MatrixXd a = MatrixXd::Random(5000,5000); // выделена матрица (5000,5000) и заполнена случайными числами
MatrixXd at, am; // как в оригинале, аналогично --- матрицы типа double динамически изменяемого размера

at = a.transpose(); // [a] транспонируется, результат преобразуется и сохраняется в матрице at

am = a * at; // произведение исходной и транспонированной матриц

// чтобы компилятор не выбросил код при оптимизации, напечатаем получившийся элемент (3,3)
// на еденичную "печать" может уйти до 300 мсек, просто помним об этом
std::cout << "am(3,3) = " << am(3,3) << std::endl;

return 0;
}


(no subject)

Date: 2015-07-20 10:12 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
интересно, включение многопоточности (предусмотренной в Eigen3) через OpenMP, эффекта не дало (процессор i5 о 4 ядрах):
потоков время
1 18
2 20
3 22
4 22

включение многопоточности сводилось к добавлению опции компилятора -fopenmp
и дописыванию к началу программы вызовов:

// включаем openMP:
Eigen::initParallel();
setNbThreads(2);

(no subject)

Date: 2015-07-20 10:17 am (UTC)
From: [identity profile] al-pas.livejournal.com
Matlab, скорее всего, использует и более быстрый алгоритм. Время фортрановской процедуры растет как O(n^3), а время матлабовской как O(n^2.9). Следующая процедура на фортране у меня работает с такой же скоростью, что и библиотечная Matmul:

SUBROUTINE MMUL(A,B,C,N)
  INTEGER N
  REAL*8 A(N,N), B(N,N), C(N,N), TEMP
  DO J=1,N          ! Порядок циклов изменен с учетом того,
    DO K=1,N        ! что фортран хранит матрицы по столбцам,
      TEMP = B(K,J) ! так чтобы уменьшить число промахов кэша.
      DO I=1,N      ! С этой же целью введена переменная TEMP.
        C(I,J) = C(I,J)+A(I,K)*TEMP
      ENDDO
    ENDDO
  ENDDO
END SUBROUTINE


Да, в последних g++ и gfortran появился замечательный ключик оптимизации -O5
Edited Date: 2015-07-20 10:18 am (UTC)

(no subject)

Date: 2015-07-20 10:27 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
вряд-ли можно сильно улучшить алгоритм перемножения матриц. Скорее, тут уже в игру вступает векторизация, оптимальное выравнивание и прочая чёрная магия.

а ключики с высокой стеепнью оптимизации.. ой, опасное это дело! :)

(no subject)

Date: 2015-07-20 10:30 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
кстати, eigen3 эту чёрную магию весьма успешно пытается задействовать. Так что очень подозрительный разрыв между Матлабом и C++ (бог с Фортраном, не доверяю я ему :) Нет ли действительно эффекта ленивых вычислений?

(no subject)

Date: 2015-07-20 10:41 am (UTC)
From: [identity profile] al-pas.livejournal.com
Да, с фортраном есть определенные непонятности. Если учесть, что gfortran транслирует фортрановский текст в c, а потом компилирует gcc, то не очень понятно, почему такой код на c++ работает при тех же ключах оптимизации в полтора раза медленнее, чем вышеприведенный фортрановский (здесь тоже изменен порядок циклов, но уже с учетом хранения матрицы по строкам):
void mmul (double **a, double **b, double **c, int n)
{
  register double temp;
  register int i, j, k;
  for (i=0;i < n;i++)
    for (j=0;j < n;j++)
      c[i][j] = 0;
  for(i=0;i < n;i++)
    for(k=0;k < n;k++)
    {
      temp = a[i][k];      
      for(j=0;j < n;j++)
        c[i][j] += temp*b[k][j];
    }
}

Инициализация матрицы c нулями имеет квадратичную сложность и на размерах больше 1000 ее вкладом во время вычислений, которое растет как куб n, можно пренебречь.
Edited Date: 2015-07-20 10:43 am (UTC)

(no subject)

Date: 2015-07-20 11:10 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
gfortran НЕ компилирует в C. Даже более древний g77 этого уже не делал.

Код выше --- это не C++, это классический Си :) Вероятно, лишнее время сжирается на обнулении матрицы (зачем? и почему не библиотечный вызов memset, который всяко будет быстрее возни с индексами?)

Ну и словечком register я бы не злоупотреблял, на x86 регистров мало и, хотя словечко и рекомендательное, пусть компилятор сам решает.

Я бы такие вещи руками не писал, есть библиотеки, типа того-же Eigen3, про выравниванием и векторизацию, оно знает, хранить матрицы по столбцам, умеет.

У меня сомнения в Фортране связаны с тем, что последние лет 10, если не больше, разработка Фортран -- компиляторов, шла по остаточному принципу (по крайней мере, в команде gcc. Посравнивать интеловский фортран да, может быть интересно)

(no subject)

Date: 2015-07-20 11:14 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
И да, c++ код (с использованием Eigen3), отработал почти в двое БЫСТРЕЕ, чем Фортрановский код. Отличаться они должны, насколько я понимаю, только использованием выравнивания и векторизации.

(no subject)

Date: 2015-07-20 11:42 am (UTC)
From: [identity profile] al-pas.livejournal.com
Код выше — это замаскированный под C код на C++, потому что матрица в основной программе создается через new.
Обнуление матрицы при тех n, для которых я тестирую, время не занимает, потому что миллиард в тысячу раз больше миллиона.
А библиотечные вызовы я не использую, чтобы иметь перед глазами по возможности всю реализацию.
Edited Date: 2015-07-20 11:46 am (UTC)

(no subject)

Date: 2015-07-22 04:36 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
>это замаскированный под C код на C++

зачем?

> потому что миллиард в тысячу раз больше миллиона.

:)

>А библиотечные вызовы я не использую, чтобы иметь перед глазами по возможности всю реализацию.

позиция кажется немного странной, соревноваться в написании общего кода с командой профессионалов (или, как минимум, тратящей тысячи человеко -- часов и постоянно тестирующей код командой), не кажется продуктивным.

(no subject)

Date: 2015-07-20 12:12 pm (UTC)
From: [identity profile] al-pas.livejournal.com
Отправился читать документацию gcc — ну действительно, вы правы, вызывается собственный компилятор f951. Странно, что у меня в голове засело, что гну-фортран транслирует код в промежуточный Си...

(no subject)

Date: 2015-07-22 04:38 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
>засело, что гну-фортран транслирует код в промежуточный Си...

до стабилизации g77 (период вблизи 1993-го), существовал транслятор for2c. Вероятно, это отголоски с тех времён.

(no subject)

Date: 2015-07-22 12:05 pm (UTC)
From: [identity profile] al-pas.livejournal.com
Я провёл небольшое исследование. Асимптотическая сложность простого алгоритма перемножения матриц составляет O(N3). Но существуют алгоритмы с меньшей асимптотической сложностью, на сегодняшний день рекорд порядка O(N2,4). Я вычислил показатель степени у Nn для разных пакетов (n = log(tk/t1)/log(Nk/N1), где tk — время вычисления матрицы размером NkхNk).
Вот что получилось:
Matlab 2015a:
N = 1000, time =   0.05 s.
N = 2000, time =   0.35 s. n =   2.54
N = 3000, time =   1.00 s. n =   2.53
N = 4000, time =   2.26 s. n =   2.59
N = 5000, time =   4.37 s. n =   2.64

Octave 3.8.2:
N = 1000, time =   0.14 s.
N = 2000, time =   0.95 s. n =   2.75
N = 3000, time =   2.97 s. n =   2.78
N = 4000, time =   6.50 s. n =   2.77
N = 5000, time =  11.93 s. n =   2.76

Scilab 5.5.1:
N = 1000, time =   0.12 s.
N = 2000, time =   0.85 s. n =   2.87
N = 3000, time =   2.72 s. n =   2.87
N = 4000, time =   6.33 s. n =   2.89
N = 5000, time =  12.08 s. n =   2.89

GFortran (Библиотечная процедура Fortran 90):
N = 1000, time =   0.72 s.
N = 2000, time =   5.54 s. n =   2.94
N = 3000, time =  17.87 s. n =   2.92
N = 4000, time =  42.62 s. n =   2.94
N = 5000, time =  82.35 s. n =   2.94

GFortran (Алгоритм Штрассена, неоптимизированный)
N = 1024, time =   0.95 s.
N = 2048, time =   6.27 s. n =   2.72
N = 4096, time =  47.83 s. n =   2.82


Можно предположить, что Octave использует алгоритм Штрассена, оптимизируя дополнительно вычисления. А вот Matlab, судя по показателю, использует более хитрый алгоритм.
Edited Date: 2015-07-22 12:46 pm (UTC)

(no subject)

Date: 2015-07-23 06:50 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
Мне кажется, надо сначала определиться, с какой-же всё-таки точностью работает Матлаб по-умолчанию. Посмотрите мой предыдущий комментарий, я поразвлекался с точностью типа. производительность при переходе float -- double -- long double падает в высшей степени быстро!

Пока нет уверенности в использованной точности, пытаться угадать алгоритм вряд-ли возможно

(no subject)

Date: 2015-07-23 09:04 am (UTC)
From: [identity profile] al-pas.livejournal.com
Команда whos в Matlab утверждает, что:
>> whos
  Name         Size                 Bytes  Class 
  A         3000x3000            72000000  double
  AM        3000x3000            72000000  double

Для очистки совести прогнал умножение матриц 3000х3000 на разных компиляторах. Чтобы сравнить результаты, я инициализировал матрицы во всех программах одинаково: A(I,J)=I*J (нумерация индексов везде начинается с единицы) и вывел элемент A(1234,2345):
gfortran, REAL*4     AM(1234,2345)=2.60565680E+16,          время 19.27 с.
gfortran, REAL*8     AM(1234,2345)=2.6056593231864656E+16,  время 22.31 с.
gfortran, REAL*10    AM(1234,2345)=2.6056593231865000E+16,  время 79.84 с.
gcc/g++, float       AM(1234,2345)=2.6056568004935680e+016, время 20.58 с.
gcc/g++, double      AM(1234,2345)=2.6056593231864656e+016, время 23.84 с.
gcc/g++, long double AM(1234,2345)=2.6056593231865000e+016, время 81.17 с.
MSVC 2013, float     AM(1234,2345)=2.6056568004935680e+016, время  9.95 с.
MSVC 2013, double    AM(1234,2345)=2.6056593231864656e+016, время 20.17 с.
MSVC 2013, long double - результат, идентичный double.    
Matlab 2015a         AM(1234,2345)=2.605659323186500e+16,   время  0.88 с.
--------------------------------------------------------------------------
Точное значение:     AM(1234,2345)= 26056593231865000

Все исходники те же, что уже приводились ранее в этом посте. Время расчета заметно уменьшается при переходе от double к float только в микрософтовском компиляторе. При переходе к 80-разрядным числам время у гну-компиляторов увеличивается в 4 раза.

Получается, что Matlab не только считает в double, но ещё и выдаёт точный результат, который в других компиляторах получается только при использовании типа long double или REAL*10.

P.S. Wolfram Mathematica 9 считает 17 секунд и выдаёт точный целочисленный результат: 26056593231865000.

P.P.S. Но если "Математике" задать исходную матрицу как A = Table[i j*1.0, {i, n}, {j, n}], то время расчёта сокращается до 2,25 секунды, результат же по-прежнему остаётся верным с нужной точностью: 2.605659323186500∙1016.
Оно и понятно, использование стандартного типа быстрее, чем работа с целыми числами, не умещающимися в стандартные регистры.
Edited Date: 2015-07-23 09:24 am (UTC)

(no subject)

Date: 2015-07-23 11:30 am (UTC)
From: [identity profile] al-pas.livejournal.com
В дополнение аналогичный тест для Mathematica 9:
N = 1000, time =  0.09 s.
N = 2000, time =  0.66 s.  n = 2.83
N = 3000, time =  2.18 s.  n = 2.86
N = 4000, time =  4.46 s.  n = 2.78
N = 5000, time =  8.73 s.  n = 2.81
Edited Date: 2015-07-23 11:32 am (UTC)

(no subject)

Date: 2015-07-20 10:24 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
И кстати...

Scilab выполнил 1-ый Матлабовский скрипт за 1.5 секунды. Но. как только я поставил печать того-же элемента AM(3,3), время выполнения увеличилось до 7 секунд.

Нет ли такого-же эффекта в Матлабе (ленивые вычисления)? Под рукой нет матлаба, проверить не могу

(no subject)

Date: 2015-07-20 10:28 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
ЗЫ: SciLab 5.5.1

(no subject)

Date: 2015-07-20 12:40 pm (UTC)
From: [identity profile] al-pas.livejournal.com
Про scilab я даже не думал в качестве замены Matlab. Дома попробую, спасибо.
В Matlab вывод значения одного из элементов время расчета не увеличивает.
N = 5000;
A = rand (N, N);
tic
AM=A'*A;
AM(3,3)
toc

Что с AM(3,3), что без — 4 секунды.
(Intel Core i5-3230M @ 2.60 ГГц, RAM 6 Гбайт,
Matlab 2015a).
Edited Date: 2015-07-20 02:30 pm (UTC)

(no subject)

Date: 2015-07-22 04:46 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
странно это.

Хотя вот что, заменяю в вышеприведённом исходнике MatrixXd на MatrixXf (снижаю точность с double до float), и время вычисления в однопоточном варианте становится 4.9 секунды.

С какой точностью считает Matlab (и какая точность приемлима)? Я считал, что точность float, это табу и фуфуфу, но я нифига не расчётчик :)

Кстати, и картинка в вышеприведённом тесте намекает, похоже :)

(no subject)

Date: 2015-07-22 11:54 am (UTC)
From: [identity profile] al-pas.livejournal.com
Сдается мне, что Matlab оперирует 80-разрядными числами, когда выполняет вычисления с плавающей точкой, поскольку это «родной» формат для математического сопроцессора (ну, по крайней мере, лет десять тому назад так было). В Delphi им соответствует тип Extended. Использование 32-разрядного float вместо double в программах на c/c++, по моему опыту, может приводить даже к уменьшению скорости, видимо из-за дополнительных команд преобразования 32-битных float в 80-битные extended и обратно.

(no subject)

Date: 2015-07-23 06:37 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
Эх, гадания, гадания... в текст бы посмотреть!

Вот прямой эксперимент: C++Eigen3, компиляция gcc(C++) version 5.1.1 20150618 (ALT Linux 5.1.1-alt2), прогон на системе с процессором Intel Core i5-4590.

точность float даёт время выполнения теста в [b]точности такое-же, как у Матлаба[/b].

Более того, смотрим на графики Вашего теста, которые попросту показывают катастрофическое снижение точности расчёта в Матлаб. По-моему, Оккам требует предположить, что Matlab не столько использует хитрый алгоритм, сколько (по умолчанию?) работает с низкой точностью.

Совпадение битности регистра сопроцессора и типа данных, это конечно, хорошо, но всё ли однозначно? Есть же требование стандарта, как его, IEE75-чего-то-там, нужно выполнять преоборазования. Пробую!

Точность double (это 64 бита) приводит к увеличению времени (в однопоточном варианте) в 8 раз (до примерно, 32 секунд)

Переход в тесте к точности long double (как раз те самые 80 бит /осторожно, Microsoft не поддерживает этот тип, эмулируя его простым double/ ), оказался катастрофичен.

При компиляции с опциями -Ofast -mtune=native время выполнения теста... я не дождался! Прошло больше 30 (!!!!) минут и конца процессу не видать.

Пробую уточнить тип процессора
$grep -m1 -A3 "vendor_id" /proc/cpuinfo

vendor_id : GenuineIntel
cpu family : 6
model : 60
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz

на странице https://wiki.gentoo.org/wiki/Safe_CFLAGS#Core_i3.2Fi5.2Fi7_.26_Xeon_E3.2FE5.2FE7_.2AV2
ищу опции компиляции для данного семейства:

-march=core-avx2


При компиляции с опциями -Ofast -march=core-avx2 -ftree-vectorize -ffast-math Всё стало значительно лучше!
время выполнения теста (long double) составило 130 секунд.

Интересно... пересобираю тест для float и double с теми-же опциями компиляции, результат:

float --> секунд = 5
double --> секунд = 16
long double --> секунд = 130

Видимо, дело более сложное, чем просто соответствие битности числа и регистра сопроцессора. Ну, или компилятор откровенно лажает.

А как вариант с OpenMP?
Пересобираю, добавив опцию -fopenmp и раскомментировав инициализацию режима в Eigen3, указание задействовать 3 нити (мониторинг показывает, что действительно, загружены все 3 ядра). Результат:

float --> секунд = 7
double --> секунд = 19
long double --> секунд = 145

Н-да...

Смотрим Фортрановский вариант.
Компиляция с опциями -Ofast -mtune=native
время выполнения = 35 секунд.

-Ofast -march=core-avx2 -ftree-vectorize -ffast-math
время выполнения ТАКОЕ ЖЕ = 35 секунд! Забавно...

Ставлю в тексте фортрановского теста вместо real --> real*10,
компилирую с опциями -Ofast -march=core-avx2 -ftree-vectorize -ffast-math
время выполнения = 295 секунд.

Выводы:
1. время выполнения тем меньше, чем [b]меньше точность[/b]. с разрядностью регистров сопроцессора это дело не коррелирует :)

2. время выполнения теста существенно зависит от опций компилятора gcc (указания архитектуры и допустимых оптимизаций)

3. оптимизации размещения матриц, автоматически выполняемые шаблонным кодом библиотеки Eigen3, приводят к [b] ДВУХКРАТНОМУ выигрышу C++Eigen3 по сравнению с Фортраном (gfortran) [/b]. С другой стороны, C++Eigen3 оказывается существенно более чувствителен к опциямкомпиляции, чем Фортран: на производительность кода, порождаемого gfortran, опции компиляции почти не влияют.

(no subject)

Date: 2015-07-20 09:22 pm (UTC)
From: [identity profile] al-pas.livejournal.com
Поставил SciLab — пока на виртуальную машину (VMWare, Ubuntu 10.04, 1.5 Гбайт RAM, 1 процессор). Удивился, что при 1,5 гбайтах отведенной машине памяти скрипт вылетает с ошибкой по нехватке памяти при n > 1400. Добавил ещё полтора гигабайта — скрипт стал вылетать при n > 2100.

(no subject)

Date: 2015-07-22 04:48 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
Киньте в меня скриптом, прогоню на реальной системе. Странно это всё.

(no subject)

Date: 2015-07-22 05:05 am (UTC)
From: [identity profile] al-pas.livejournal.com
Так тот же самый:
N = 5000;
A = rand (N, N);
tic
AM=A'*A;
AM(3,3)
toc

(no subject)

Date: 2015-07-22 06:03 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
странно, у меня выполнил не поперхнувшись, только попросил увеличить стек. И убрать tic//toc, которых он не знает. сколько при этом выжрал памяти, сказать не могу, уж очень быстро всё посчиталось.

Кстати, радикально ускорить выполнение этого теста и получить аналогичный матлабовскому результат, получилось только снижением точности до float. Распараллеливание в этом тесте не даёт ощутимого выигрыша. Не проверяли?

(no subject)

Date: 2015-07-22 07:06 am (UTC)
From: [identity profile] al-pas.livejournal.com
Может, у нас разные скайлэбы? Мой tic/toc съедает совершенно нормально (Scilab 5.2):
Image
Edited Date: 2015-07-22 07:08 am (UTC)

(no subject)

Date: 2015-07-22 07:20 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
у меня 5.5.1 (может быть, нужно подгрузить какой пакет?)

(no subject)

Date: 2015-07-22 08:11 am (UTC)
From: [identity profile] al-pas.livejournal.com
Не могу сказать. У меня опыта работы с Scilab нет. Я просто установил то, что предлагается по умолчанию, из Центра приложений Ubuntu.

(no subject)

Date: 2015-07-22 07:21 am (UTC)
From: [identity profile] gleb-kulikov.livejournal.com
кстати, картинка не показывается: 403. That’s an error.
Your client does not have permission to get URL /sEpD7fwziQWpYSJNPQt-x4k-2UkHy9Er0OrFErI_rET5Q7cs4--YwuYoBNq7pl3dU0j6ZIK-gmcmaNE=w1886-h745 from this server.

:)

(no subject)

Date: 2015-07-22 07:26 am (UTC)
From: [identity profile] al-pas.livejournal.com
Они™ опят изменили политику... Вот картинка: https://drive.google.com/file/d/0B-OXHW6h1zVeRUVrYllFWUJNUUE/view?usp=sharing
Ушёл пытать гуглдрайв на предмет, как по новым правилам корректно картинки оттуда в блоги вставлять.
Page generated Jan. 1st, 2026 05:05 pm
Powered by Dreamwidth Studios