Dưới đây là những lập luận của tôi về lý do tại sao lập trình chức năng có thể , và nên được sử dụng cho khoa học tính toán. Những lợi ích là rất lớn, và những khuyết điểm sẽ nhanh chóng biến mất. Trong tâm trí tôi chỉ có một con:
Con : thiếu hỗ trợ ngôn ngữ trong C / C ++ / Fortran
Ít nhất là trong C ++, con lừa này đang biến mất - vì C ++ 14/17 đã thêm các phương tiện mạnh mẽ để hỗ trợ lập trình chức năng. Bạn có thể cần phải tự viết một số thư viện / mã hỗ trợ, nhưng ngôn ngữ sẽ là bạn của bạn. Ví dụ, đây là một thư viện (cảnh báo: plug) thực hiện các mảng đa chiều bất biến trong C ++: https://github.com/jzrake/ndarray-v2 .
Ngoài ra, đây là một liên kết đến một cuốn sách hay về lập trình chức năng trong C ++, mặc dù nó không tập trung vào các ứng dụng khoa học.
Dưới đây là tóm tắt của tôi về những gì tôi tin là chuyên nghiệp:
Ưu điểm :
Xét về tính đúng đắn , các chương trình chức năng là ràng nổi đặt ra : họ buộc bạn phải xác định đúng tình trạng tối thiểu của các biến vật lý của bạn, và chức năng mà những tiến bộ mà nhà nước về phía trước trong thời gian:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Giải phương trình vi phân từng phần (hoặc ODE) là hoàn hảo cho lập trình hàm; bạn chỉ đang áp dụng một hàm thuần túy ( advance
) cho giải pháp hiện tại để tạo ra hàm tiếp theo.
Theo kinh nghiệm của tôi, phần mềm mô phỏng vật lý thường là do gánh nặng quản lý nhà nước kém . Thông thường, mỗi giai đoạn của thuật toán hoạt động trên một số trạng thái được chia sẻ (có hiệu quả toàn cầu). Điều này gây khó khăn, hoặc thậm chí là không thể, để đảm bảo thứ tự hoạt động chính xác, khiến phần mềm dễ bị lỗi có thể biểu hiện là lỗi phân tách hoặc tệ hơn là các thuật ngữ lỗi không làm hỏng mã của bạn mà âm thầm làm tổn hại đến tính toàn vẹn của khoa học. đầu ra. Cố gắng quản lý trạng thái chia sẻ trong một mô phỏng vật lý cũng ức chế đa luồng - đó là một vấn đề cho tương lai, vì các siêu máy tính đang tiến tới số lượng lõi cao hơn và nhân rộng với MPI thường đạt tới ~ 100 nghìn nhiệm vụ. Ngược lại, lập trình chức năng làm cho song song bộ nhớ chia sẻ trở nên tầm thường, vì tính bất biến.
Hiệu năng cũng được cải thiện trong lập trình chức năng do đánh giá lười biếng các thuật toán (trong C ++, điều này có nghĩa là tạo ra nhiều loại tại thời gian biên dịch - thường là một loại cho mỗi ứng dụng của hàm). Nhưng nó làm giảm chi phí truy cập và phân bổ bộ nhớ, cũng như loại bỏ công văn ảo - cho phép trình biên dịch tối ưu hóa toàn bộ thuật toán bằng cách nhìn thấy tất cả các đối tượng hàm bao gồm nó. Trong thực tế, bạn sẽ thử nghiệm các cách sắp xếp khác nhau của các điểm đánh giá (trong đó kết quả thuật toán được lưu vào bộ đệm) để tối ưu hóa việc sử dụng CPU so với phân bổ bộ nhớ. Điều này khá dễ dàng do tính địa phương cao (xem ví dụ bên dưới) của các giai đoạn thuật toán so với những gì bạn thường thấy trong một mô-đun hoặc mã dựa trên lớp.
Các chương trình chức năng dễ hiểu hơn khi chúng tầm thường hóa trạng thái vật lý. Điều đó không có nghĩa là cú pháp của họ dễ hiểu bởi tất cả các đồng nghiệp của bạn! Các tác giả nên cẩn thận sử dụng các hàm có tên tốt và các nhà nghiên cứu nói chung nên làm quen với việc nhìn thấy các thuật toán được thể hiện theo chức năng hơn là theo thủ tục. Tôi sẽ thừa nhận rằng sự vắng mặt của các cấu trúc điều khiển có thể gây khó khăn cho một số người, nhưng tôi không nghĩ rằng điều đó sẽ ngăn chúng ta đi vào tương lai có thể làm khoa học chất lượng tốt hơn trên máy tính.
Dưới đây là một advance
hàm mẫu , được điều chỉnh từ mã khối lượng hữu hạn bằng cách sử dụng ndarray-v2
gói. Lưu ý các to_shared
nhà khai thác - đây là những điểm đánh giá tôi đã ám chỉ trước đó.
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() };
}