Là lập trình chức năng nhanh hơn trong đa luồng vì tôi viết những thứ khác nhau hoặc bởi vì mọi thứ được biên dịch khác nhau?


63

Tôi đang đi sâu vào thế giới lập trình chức năng và tôi tiếp tục đọc ở mọi nơi rằng các ngôn ngữ chức năng tốt hơn cho các chương trình đa luồng / đa lõi. Tôi hiểu cách ngôn ngữ chức năng làm rất nhiều thứ khác nhau, chẳng hạn như đệ quy , số ngẫu nhiên vv nhưng tôi dường như không thể tìm ra nếu đa luồng là nhanh hơn trong một ngôn ngữ chức năng bởi vì nó được biên soạn khác nhau hoặc vì tôi viết nó khác nhau.

Ví dụ, tôi đã viết một chương trình bằng Java thực hiện một giao thức nhất định. Trong giao thức này, hai bên gửi và nhận cho nhau hàng ngàn tin nhắn, họ mã hóa các tin nhắn đó và gửi lại chúng (và nhận chúng) nhiều lần. Như mong đợi, đa luồng là chìa khóa khi bạn giao dịch ở quy mô hàng ngàn. Trong chương trình này không có khóa liên quan .

Nếu tôi viết cùng một chương trình trong Scala (sử dụng JVM), việc triển khai này có nhanh hơn không? Nếu đúng thì tại sao? Có phải vì phong cách viết? Nếu đó do phong cách viết, thì bây giờ Java bao gồm các biểu thức lambda, tôi không thể đạt được kết quả tương tự khi sử dụng Java với lambda? Hay là nhanh hơn vì Scala sẽ biên dịch mọi thứ khác nhau?


64
Lập trình chức năng Afaik không làm cho đa luồng nhanh hơn. Nó làm cho đa luồng dễ thực hiện hơn và an toàn hơn vì có một số tính năng của lập trình chức năng như tính bất biến và các chức năng không có tác dụng phụ giúp ích trong vấn đề này.
Pieter B

7
Lưu ý rằng 1) tốt hơn không thực sự được xác định 2) nó chắc chắn không được định nghĩa là "nhanh hơn". Một ngôn ngữ X yêu cầu một tỷ lần kích thước của mã để đạt hiệu suất 0,1% đối với Y không tốt hơn Y đối với bất kỳ định nghĩa hợp lý nào tốt hơn.
Bakuriu

2
Bạn có ý định hỏi về "lập trình chức năng" hay "chương trình được viết theo phong cách chức năng" không? Thường thì lập trình nhanh hơn không mang lại chương trình nhanh hơn.
Ben Voigt

1
Đừng quên luôn có một GC phải chạy trong nền và theo kịp nhu cầu phân bổ của bạn ... và tôi không chắc nó đã đa luồng ...
Mehrdad

4
Câu trả lời đơn giản nhất ở đây là: lập trình chức năng cho phép viết các chương trình xem xét các vấn đề về tình trạng chủng tộc ít hơn , tuy nhiên điều đó không có nghĩa là các chương trình được viết theo phong cách bắt buộc sẽ chậm hơn.
Dawid Pura

Câu trả lời:


97

Lý do mọi người nói các ngôn ngữ chức năng tốt hơn cho xử lý song song là do thực tế là chúng thường tránh trạng thái đột biến. Trạng thái có thể thay đổi là "gốc rễ của mọi tội lỗi" trong bối cảnh xử lý song song; chúng làm cho nó thực sự dễ dàng chạy vào các điều kiện chủng tộc khi chúng được chia sẻ giữa các quy trình đồng thời. Giải pháp cho các điều kiện cuộc đua sau đó liên quan đến các cơ chế khóa và đồng bộ hóa, như bạn đã đề cập, gây ra chi phí thời gian chạy, vì các quá trình chờ nhau sử dụng tài nguyên được chia sẻ và độ phức tạp thiết kế lớn hơn, vì tất cả các khái niệm này có xu hướng lồng sâu trong các ứng dụng như vậy.

Khi bạn tránh trạng thái có thể thay đổi, nhu cầu đồng bộ hóa và cơ chế khóa sẽ biến mất cùng với nó. Bởi vì các ngôn ngữ chức năng thường tránh trạng thái có thể thay đổi, chúng tự nhiên hiệu quả và hiệu quả hơn để xử lý song song - bạn sẽ không có thời gian chạy của các tài nguyên được chia sẻ và bạn sẽ không có sự phức tạp trong thiết kế thường xảy ra.

Tuy nhiên, đây là tất cả sự cố. Nếu giải pháp của bạn trong Java cũng tránh được trạng thái có thể thay đổi (được chia sẻ cụ thể giữa các luồng), thì việc chuyển đổi nó sang ngôn ngữ chức năng như Scala hoặc Clojure sẽ không mang lại bất kỳ lợi ích nào về hiệu quả đồng thời, vì giải pháp ban đầu đã không có chi phí gây ra bởi các cơ chế khóa và đồng bộ.

TL; DR: Nếu một giải pháp trong Scala hiệu quả hơn trong xử lý song song so với giải pháp trong Java, thì đó không phải là do cách mã được biên dịch hoặc chạy qua JVM, mà thay vào đó vì giải pháp Java đang chia sẻ trạng thái có thể thay đổi giữa các luồng, hoặc gây ra các điều kiện chủng tộc hoặc thêm chi phí đồng bộ hóa để tránh chúng.


2
Nếu chỉ có một luồng sửa đổi một phần dữ liệu; không cần chăm sóc đặc biệt Chỉ khi nhiều luồng có thể sửa đổi cùng một dữ liệu mà bạn cần một số loại chăm sóc đặc biệt (đồng bộ hóa, bộ nhớ giao dịch, khóa, bất cứ điều gì). Một ví dụ về điều này là ngăn xếp của luồng, liên tục bị thay đổi bởi mã chức năng nhưng không được sửa đổi bởi nhiều luồng.
Brendan

31
Có một luồng làm thay đổi dữ liệu trong khi những người khác đọc nó là đủ để bạn phải bắt đầu "chăm sóc đặc biệt".
Peter Green

10
@Brendan: Không, nếu một luồng sửa đổi dữ liệu trong khi các luồng khác đang đọc từ cùng một dữ liệu đó, thì bạn có một điều kiện cuộc đua. Chăm sóc đặc biệt là cần thiết ngay cả khi chỉ có một chủ đề được sửa đổi.
Bắp ngô

3
Trạng thái có thể thay đổi là "gốc rễ của mọi tội lỗi" trong bối cảnh xử lý song song => nếu bạn chưa nhìn vào Rust, tôi khuyên bạn nên nhìn trộm nó. Nó quản lý để cho phép khả năng biến đổi rất hiệu quả bằng cách nhận ra rằng vấn đề thực sự có thể thay đổi được trộn lẫn với răng cưa: nếu bạn chỉ có bí danh hoặc chỉ có khả năng biến đổi thì không có vấn đề gì.
Matthieu M.

2
@MatthieuM. Phải, cảm ơn! Tôi chỉnh sửa để diễn đạt mọi thứ rõ ràng hơn trong câu trả lời của tôi. Trạng thái có thể thay đổi chỉ là "gốc rễ của mọi tội lỗi" khi nó được chia sẻ giữa các quy trình đồng thời - điều mà Rust tránh được với các cơ chế kiểm soát quyền sở hữu của nó.
MichelHenrich 16/2/2016

8

Sắp xếp cả hai. Nó nhanh hơn vì dễ viết mã của bạn theo cách dễ biên dịch nhanh hơn. Bạn sẽ không nhất thiết phải có sự khác biệt về tốc độ bằng cách chuyển đổi ngôn ngữ, nhưng nếu bạn đã bắt đầu với một ngôn ngữ chức năng, có lẽ bạn đã thực hiện đa luồng với nỗ lực lập trình ít hơn rất nhiều . Đồng thời, lập trình viên dễ dàng mắc lỗi phân luồng sẽ tốn tốc độ trong một ngôn ngữ bắt buộc và khó nhận ra những lỗi đó hơn rất nhiều.

Lý do là các lập trình viên bắt buộc thường cố gắng đặt tất cả các mã khóa, không có khóa vào một hộp nhỏ nhất có thể, và thoát nó càng sớm càng tốt, trở lại thế giới đồng bộ, dễ biến đổi của họ. Hầu hết các lỗi làm bạn mất tốc độ đều được thực hiện trên giao diện biên đó. Trong ngôn ngữ lập trình chức năng, bạn không phải lo lắng nhiều về việc mắc lỗi trên ranh giới đó. Hầu hết các mã gọi của bạn cũng là "bên trong hộp", có thể nói như vậy.


7

Lập trình hàm không tạo ra các chương trình nhanh hơn, như một quy luật chung. Những gì nó làm là để lập trình song song và đồng thời dễ dàng hơn . Có hai chìa khóa chính cho việc này:

  1. Việc tránh trạng thái đột biến có xu hướng làm giảm số lượng những điều có thể sai trong một chương trình, và thậm chí còn nhiều hơn trong một chương trình đồng thời.
  2. Việc tránh các nguyên thủy đồng bộ hóa dựa trên bộ nhớ và khóa có lợi cho các khái niệm cấp cao hơn có xu hướng đơn giản hóa đồng bộ hóa giữa các luồng mã.

Một ví dụ tuyệt vời của điểm # 2 là trong Haskell chúng tôi có một sự phân biệt rõ ràng giữa song song xác định so với đồng thời không xác định . Không có lời giải thích nào tốt hơn việc trích dẫn cuốn sách tuyệt vời Lập trình song song và đồng thời của Simon Marlow trong Haskell (trích dẫn từ Chương 1 ):

Một chương trình song song là một chương trình sử dụng nhiều phần cứng tính toán (ví dụ: một số lõi xử lý) để thực hiện tính toán nhanh hơn. Mục đích là đi đến câu trả lời sớm hơn, bằng cách ủy thác các phần khác nhau của tính toán cho các bộ xử lý khác nhau thực thi cùng một lúc.

Ngược lại, concurrency là một kỹ thuật cấu trúc chương trình, trong đó có nhiều luồng điều khiển. Về mặt khái niệm, các luồng điều khiển thực thi trên cùng một lúc; đó là, người dùng thấy tác dụng của chúng xen kẽ. Cho dù họ thực sự thực hiện cùng một lúc hay không là một chi tiết thực hiện; một chương trình đồng thời có thể thực thi trên một bộ xử lý thông qua thực thi xen kẽ hoặc trên nhiều bộ xử lý vật lý.

Ngoài ra, Marlow còn đề cập đến khía cạnh quyết định :

Một sự khác biệt có liên quan là giữa các mô hình lập trình xác địnhkhông xác định . Một mô hình lập trình xác định là một mô hình trong đó mỗi chương trình chỉ có thể cho một kết quả, trong khi đó mô hình lập trình không xác định thừa nhận các chương trình có thể có kết quả khác nhau, tùy thuộc vào một số khía cạnh của việc thực hiện. Các mô hình lập trình đồng thời nhất thiết là không xác định bởi vì chúng phải tương tác với các tác nhân bên ngoài gây ra các sự kiện vào thời điểm không thể đoán trước. Tuy nhiên, Nondeterminism có một số nhược điểm đáng chú ý: Các chương trình trở nên khó khăn hơn đáng kể để kiểm tra và lý do.

Để lập trình song song, chúng tôi muốn sử dụng các mô hình lập trình xác định nếu có thể. Vì mục tiêu chỉ là đi đến câu trả lời nhanh hơn, chúng tôi thà không làm cho chương trình của chúng tôi khó gỡ lỗi hơn trong quá trình. Lập trình song song xác định là tốt nhất của cả hai thế giới: Kiểm tra, gỡ lỗi và lý luận có thể được thực hiện trên chương trình tuần tự, nhưng chương trình chạy nhanh hơn với việc bổ sung thêm bộ xử lý.

Trong Haskell, các tính năng song song và đồng thời được thiết kế xung quanh các khái niệm này. Cụ thể, những ngôn ngữ khác nhóm lại thành một bộ tính năng, Haskell chia thành hai:

  • Các tính năng quyết định và thư viện cho song song .
  • Các tính năng và thư viện không xác định cho đồng thời .

Nếu bạn chỉ đang cố gắng tăng tốc một tính toán thuần túy, xác định, thì việc có sự song song xác định thường giúp mọi việc dễ dàng hơn nhiều. Thường thì bạn chỉ cần làm một cái gì đó như thế này:

  1. Viết một hàm tạo ra một danh sách các câu trả lời, mỗi câu trả lời rất tốn kém để tính toán nhưng không phụ thuộc nhiều vào nhau. Đây là Haskell, vì vậy các danh sách lười biếng giá trị của các yếu tố của chúng không thực sự được tính toán cho đến khi người tiêu dùng yêu cầu chúng.
  2. Sử dụng thư viện Chiến lược để sử dụng song song các phần tử danh sách kết quả của hàm của bạn trên nhiều lõi.

Tôi thực sự đã làm điều này với một trong những chương trình dự án đồ chơi của tôi vài tuần trước . Thật là tầm thường khi song song hóa chương trình, điều quan trọng tôi phải làm là, thực tế, thêm một số mã có nghĩa là "tính toán các yếu tố của danh sách này song song" (dòng 90) và tôi đã tăng được thông lượng gần như tuyến tính trong một số trường hợp thử nghiệm đắt tiền hơn của tôi.

Chương trình của tôi có nhanh hơn nếu tôi sử dụng các tiện ích đa luồng dựa trên khóa thông thường không? Tôi rất nghi ngờ như vậy. Điều gọn gàng trong trường hợp của tôi đã nhận được rất nhiều tiếng vang từ rất ít. và không có rủi ro về điều kiện chủng tộc. Và điều đó, tôi sẽ khẳng định, là cách chính mà lập trình chức năng cho phép bạn viết các chương trình "nhanh hơn".


2

Trong Haskell, sửa đổi theo nghĩa đen là không thể nếu không có các biến có thể sửa đổi đặc biệt thông qua thư viện sửa đổi. Thay vào đó, các hàm tạo các biến họ cần cùng lúc với các giá trị của chúng (được tính toán một cách lười biếng) và rác được thu thập khi không còn cần thiết.

Ngay cả khi bạn cần các biến sửa đổi, bạn thường có thể nhận được bằng cách sử dụng một cách tiết kiệm và cùng với các biến không thể thay đổi. . hiệu suât khôn ngoan.

Điều này làm cho sự song song trong Haskell trở nên dễ dàng rất nhiều thời gian, và trên thực tế, những nỗ lực đang được thực hiện để làm cho nó tự động. Đối với mã đơn giản, sự song song và logic thậm chí có thể được tách ra.

Ngoài ra, do thực tế là thứ tự đánh giá không thành vấn đề trong Haskell, trình biên dịch chỉ tạo ra một hàng đợi những thứ cần đánh giá và gửi chúng đến bất kỳ lõi nào có sẵn, vì vậy bạn có thể tạo ra một loạt các "luồng" không thực sự trở thành chủ đề cho đến khi cần thiết Thứ tự đánh giá không quan trọng là đặc trưng của độ tinh khiết, thường đòi hỏi phải lập trình chức năng.

Đọc thêm về tính
song song trong Haskell (HaskellWiki)
Lập trình đồng thời và đa lõi trong lập trình song song và "Haskell thế giới thực"
trong Haskell của Simon Marlow


7
grep java this_post. grep scala this_postgrep jvm this_postkhông trả lại kết quả nào :)
Andres F.

4
Câu hỏi mơ hồ. Trong tiêu đề và đoạn đầu tiên, nó hỏi về lập trình chức năng nói chung , trong đoạn thứ hai và thứ ba, nó hỏi về Java và Scala nói riêng . Điều đó thật đáng tiếc, đặc biệt là vì một trong những thế mạnh cốt lõi của Scala chính xác là nó không phải (chỉ) một ngôn ngữ chức năng. Martin Oderky gọi nó là "hậu chức năng", những người khác gọi nó là "chức năng đối tượng". Có hai định nghĩa khác nhau về thuật ngữ "lập trình chức năng". Một là "lập trình với các thủ tục hạng nhất" (định nghĩa ban đầu được áp dụng cho LISP), hai là "
Wörg W Mittag

2
"Lập trình với các chức năng miễn phí trong suốt, thuần túy, có tác dụng phụ và dữ liệu bền vững bất biến" (một cách giải thích chặt chẽ hơn và cũng mới hơn). Câu trả lời này giải quyết cách hiểu thứ hai, có ý nghĩa, bởi vì a) cách hiểu thứ nhất hoàn toàn không liên quan đến sự song song và đồng thời, b) cách giải thích đầu tiên về cơ bản trở nên vô nghĩa vì ngoại trừ C hầu như mọi ngôn ngữ đều được sử dụng rộng rãi một cách khiêm tốn hôm nay có các thủ tục hạng nhất (bao gồm Java) và c) OP hỏi về sự khác biệt giữa Java và Scala, nhưng không có câu hỏi nào
Jörg W Mittag

2
giữa hai liên quan đến định nghĩa # 1, chỉ định nghĩa # 2.
Jörg W Mittag

Điều đánh giá không hoàn toàn đúng vì nó được viết ở đây; Theo mặc định, bộ thực thi hoàn toàn không sử dụng đa luồng, và IIRC ngay cả khi bạn bật đa luồng, bạn vẫn phải nói với bộ thực thi những gì cần đánh giá song song.
Khối
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.