Ось мої аргументи, чому функціональне програмування може і потрібно використовувати для обчислювальної науки. Переваг величезна, і мінуси швидко проходять. У моїй думці є лише один мінус:
Con : відсутність мовної підтримки в C / C ++ / Fortran
Принаймні, на C ++, ця проблема зникає - оскільки C ++ 14/17 додав потужні засоби для підтримки функціонального програмування. Можливо, вам доведеться самостійно написати якийсь код бібліотеки / підтримки, але мова буде вашим другом. Як приклад, ось бібліотека (застереження: plug), яка робить незмінні багатовимірні масиви в C ++: https://github.com/jzrake/ndarray-v2 .
Також, ось посилання на хорошу книгу про функціональне програмування на C ++, хоча вона не зосереджена на наукових програмах.
Ось мій підсумок того, що я вважаю професіоналами:
Плюси :
- Правильність
- Зрозумілість
- Продуктивність
З точки зору правильності , функціональні програми явно добре поставлені : вони змушують вас правильно визначити мінімальний стан змінних фізики та функцію, яка просуває цей стан вперед:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Розв’язання часткового диференціального рівняння (або ODE) ідеально підходить для функціонального програмування; ви просто застосовуєте чисту функцію ( advance
) до поточного рішення для створення наступного.
На мій досвід, програмне забезпечення для фізичного моделювання значною мірою обтяжене поганим управлінням державою . Зазвичай кожен етап алгоритму працює на деякому фрагменті спільного (фактично глобального) стану. Це ускладнює або навіть неможливо забезпечити правильний порядок операцій, залишаючи програмне забезпечення вразливим до помилок, які можуть виявлятися як seg-помилки, або ще гірше, помилки, які не руйнують ваш код, але мовчки ставлять під загрозу цілісність його науки вихід. Спроба керувати загальним станом у фізичному моделюванні також гальмує багатопотоковість - що є проблемою для майбутнього, оскільки суперкомп'ютери рухаються до більшої кількості ядер, а масштабування з MPI часто перевищує завдання на 100 к. На відміну від цього, функціональне програмування робить паралелізм спільної пам'яті тривіальним через незмінність.
Продуктивність також покращується у функціональному програмуванні завдяки лінивій оцінці алгоритмів (у С ++ це означає генерування багатьох типів за час компіляції - часто по одному для кожного застосування функції). Але це зменшує накладні витрати на доступ до пам'яті та їх розподіл, а також виключає віртуальну диспетчеризацію - дозволяє компілятору оптимізувати цілий алгоритм, побачивши відразу всі об'єкти функції, що його містять. На практиці ви експериментуєте з різними розташуванням балів оцінювання (де результат алгоритму кешується в буфері пам'яті) для оптимізації використання процесора та розподілу пам'яті. Це досить просто через високу локальність (див. Приклад нижче) етапів алгоритму порівняно з тими, які ти зазвичай бачиш у модулі або на основі класу коду.
Функціональні програми легше зрозуміти настільки, наскільки вони дорікають стан фізики. Це не означає, що їх синтаксис легко зрозумілий усім вашим колегам! Автори повинні обережно використовувати добре названі функції, а дослідники взагалі повинні звикати бачити алгоритми, виражені функціонально, а не процедурно. Я визнаю, що відсутність структур управління може перешкоджати деяким, але я не думаю, що це може перешкоджати нам іти в майбутнє, здатні робити якісніші науки на комп'ютерах.
Нижче наведена прикладна advance
функція, адаптована з кінцево-об'ємного коду за допомогою ndarray-v2
пакета. Зверніть увагу на to_shared
операторів - це точки оцінки, на які я натякав раніше.
auto advance(const solution_state_t& state)
{
auto dt = determine_time_step_size(state);
auto du = state.u
| divide(state.vertices | volume_from_vertices)
| nd::map(recover_primitive)
| extrapolate_boundary_on_axis(0)
| nd::to_shared()
| compute_intercell_flux(0)
| nd::to_shared()
| nd::difference_on_axis(0)
| nd::multiply(-dt * mara::make_area(1.0));
return solution_state_t {
state.time + dt,
state.iteration + 1,
state.vertices,
state.u + du | nd::to_shared() };
}