Tôi nghĩ rằng các đảm bảo hiện tại của Java memory model làm cho nó khá khó khăn để làm nhiều, nếu có, tự động song song ở trình biên dịch hoặc cấp VM. Ngôn ngữ Java không có ngữ nghĩa để đảm bảo rằng bất kỳ cấu trúc dữ liệu nào thậm chí có hiệu quả bất biến, hoặc bất kỳ câu lệnh cụ thể nào là thuần túy và không có các tác dụng phụ, do đó trình biên dịch sẽ phải tự động hóa chúng để song song. Một số cơ hội cơ bản sẽ có thể suy ra trong trình biên dịch, nhưng trường hợp chung sẽ được để lại cho thời gian chạy, vì tải động và ràng buộc có thể đưa ra các đột biến mới không tồn tại trong thời gian biên dịch.
Xét đoạn mã sau:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array[i]);
}
Nó sẽ là tầm thường để parallelize, nếu expensiveComputation
là một pure function, mà đầu ra chỉ phụ thuộc vào đối số của nó, và nếu chúng ta có thể đảm bảo rằng array
sẽ không bị thay đổi trong quá vòng lặp (thực ra chúng ta đang thay đổi nó, thiết lập array[i]=...
, nhưng trong trường hợp cụ thể này expensiveComputation(array[i])
luôn được gọi đầu tiên vì vậy nó không sao ở đây - giả sử rằng array
là cục bộ và không được tham chiếu từ bất kỳ nơi nào khác).
Hơn nữa, nếu chúng ta thay đổi vòng lặp như thế này:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array, i);
// expensiveComputation has the whole array at its disposal!
// It could read or write values anywhere in it!
}
sau đó song song là không nhỏ nữa ngay cả khi expensiveComputation
là tinh khiết và không làm thay đổi đối số của nó, bởi vì các chủ đề song song sẽ được thay đổi nội dung của array
trong khi những người khác đang đọc nội dung đó! Bộ song song sẽ phải tìm ra các bộ phận của mảng expensiveComputation
đang đề cập đến trong các điều kiện khác nhau và đồng bộ hóa cho phù hợp.
Có lẽ nó sẽ không phải là hoàn toàn không thể để phát hiện tất cả các đột biến và tác dụng phụ mà có thể xảy ra và có những vào tài khoản khi parallelizing, nhưng nó sẽ là rất cứng, chắc chắn, có lẽ không khả thi trong thực hành. Đây là lý do tại sao song song, và tìm ra rằng tất cả mọi thứ vẫn hoạt động chính xác, là đau đầu của lập trình viên trong Java.
Ngôn ngữ chức năng (ví dụ: Clojure trên JVM) là câu trả lời nóng cho chủ đề này. Các hàm thuần túy, không có tác dụng phụ cùng với cấu trúc dữ liệu persistent ("có thể thay đổi một cách hiệu quả") có khả năng cho phép song song ngầm hoặc ngầm gần như ẩn. Hãy tăng gấp đôi mỗi phần tử của một mảng:
(map #(* 2 %) [1 2 3 4 5])
(pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel.
Đây là minh bạch vì 2 điều:
- Chức năng
#(* 2 %)
là tinh khiết: phải mất một giá trị trong và đưa ra một giá trị ra, và đó là nó. Nó không thay đổi bất cứ điều gì, và đầu ra của nó chỉ phụ thuộc vào đối số của nó.
- Vectơ
[1 2 3 4 5]
là không thay đổi: bất kể ai đang xem hoặc khi nào thì cũng giống nhau.
Có thể tạo các hàm thuần túy trong Java, nhưng 2), bất biến, là gót chân Achilles ở đây. Không có mảng bất biến trong Java. Để là hình tròn, không có gì là bất biến trong Java vì thậm chí có thể thay đổi các trường final
bằng cách sử dụng phản chiếu. Do đó không có đảm bảo nào có thể được thực hiện rằng đầu ra (hoặc đầu vào!) Của một phép tính sẽ không bị thay đổi bởi sự song song -> vì vậy việc song song tự động thường không khả thi.
Người câm "tăng gấp đôi các yếu tố" dụ kéo dài đến tùy tiện chế biến phức tạp, nhờ vào tính bất biến:
(defn expensivefunction [v x]
(/ (reduce * v) x))
(let [v [1 2 3 4 5]]
(map (partial expensivefunction v) v)) ; pmap would work equally well here!
Thông thường, một ExecutorService hoặc ngã ba/join là một lựa chọn tốt hơn. Do thời gian cần để phân phối nhiệm vụ giữa các chủ đề là cao, rất khó có khả năng một trình biên dịch tự động sẽ tốt hơn mã viết tay. Thông thường đối với các vòng lặp đơn giản, sử dụng nhiều luồng sẽ chậm hơn. –
Rất nhiều trình biên dịch khác làm việc này vào những ngày này, tôi không thấy lý do tại sao một JVM/JIT không nên. –
Từ kinh nghiệm thực tế của tôi, JVM thực sự là khá tốt ở đây. – dagnelies