phương pháp nhanh nhất (độ trễ thấp) cho Giao tiếp giữa các quy trình giữa Java và C / C ++


100

Tôi có một ứng dụng Java, kết nối thông qua cổng TCP với một "máy chủ" được phát triển bằng C / C ++.

cả ứng dụng và máy chủ đều đang chạy trên cùng một máy, một hộp Solaris (nhưng cuối cùng chúng tôi đang xem xét chuyển sang Linux). loại dữ liệu được trao đổi là các thông điệp đơn giản (đăng nhập, đăng nhập ACK, sau đó máy khách yêu cầu một cái gì đó, máy chủ trả lời). mỗi tin nhắn dài khoảng 300 byte.

Hiện tại chúng tôi đang sử dụng Sockets và tất cả đều ổn, tuy nhiên tôi đang tìm một cách nhanh hơn để trao đổi dữ liệu (độ trễ thấp hơn), bằng cách sử dụng các phương pháp IPC.

Tôi đã nghiên cứu mạng và tìm ra tài liệu tham khảo về các công nghệ sau:

  • chia sẻ bộ nhớ
  • đường ống
  • hàng đợi
  • cũng như những gì được gọi là DMA (Truy cập bộ nhớ trực tiếp)

nhưng tôi không thể tìm thấy phân tích thích hợp về hiệu suất tương ứng của chúng, cũng không phải cách triển khai chúng trong cả JAVA và C / C ++ (để chúng có thể nói chuyện với nhau), ngoại trừ có thể là các đường ống mà tôi có thể hình dung cách làm.

bất cứ ai có thể nhận xét về hiệu suất và tính khả thi của mỗi phương pháp trong bối cảnh này? bất kỳ con trỏ / liên kết đến thông tin triển khai hữu ích?


CHỈNH SỬA / CẬP NHẬT

theo nhận xét và câu trả lời tôi nhận được ở đây, tôi tìm thấy thông tin về Unix Domain Sockets, có vẻ như chỉ được xây dựng trên đường ống và sẽ giúp tôi tiết kiệm toàn bộ ngăn xếp TCP. nó dành riêng cho nền tảng, vì vậy tôi dự định thử nghiệm nó với JNI hoặc juds hoặc junixsocket .

Các bước khả thi tiếp theo sẽ là thực hiện trực tiếp các đường ống, sau đó chia sẻ bộ nhớ, mặc dù tôi đã được cảnh báo về mức độ phức tạp hơn ...


Cảm ơn bạn đã giúp đỡ


7
Nó có thể là quá mức cần thiết trong trường hợp của bạn, nhưng xem xét zeromq.org
JFS

điều đó thật thú vị, tuy nhiên ý tưởng sẽ là sử dụng các phương thức "chung chung" (như trong các phương thức do hệ điều hành cung cấp hoặc do ngôn ngữ cung cấp), đó là lý do tại sao tôi đề cập đến hàng đợi và bộ nhớ được chia sẻ.
Bastien


Đừng quên các tệp được ánh xạ hoặc chỉ UDP.

10
UDP chậm hơn TCP ??? hmmm ... bằng chứng xin
Boppity Bop

Câu trả lời:


103

Vừa mới kiểm tra độ trễ từ Java trên Corei5 2,8GHz của tôi, chỉ gửi / nhận một byte duy nhất, 2 quy trình Java vừa sinh ra mà không chỉ định lõi CPU cụ thể với bộ tác vụ:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Bây giờ chỉ định rõ ràng mặt nạ lõi, như tasket 1 java Srv hoặc tasket 2 java Cli :

TCP, same cores:                      30 microseconds
TCP, explicit different cores:        22 microseconds
Named pipes, same core:               4-5 microseconds !!!!
Named pipes, taskset different cores: 7-8 microseconds !!!!

vì thế

TCP overhead is visible
scheduling overhead (or core caches?) is also the culprit

Đồng thời, Thread.sleep (0) (như strace cho thấy nguyên nhân thực thi một lệnh gọi nhân Linux Sched_yield () duy nhất) mất 0,3 micro giây - vì vậy các đường ống được đặt tên được lập lịch cho lõi đơn vẫn có nhiều chi phí

Một số phép đo bộ nhớ dùng chung: Ngày 14 tháng 9 năm 2009 - Solace Systems đã công bố hôm nay rằng API nền tảng nhắn tin hợp nhất của nó có thể đạt được độ trễ trung bình dưới 700 nano giây bằng cách sử dụng truyền tải bộ nhớ chia sẻ. http://solacesystems.com/news/fastest-ipc-messaging/

PS - đã thử bộ nhớ được chia sẻ vào ngày hôm sau dưới dạng tệp ánh xạ bộ nhớ, nếu việc chờ đợi bận được chấp nhận, chúng tôi có thể giảm độ trễ xuống 0,3 micro giây để truyền một byte đơn với mã như sau:

MappedByteBuffer mem =
  new RandomAccessFile("/tmp/mapped.txt", "rw").getChannel()
  .map(FileChannel.MapMode.READ_WRITE, 0, 1);

while(true){
  while(mem.get(0)!=5) Thread.sleep(0); // waiting for client request
  mem.put(0, (byte)10); // sending the reply
}

Lưu ý: Thread.sleep (0) là cần thiết để 2 quy trình có thể nhìn thấy những thay đổi của nhau (tôi chưa biết cách khác). Nếu 2 quy trình buộc phải sử dụng cùng một lõi với bộ tác vụ, độ trễ sẽ trở thành 1,5 micro giây - đó là độ trễ chuyển đổi ngữ cảnh

PPS - và 0,3 micro giây là một con số tốt! Đoạn mã sau mất chính xác 0,1 micro giây, trong khi chỉ thực hiện ghép chuỗi nguyên thủy:

int j=123456789;
String ret = "my-record-key-" + j  + "-in-db";

PPPS - hy vọng điều này không quá lạc đề, nhưng cuối cùng tôi đã thử thay thế Thread.sleep (0) bằng cách tăng một biến int biến động tĩnh (JVM xảy ra để xóa bộ đệm CPU khi làm như vậy) và thu được - ghi lại! - Giao tiếp quy trình java-to-java độ trễ 72 nano giây !

Tuy nhiên, khi buộc phải sử dụng cùng một Lõi CPU, các JVM tăng biến động không bao giờ nhường quyền kiểm soát cho nhau, do đó tạo ra độ trễ chính xác 10 mili giây - lượng tử thời gian của Linux dường như là 5ms ... Vì vậy, điều này chỉ nên được sử dụng nếu có một lõi dự phòng - nếu không thì sleep (0) an toàn hơn.


cảm ơn Andriy, rất nghiên cứu thông tin và nó phù hợp ít nhiều với các phép đo của tôi cho TCP, vì vậy đó là một tài liệu tham khảo tốt. Tôi đoán tôi sẽ xem xét các đường ống được đặt tên.
Bastien

Vì vậy, việc thay thế Thread (Sleep) bằng việc tăng int static dễ bay hơi chỉ nên được thực hiện nếu bạn có thể ghim một tiến trình vào các lõi khác nhau? Ngoài ra, tôi không nhận ra bạn có thể làm điều này? Tôi nghĩ rằng hệ điều hành quyết định?
mezamorphic

3
Hãy thử LockSupport.parkNanos (1), sẽ làm điều tương tự.
reccles

Rất đẹp. Tuy nhiên, bạn có thể làm tốt hơn (như trong độ trễ RTT 5-7us) cho TCP ping. Xem tại đây: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart

1
Khám phá thêm về việc sử dụng tệp ánh xạ bộ nhớ làm bộ nhớ dùng chung để hỗ trợ hàng đợi IPC trong Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html đạt được 135 triệu tin nhắn một giây. Cũng xem câu trả lời của tôi bên dưới để nghiên cứu so sánh về độ trễ theo phương pháp.
Nitsan Wakart

10

DMA là một phương pháp mà các thiết bị phần cứng có thể truy cập RAM vật lý mà không làm gián đoạn CPU. Ví dụ: một ví dụ phổ biến là bộ điều khiển đĩa cứng có thể sao chép các byte trực tiếp từ đĩa vào RAM. Vì vậy, nó không áp dụng cho IPC.

Bộ nhớ dùng chung và đường ống đều được hỗ trợ trực tiếp bởi các hệ điều hành hiện đại. Như vậy, chúng khá nhanh. Hàng đợi thường là trừu tượng, ví dụ như được triển khai trên các ổ cắm, đường ống và / hoặc bộ nhớ dùng chung. Điều này có thể trông giống như một cơ chế chậm hơn, nhưng thay thế là bạn tạo ra một sự trừu tượng như vậy.


đối với DMA, tại sao đó là khi tôi có thể đọc rất nhiều thứ liên quan đến RDMA (như Truy cập Bộ nhớ Trực tiếp Từ xa) sẽ áp dụng trên toàn mạng (đặc biệt là với InfiniBand) và làm điều tương tự. Tôi thực sự đang cố gắng đạt được điều tương đương KHÔNG có mạng (vì tất cả đều nằm trên cùng một hộp).
Bastien

RDMA là khái niệm tương tự: sao chép byte qua mạng mà không làm gián đoạn CPU ở hai bên. Nó vẫn không hoạt động ở cấp quy trình.
MSalters

10

Câu hỏi đã được đặt ra cách đây một thời gian, nhưng bạn có thể quan tâm đến https://github.com/peter-lawrey/Java-Chronicle hỗ trợ độ trễ điển hình là 200 ns và thông lượng là 20 M tin nhắn / giây. Nó sử dụng các tệp được ánh xạ bộ nhớ được chia sẻ giữa các quy trình (nó cũng duy trì dữ liệu, đây là cách nhanh nhất để duy trì dữ liệu)



6

Nếu bạn từng cân nhắc sử dụng quyền truy cập gốc (vì cả ứng dụng của bạn và "máy chủ" đều trên cùng một máy), hãy xem xét JNA , nó có ít mã soạn sẵn hơn để bạn xử lý.


6

Đến muộn, nhưng muốn chỉ ra một dự án nguồn mở dành riêng để đo độ trễ ping bằng Java NIO.

Khám phá thêm / giải thích trong bài đăng blog này . Kết quả là (RTT tính bằng nano):

Implementation, Min,   50%,   90%,   99%,   99.9%, 99.99%,Max
IPC busy-spin,  89,    127,   168,   3326,  6501,  11555, 25131
UDP busy-spin,  4597,  5224,  5391,  5958,  8466,  10918, 18396
TCP busy-spin,  6244,  6784,  7475,  8697,  11070, 16791, 27265
TCP select-now, 8858,  9617,  9845,  12173, 13845, 19417, 26171
TCP block,      10696, 13103, 13299, 14428, 15629, 20373, 32149
TCP select,     13425, 15426, 15743, 18035, 20719, 24793, 37877

Đây là dọc theo dòng của câu trả lời được chấp nhận. Sai số System.nanotime () (ước tính bằng cách không đo gì) được đo ở khoảng 40 nano vì vậy đối với IPC, kết quả thực tế có thể thấp hơn. Thưởng thức.


2

Tôi không biết nhiều về giao tiếp giữa các quá trình bản địa, nhưng tôi đoán rằng bạn cần giao tiếp bằng mã gốc, mà bạn có thể truy cập bằng cách sử dụng cơ chế JNI. Vì vậy, từ Java, bạn sẽ gọi một hàm gốc nói chuyện với quy trình khác.



0

Bạn đã xem xét việc giữ các ổ cắm luôn mở để các kết nối có thể được sử dụng lại chưa?


các ổ cắm vẫn mở. kết nối vẫn tồn tại trong toàn bộ thời gian ứng dụng đang chạy (khoảng 7 giờ). tin nhắn được trao đổi ít nhiều liên tục (giả sử khoảng 5 đến 10 mỗi giây). độ trễ hiện tại là khoảng 200 micro giây, mục tiêu là giảm 1 hoặc 2 bậc độ lớn.
Bastien

Độ trễ 2 ms? Đầy tham vọng. Liệu có khả thi nếu viết lại C-things vào một thư viện chia sẻ mà bạn có thể giao tiếp với JNI?
Thorbjørn Ravn Andersen

2ms là 2000 micro giây, không phải 200. điều này khiến 2ms ít tham vọng hơn rất nhiều.
thewhiteambit

-1

Báo cáo lỗi của Oracle về hiệu suất JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI là một giao diện chậm và do đó, Java TCP socket là phương pháp nhanh nhất để thông báo giữa các ứng dụng, tuy nhiên điều đó không có nghĩa là bạn phải gửi trọng tải qua một socket. Sử dụng LDMA để truyền tải trọng, nhưng như các câu hỏi trước đã chỉ ra, hỗ trợ của Java cho ánh xạ bộ nhớ không lý tưởng và vì vậy bạn sẽ muốn triển khai thư viện JNI để chạy mmap.


3
Tại sao JNI chậm? Hãy xem xét cách hoạt động của lớp TCP cấp thấp trong Java, nó không được viết bằng mã byte Java! (Ví dụ: điều này phải truyền qua máy chủ gốc.) Do đó, tôi bác bỏ khẳng định rằng các socket Java TCP nhanh hơn JNI. (JNI, tuy nhiên, không phải là IPC.)

4
Một cuộc gọi JNI duy nhất khiến bạn mất 9ns (trên Intel i5) nếu bạn chỉ sử dụng nguyên thủy. Vì vậy, nó không phải là chậm.
Martin Kersten,
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.