Về mặt kỹ thuật, tại sao các quy trình trong Erlang hiệu quả hơn các luồng hệ điều hành?


170

Đặc điểm của Erlang

Từ lập trình Erlang (2009):

Đồng thời Erlang là nhanh chóng và có thể mở rộng. Các quy trình của nó rất nhẹ ở chỗ máy ảo Erlang không tạo ra một luồng hệ điều hành cho mọi quy trình được tạo. Chúng được tạo, lên lịch và xử lý trong VM, độc lập với hệ điều hành cơ bản. Kết quả là, thời gian tạo quy trình là thứ tự của micro giây và không phụ thuộc vào số lượng quy trình hiện có đồng thời. So sánh điều này với Java và C #, trong đó đối với mọi quy trình, một luồng hệ điều hành cơ bản được tạo ra: bạn sẽ có được một số so sánh rất cạnh tranh, với Erlang vượt trội hơn cả hai ngôn ngữ.

Từ lập trình định hướng đồng thời trong Erlang (pdf) (slide) (2003):

Chúng tôi quan sát thấy rằng thời gian để tạo ra một quy trình Erlang là 1 hằng số lên đến 2.500 quy trình; Sau đó, nó tăng lên khoảng 3 Lời nói cho tối đa 30.000 quy trình. Hiệu năng của Java và C # được hiển thị ở trên cùng của hình. Đối với một số lượng nhỏ các quy trình, cần khoảng 300 Lời nói để tạo ra một quy trình. Tạo hơn hai nghìn quy trình là không thể.

Chúng tôi thấy rằng trong tối đa 30.000 quy trình, thời gian để gửi tin nhắn giữa hai quy trình Erlang là khoảng 0,8. Đối với C #, phải mất khoảng 50 mật khẩu cho mỗi tin nhắn, tối đa số lượng quy trình tối đa (khoảng 1800 quy trình). Java thậm chí còn tệ hơn, với tối đa 100 quy trình, phải mất khoảng 50 cảm nhận cho mỗi tin nhắn sau đó, nó đã tăng nhanh lên 10ms mỗi tin nhắn khi có khoảng 1000 quy trình Java.

Suy nghĩ của tôi

Tôi không hiểu đầy đủ về mặt kỹ thuật tại sao các quy trình Erlang hiệu quả hơn nhiều trong việc sinh ra các quy trình mới và có dấu chân bộ nhớ nhỏ hơn nhiều cho mỗi quy trình. Cả HĐH và Erlang VM đều phải lập lịch, chuyển đổi ngữ cảnh và theo dõi các giá trị trong các thanh ghi, v.v.

Đơn giản là tại sao các luồng hệ điều hành không được triển khai giống như các quy trình trong Erlang? Họ có phải hỗ trợ thêm gì không? Và tại sao họ cần một dấu chân bộ nhớ lớn hơn? Và tại sao chúng có khả năng sinh sản và giao tiếp chậm hơn?

Về mặt kỹ thuật, tại sao các quy trình trong Erlang hiệu quả hơn các luồng hệ điều hành khi nói đến sinh sản và giao tiếp? Và tại sao các luồng trong HĐH không thể được triển khai và quản lý theo cùng một cách hiệu quả? Và tại sao các luồng hệ điều hành có dung lượng bộ nhớ lớn hơn, cộng với việc sinh sản và giao tiếp chậm hơn?

Đọc thêm


1
Trước khi cố gắng hiểu lý do tại sao một giả thuyết là đúng, bạn cần xác định xem giả thuyết đó có đúng hay không - ví dụ, được hỗ trợ bởi bằng chứng. Bạn có tài liệu tham khảo cho bất kỳ như-cho-như so sánh chứng minh rằng một quá trình Erlang thực sự hiệu quả hơn (nói) một sợi Java trên một ngày up-to-JVM? Hoặc một ứng dụng C sử dụng quy trình OS và hỗ trợ luồng trực tiếp? (Cái sau có vẻ rất, rất khó xảy ra với tôi. Cái trước chỉ có khả năng.) Ý tôi là, với một môi trường đủ hạn chế (quan điểm của Francisco), nó có thể đúng, nhưng tôi muốn xem các con số.
TJ Crowder

1
@Donal: Như trường hợp với rất nhiều tuyên bố tuyệt đối khác. :-)
TJ Crowder

1
@Jonas: Cảm ơn, nhưng tôi đã đến ngày (1998-11-02) và phiên bản JVM (1.1.6) và dừng lại. JVM của Sun đã cải thiện một chút công bằng trong 11,5 năm qua (và có lẽ là thông dịch viên của Erlang cũng vậy), đặc biệt là trong lĩnh vực phân luồng. (Nói rõ hơn, tôi không nói rằng giả thuyết đó không đúng [và Francisco và Donal đã chỉ ra lý do tại sao Erland có thể làm gì đó ở đó]; Tôi đang nói rằng không nên lấy theo mệnh giá mà không được kiểm tra.)
TJ Crowder

1
@Jonas: "... nhưng tôi đoán bạn có thể làm điều đó trong Erlang ..." Đó là phần "đoán", anh bạn. :-) Bạn đang đoán rằng quá trình chuyển đổi quy trình của Erlang vượt quá hàng ngàn. Bạn đoán rằng nó hoạt động tốt hơn các luồng Java hoặc OS. Đoán và phần mềm dev không phải là một sự kết hợp tuyệt vời. :-) Nhưng tôi nghĩ rằng tôi đã đưa ra quan điểm của mình.
TJ Crowder

17
@TJ Crowder: Cài đặt erlang và chạy erl +P 1000100 +hms 100và hơn loại {_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).và chờ khoảng ba phút để có kết quả. Điều đó thật đơn giản. Phải mất 140us cho mỗi quá trình và 1GB toàn bộ RAM trên máy tính xách tay của tôi. Nhưng nó là shell trực tiếp, nó sẽ tốt hơn từ mã được biên dịch.
Hynek -Pichi- Vychodil

Câu trả lời:


113

Có một số yếu tố góp phần:

  1. Các quy trình Erlang không phải là các quy trình HĐH. Chúng được Erlang VM triển khai bằng cách sử dụng mô hình luồng hợp tác nhẹ (ưu tiên ở cấp độ Erlang, nhưng dưới sự kiểm soát của thời gian chạy theo lịch trình hợp tác). Điều này có nghĩa là việc chuyển đổi ngữ cảnh rẻ hơn nhiều, vì chúng chỉ chuyển đổi tại các điểm đã biết, được kiểm soát và do đó không phải lưu toàn bộ trạng thái CPU (bình thường, các thanh ghi SSE và FPU, ánh xạ không gian địa chỉ, v.v.).
  2. Các quy trình Erlang sử dụng các ngăn xếp được phân bổ động, bắt đầu rất nhỏ và phát triển khi cần thiết. Điều này cho phép sinh ra hàng ngàn - thậm chí hàng triệu quy trình Erlang mà không cần hút hết RAM có sẵn.
  3. Erlang được sử dụng là một luồng đơn, có nghĩa là không có yêu cầu để đảm bảo an toàn luồng giữa các quy trình. Hiện tại nó hỗ trợ SMP, nhưng sự tương tác giữa các quy trình Erlang trên cùng một bộ lập lịch / lõi vẫn rất nhẹ (có hàng đợi chạy riêng cho mỗi lõi).

6
Đến điểm thứ 2 của bạn: Và nếu quá trình chưa chạy, không có lý do gì để ngăn xếp được phân bổ cho nó. Ngoài ra: Một số thủ thuật có thể được chơi bằng cách đấu tranh với GC của một quá trình sao cho nó không bao giờ thu thập bộ nhớ. Nhưng điều đó là tiên tiến và có phần nguy hiểm :)
TÔI ĐÃ TRẢ LỜI CÂU TRẢ LỜI

3
Đến điểm thứ 3 của bạn: Erlang thực thi dữ liệu bất biến, vì vậy việc giới thiệu SMP sẽ không ảnh hưởng đến an toàn luồng.
nilskp

@ nilskp, điều đó đúng, erlang cũng là một ngôn ngữ lập trình chức năng. Vì vậy, không có dữ liệu "biến". Điều này dẫn đến an toàn luồng.
liuyang1

6
Tài khoản Hỗ trợ SMP chính xác và hiệu quả đã không xảy ra chỉ với một cú gạt.
Marcelo Cantos

@rvirding: Cảm ơn đã bổ sung làm rõ. Tôi đã lấy tự do để tích hợp điểm của bạn vào phần câu trả lời của tôi.
Marcelo Cantos

73

Sau một số nghiên cứu thêm, tôi tìm thấy một bài thuyết trình của Joe Armstrong.

Từ Erlang - phần mềm cho một thế giới đồng thời (thuyết trình) (lúc 13 phút):

[Erlang] là một ngôn ngữ đồng thời - ý tôi là các luồng là một phần của ngôn ngữ lập trình, chúng không thuộc về hệ điều hành. Điều đó thực sự có vấn đề với các ngôn ngữ lập trình như Java và C ++. Đó là các luồng không có trong ngôn ngữ lập trình, các luồng là một thứ gì đó trong hệ điều hành - và chúng thừa hưởng tất cả các vấn đề mà chúng có trong hệ điều hành. Một trong những vấn đề là độ chi tiết của hệ thống quản lý bộ nhớ. Quản lý bộ nhớ trong hệ điều hành bảo vệ toàn bộ trang của bộ nhớ, vì vậy kích thước nhỏ nhất mà một luồng có thể là kích thước nhỏ nhất của trang. Điều đó thực sự quá lớn.

Nếu bạn thêm nhiều bộ nhớ vào máy của mình - bạn có cùng số bit bảo vệ bộ nhớ để độ chi tiết của các bảng trang tăng lên - cuối cùng bạn sử dụng nói 64kB cho một quá trình bạn biết chạy trong vài trăm byte.

Tôi nghĩ rằng nó trả lời nếu không phải tất cả, ít nhất là một vài câu hỏi của tôi



2
Việc bảo vệ bộ nhớ trên ngăn xếp là có lý do. Có phải Erlang không bảo vệ các ngăn xếp của các bối cảnh thực thi khác nhau thông qua MMU của bộ xử lý? (Và chỉ hy vọng điều tốt nhất?) Điều gì xảy ra nếu một chủ đề sử dụng nhiều hơn ngăn xếp nhỏ của nó? (Có phải tất cả phân bổ chồng kiểm tra để xem nếu một chồng lớn hơn là cần thiết là chồng di chuyển?)
Thanatos

2
@Thanatos: Erlang không cho phép các chương trình truy cập bộ nhớ hoặc fiddle với ngăn xếp. Tất cả các phân bổ phải đi qua thời gian chạy được quản lý, cả heap và stack. Nói cách khác: bảo vệ phần cứng là vô ích vì nó bảo vệ chống lại những điều không thể xảy ra. Ngôn ngữ là an toàn con trỏ, an toàn ngăn xếp, an toàn bộ nhớ và an toàn loại. Một quá trình không thể sử dụng nhiều hơn "ngăn xếp nhỏ" của nó vì ngăn xếp phát triển khi cần thiết. Bạn có thể nghĩ về nó như đối nghịch với nhỏ bé: vô cùng lớn. (Nhưng được phân bổ một cách lười biếng.)
Jörg W Mittag

4
Bạn nên xem Hệ điều hành Singularity của Microsoft Research. Trong Singularity, tất cả mã, kernel, trình điều khiển thiết bị, thư viện và chương trình người dùng chạy trong vòng 0 với các đặc quyền kernel đầy đủ. Tất cả mã, kernel, trình điều khiển thiết bị, thư viện và chương trình người dùng chạy trong một không gian địa chỉ vật lý phẳng duy nhất không có bảo vệ bộ nhớ nào. Nhóm nghiên cứu nhận thấy rằng các đảm bảo ngôn ngữ tạo ra mạnh hơn nhiều so với các đảm bảo mà MMU có thể thực hiện, đồng thời sử dụng MMU khiến chúng có hiệu suất lên tới 30% (!!!). Vì vậy, tại sao sử dụng MMU nếu ngôn ngữ của bạn đã thực hiện nó?
Jörg W Mittag

1
Hệ điều hành OS / 400 hoạt động theo cùng một cách. Chỉ có một không gian địa chỉ phẳng duy nhất cho tất cả các chương trình. Và hầu hết các ngôn ngữ trong sử dụng thực tế ngày nay đều có các thuộc tính an toàn giống nhau (ECMAScript, Java, C♯, VB.NET, PHP, Perl, Python, Ruby, Clojure, Scala, Kotlin, Groovy, Ceylon, F♯, OCaml, Phần "Mục tiêu" của "Mục tiêu-C", phần "++" của "C ++"). Nếu nó không dành cho mã C kế thừa và các tính năng cũ của C ++ và Objective-C, chúng ta thậm chí sẽ không cần bộ nhớ ảo nữa.
Jörg W Mittag

47

Tôi đã triển khai coroutines trong trình biên dịch chương trình, và đo hiệu suất.

Chuyển đổi giữa các coroutines, còn gọi là quá trình Erlang, mất khoảng 16 lệnh và 20 nano giây trên bộ xử lý hiện đại. Ngoài ra, bạn thường biết quy trình bạn đang chuyển sang (ví dụ: một quy trình nhận tin nhắn trong hàng đợi của nó có thể được thực hiện dưới dạng chuyển thẳng từ quy trình gọi sang quy trình nhận) để bộ lập lịch không hoạt động, thực hiện đó là một hoạt động O (1).

Để chuyển đổi các luồng hệ điều hành, phải mất khoảng 500-1000 nano giây, vì bạn đang gọi xuống kernel. Bộ lập lịch xử lý luồng của hệ điều hành có thể chạy trong thời gian O (log (n)) hoặc O (log (log (n))), sẽ bắt đầu đáng chú ý nếu bạn có hàng chục nghìn hoặc thậm chí hàng triệu luồng.

Do đó, các quy trình Erlang nhanh hơn và mở rộng quy mô tốt hơn vì cả hoạt động cơ bản của chuyển đổi đều nhanh hơn và bộ lập lịch chạy ít thường xuyên hơn.


33

Các quy trình Erlang tương ứng (xấp xỉ) với các luồng màu xanh lục trong các ngôn ngữ khác; không có sự phân tách do hệ điều hành thực thi giữa các quy trình. (Cũng có thể có sự phân tách bằng ngôn ngữ, nhưng đó là sự bảo vệ ít hơn mặc dù Erlang làm việc tốt hơn hầu hết.) Vì chúng có trọng lượng nhẹ hơn rất nhiều, nên chúng có thể được sử dụng rộng rãi hơn nhiều.

Mặt khác, các luồng hệ điều hành có thể được lên lịch đơn giản trên các lõi CPU khác nhau và (hầu hết) có thể hỗ trợ xử lý ràng buộc CPU độc lập. Các quy trình của hệ điều hành giống như các luồng của hệ điều hành, nhưng với sự phân tách do hệ điều hành mạnh hơn nhiều. Giá của các khả năng này là các luồng hệ điều hành và các quy trình (thậm chí nhiều hơn) đắt hơn.


Một cách khác để hiểu sự khác biệt là điều này. Giả sử bạn sẽ viết một triển khai Erlang trên JVM (không phải là một gợi ý đặc biệt điên rồ) thì bạn sẽ biến mỗi quá trình Erlang trở thành một đối tượng với một số trạng thái. Sau đó, bạn sẽ có một nhóm các phiên bản Thread (thường có kích thước theo số lượng lõi trong hệ thống máy chủ của bạn; đó là một tham số có thể điều chỉnh trong BTW runtimes BTW thực) chạy các quy trình Erlang. Đổi lại, điều đó sẽ phân phối công việc sẽ được thực hiện trên các tài nguyên hệ thống thực có sẵn. Đó là một cách làm việc khá gọn gàng, nhưng hoàn toàn dựa vàotrên thực tế là mỗi quá trình Erlang riêng lẻ không làm được gì nhiều. Tất nhiên là ổn rồi; Erlang được cấu trúc để không yêu cầu các quá trình riêng lẻ đó trở nên nặng nề vì đó là toàn bộ nhóm thực hiện chương trình.

Theo nhiều cách, vấn đề thực sự là một trong những thuật ngữ. Những thứ mà Erlang gọi là các quy trình (và tương ứng mạnh với cùng một khái niệm trong CSP, CCS và đặc biệt là phép tính π) đơn giản không giống với những thứ mà các ngôn ngữ có di sản C (bao gồm C ++, Java, C # và nhiều người khác) gọi một quá trình hoặc một chủ đề. Có một số điểm tương đồng (tất cả đều liên quan đến một số khái niệm về thực thi đồng thời) nhưng chắc chắn không có sự tương đương. Vì vậy, hãy cẩn thận khi có ai đó nói về quy trình của bạn với bạn; họ có thể hiểu nó có nghĩa là một cái gì đó hoàn toàn khác biệt


3
Erlang không đến được bất cứ nơi nào gần với Pi Compus. Tính toán Pi giả định các sự kiện đồng bộ trên các kênh có thể được liên kết với các biến. Kiểu khái niệm này hoàn toàn không phù hợp với mô hình Erlang. Hãy thử Tham gia tính toán, Erlang gần hơn với điều đó mặc dù nó vẫn cần có khả năng tham gia một cách tự nhiên vào một số tin nhắn và không có gì. Có một bài luận văn (và dự án) có tên JErlang dành riêng để thực hiện nó.
TÔI ĐÃ TƯ VẤN TERRIBLE

Tất cả phụ thuộc vào chính xác bạn xem tính toán pi là gì (và bạn có thể mô hình hóa các kênh không đồng bộ với các kênh đồng bộ cộng với các quy trình đệm).
Donal Fellows

Bạn chỉ nói rằng các quy trình Erlang rất nhẹ nhưng bạn không giải thích được tại sao chúng có dấu chân nhỏ hơn (nhẹ) và tại sao chúng có hiệu suất tốt hơn các luồng của hệ điều hành.
Jonas

1
@Jonas: Đối với một số loại tác vụ (đặc biệt là các tác vụ nặng tính toán) Các luồng hệ điều hành làm tốt hơn. Xin lưu ý bạn, đó không phải là những nhiệm vụ điển hình mà Erlang được sử dụng; Erlang tập trung vào việc có số lượng lớn các nhiệm vụ giao tiếp đơn giản. Một trong những lợi ích của việc đó là trong trường hợp một nhóm các tác vụ xử lý một phần công việc xung quanh và chờ kết quả, tất cả có thể được thực hiện trong một luồng hệ điều hành duy nhất trên một bộ xử lý, hiệu quả hơn có chuyển mạch ngữ cảnh.
Donal Fellows

Về mặt lý thuyết, bạn có thể làm cho một luồng hệ điều hành trở nên rất rẻ thông qua việc sử dụng một ngăn xếp rất nhỏ và kiểm soát cẩn thận số lượng tài nguyên dành riêng cho luồng khác được phân bổ, nhưng thực tế nó có vấn đề. . số lượng xử lý mỗi.
Donal Fellows

3

Tôi nghĩ Jonas muốn một số con số về việc so sánh các luồng của hệ điều hành với các quá trình Erlang. Tác giả của Lập trình Erlang, Joe Armstrong, một thời gian trước đã kiểm tra khả năng mở rộng của việc sinh ra các quy trình Erlang cho các luồng của hệ điều hành. Ông đã viết một máy chủ web đơn giản bằng Erlang và thử nghiệm nó với Apache đa luồng (vì Apache sử dụng các luồng của hệ điều hành). Có một trang web cũ với dữ liệu có từ năm 1998. Tôi chỉ quản lý để tìm thấy trang web đó chính xác một lần. Vì vậy, tôi không thể cung cấp một liên kết. Nhưng thông tin là có. Điểm chính của nghiên cứu cho thấy Apache đã đạt tối đa chỉ dưới 8K quy trình, trong khi máy chủ Erlang viết tay của anh ấy xử lý các quy trình 10K +.


5
Tôi nghĩ rằng bạn đang nói về điều này: sics.se/~joe/apachevsyaws.html Nhưng tôi đã hỏi làm thế nào erlang làm cho các chủ đề rất hiệu quả so với các chủ đề kerlenl.
Jonas

Liên kết @Jonas đã chết. Ảnh chụp cuối cùng ở đây
alvaro g

1
Bài báo viết: "Apache chết ở khoảng 4.000 phiên song song. Yaws vẫn hoạt động ở hơn 80.000 kết nối song song."
Nathan Long

xem toàn bộ bài viết tại citeseerx.ist.psu.edu/viewdoc/ , Thật vậy, đã chứng minh không thể phá vỡ máy chủ Erlang bằng 16 máy tấn công - mặc dù rất dễ để chặn máy chủ Apache.
Bernhard

1

Do trình thông dịch Erlang chỉ phải lo lắng về bản thân, nên HĐH có nhiều thứ khác phải lo lắng.


0

Một trong những lý do là quá trình erlang được tạo ra không phải trong HĐH, mà là trong evm (máy ảo erlang), do đó chi phí nhỏ hơn.

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.