วิธีที่เร็วที่สุด (เวลาแฝงต่ำ) สำหรับการสื่อสารระหว่างกระบวนการระหว่าง Java และ C / C ++


101

ฉันมีแอป Java ที่เชื่อมต่อผ่านซ็อกเก็ต TCP ไปยัง "เซิร์ฟเวอร์" ที่พัฒนาใน C / C ++

ทั้งแอพและเซิร์ฟเวอร์ทำงานบนเครื่องเดียวกันกล่อง Solaris (แต่เรากำลังพิจารณาที่จะย้ายไปยัง Linux ในที่สุด) ประเภทของข้อมูลที่แลกเปลี่ยนคือข้อความธรรมดา (ล็อกอินเข้าสู่ระบบ ACK จากนั้นไคลเอนต์ขอบางสิ่งบางอย่างการตอบกลับของเซิร์ฟเวอร์) แต่ละข้อความมีความยาวประมาณ 300 ไบต์

ขณะนี้เราใช้ Sockets และทุกอย่างก็โอเคอย่างไรก็ตามฉันกำลังมองหาวิธีที่เร็วกว่าในการแลกเปลี่ยนข้อมูล (เวลาแฝงที่ต่ำกว่า) โดยใช้วิธี IPC

ฉันได้ทำการค้นคว้าทางเน็ตและได้ทำการอ้างอิงถึงเทคโนโลยีต่อไปนี้:

  • หน่วยความจำที่ใช้ร่วมกัน
  • ท่อ
  • คิว
  • รวมทั้งสิ่งที่เรียกว่า DMA (Direct Memory Access)

แต่ฉันไม่พบการวิเคราะห์ที่เหมาะสมของการแสดงตามลำดับทั้งวิธีการใช้งานทั้งใน JAVA และ C / C ++ (เพื่อให้พวกเขาสามารถพูดคุยกันได้) ยกเว้นท่อที่ฉันสามารถจินตนาการได้ว่าจะทำอย่างไร

ใครสามารถแสดงความคิดเห็นเกี่ยวกับประสิทธิภาพและความเป็นไปได้ของแต่ละวิธีในบริบทนี้ ตัวชี้ / ลิงก์ไปยังข้อมูลการใช้งานที่เป็นประโยชน์หรือไม่


แก้ไข / อัปเดต

ตามความคิดเห็นและคำตอบที่ฉันได้มาที่นี่ฉันพบข้อมูลเกี่ยวกับ Unix Domain Sockets ซึ่งดูเหมือนจะสร้างขึ้นบนท่อและจะช่วยฉันทั้งสแต็ก TCP ทั้งหมด มันเฉพาะแพลตฟอร์มดังนั้นผมวางแผนที่จะทดสอบกับ JNI หรืออย่างใดอย่างหนึ่งjudsหรือjunixsocket

ขั้นตอนต่อไปที่เป็นไปได้คือการใช้งานไปป์โดยตรงจากนั้นแชร์หน่วยความจำแม้ว่าฉันจะได้รับคำเตือนถึงระดับความซับซ้อนเพิ่มเติม ...


ขอบคุณสำหรับความช่วยเหลือของคุณ


7
ในกรณีของคุณอาจเกินความจำเป็น แต่ให้พิจารณาzeromq.org
jfs

เป็นสิ่งที่น่าสนใจอย่างไรก็ตามแนวคิดคือการใช้วิธีการ "ทั่วไป" (ตามที่ OS ให้ไว้หรือภาษาที่ให้มา) ก่อนนั่นคือเหตุผลที่ฉันพูดถึงคิวและหน่วยความจำที่ใช้ร่วมกัน
Bastien


อย่าลืมไฟล์ที่แมปหรือแค่ UDP

10
UDP ช้ากว่า TCP ??? อืม ... โปรดพิสูจน์
Boppity Bop

คำตอบ:


103

เพิ่งทดสอบความหน่วงแฝงจาก Java บน Corei5 2.8GHz ของฉันการส่ง / รับเพียงไบต์เดียวกระบวนการ Java 2 กระบวนการที่เพิ่งสร้างโดยไม่ต้องกำหนดแกน CPU เฉพาะด้วยชุดงาน:

TCP         - 25 microseconds
Named pipes - 15 microseconds

ตอนนี้ระบุมาสก์หลักอย่างชัดเจนเช่นtasket 1 java Srvหรือ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 !!!!

ดังนั้น

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

ในเวลาเดียวกัน Thread.sleep (0) (ซึ่งตามที่แสดง strace ทำให้การเรียกเคอร์เนล sched_yield () ลินุกซ์เดียวถูกเรียกใช้) ใช้เวลา 0.3 ไมโครวินาที - ดังนั้นไพพ์ที่กำหนดให้เป็นแกนเดี่ยวยังคงมีค่าใช้จ่ายมาก

การวัดหน่วยความจำที่ใช้ร่วมกันบางส่วน: 14 กันยายน 2552 - Solace Systems ประกาศในวันนี้ว่า Unified Messaging Platform API สามารถบรรลุเวลาแฝงเฉลี่ยน้อยกว่า 700 นาโนวินาทีโดยใช้การขนส่งหน่วยความจำแบบแบ่งใช้ http://solacesystems.com/news/fastest-ipc-messaging/

PS - ลองใช้หน่วยความจำที่ใช้ร่วมกันในวันถัดไปในรูปแบบของไฟล์ที่แมปหน่วยความจำหากยอมรับการรอไม่ว่างเราสามารถลดเวลาแฝงเหลือ 0.3 ไมโครวินาทีสำหรับการส่งผ่านไบต์เดียวพร้อมรหัสดังนี้

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
}

หมายเหตุ: ต้องใช้ Thread.sleep (0) เพื่อให้ 2 กระบวนการสามารถเห็นการเปลี่ยนแปลงของกันและกัน (ฉันยังไม่รู้วิธีอื่น) หาก 2 กระบวนการบังคับให้ใช้แกนเดียวกันกับชุดงานเวลาแฝงจะกลายเป็น 1.5 ไมโครวินาทีนั่นคือความล่าช้าในการสลับบริบท

PPS - และ 0.3 ไมโครวินาทีเป็นตัวเลขที่ดี! รหัสต่อไปนี้ใช้เวลา 0.1 ไมโครวินาทีในขณะที่ทำการต่อสายอักขระดั้งเดิมเท่านั้น:

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

PPPS - หวังว่านี่จะไม่นอกประเด็นมากเกินไป แต่ในที่สุดฉันก็ลองเปลี่ยน Thread.sleep (0) ด้วยการเพิ่มตัวแปร int ระเหยแบบคงที่ (JVM เกิดขึ้นกับการล้างแคช CPU เมื่อทำเช่นนั้น) และได้รับ - บันทึก! - 72 นาโนวินาทีเวลาแฝงการสื่อสารกระบวนการ java-to-java !

อย่างไรก็ตามเมื่อถูกบังคับให้ใช้ CPU Core เดียวกัน JVM ที่เพิ่มความผันผวนไม่เคยให้การควบคุมซึ่งกันและกันดังนั้นจึงให้เวลาแฝง 10 มิลลิวินาที - ควอนตัมเวลาของ Linux ดูเหมือนจะเป็น 5ms ... ดังนั้นควรใช้เฉพาะเมื่อมีแกนสำรอง - มิฉะนั้นการนอนหลับ (0) จะปลอดภัยกว่า


ขอบคุณ Andriy การศึกษาข้อมูลเป็นอย่างดีและมันตรงกับการวัดของฉันสำหรับ TCP มากหรือน้อยดังนั้นจึงเป็นข้อมูลอ้างอิงที่ดี ฉันเดาว่าฉันจะดูท่อที่มีชื่อ
Bastien

ดังนั้นการแทนที่เธรด (สลีป) ด้วยการเพิ่มค่า int คงที่ที่ระเหยได้ควรทำก็ต่อเมื่อคุณสามารถตรึงกระบวนการกับคอร์อื่นได้? นอกจากนี้ฉันไม่รู้ว่าคุณสามารถทำสิ่งนี้ได้หรือไม่? ฉันคิดว่าระบบปฏิบัติการตัดสินใจ?
mezamorphic

3
ลอง LockSupport.parkNanos (1) ควรทำสิ่งเดียวกัน
บรรยาย

ดีมาก. คุณทำได้ดีกว่า (เช่นเดียวกับเวลาแฝง RTT 5-7us) สำหรับ TCP ping ดูที่นี่: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart

1
การสำรวจเพิ่มเติมเกี่ยวกับการใช้ไฟล์ที่แมปหน่วยความจำเป็นหน่วยความจำที่ใช้ร่วมกันเพื่อรองรับคิว IPC ใน Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.htmlบรรลุ 135M ข้อความต่อวินาที ดูคำตอบของฉันด้านล่างสำหรับการศึกษาเปรียบเทียบเวลาแฝงตามวิธี
Nitsan Wakart

10

DMA เป็นวิธีการที่อุปกรณ์ฮาร์ดแวร์สามารถเข้าถึง RAM จริงได้โดยไม่ขัดจังหวะ CPU เช่นตัวอย่างทั่วไปคือตัวควบคุมฮาร์ดดิสก์ซึ่งสามารถคัดลอกไบต์จากดิสก์ไปยัง RAM ได้โดยตรง ดังนั้นจึงไม่สามารถใช้ได้กับ IPC

ทั้งหน่วยความจำและท่อที่ใช้ร่วมกันได้รับการสนับสนุนโดยตรงจาก OS ที่ทันสมัย ดังนั้นจึงค่อนข้างเร็ว โดยทั่วไปคิวจะเป็นนามธรรมเช่นติดตั้งที่ด้านบนของซ็อกเก็ตท่อและ / หรือหน่วยความจำที่ใช้ร่วมกัน สิ่งนี้อาจดูเหมือนกลไกที่ช้าลง แต่ทางเลือกคือคุณสร้างสิ่งที่เป็นนามธรรม


สำหรับ DMA ทำไมถึงเป็นเช่นนั้นฉันจึงสามารถอ่านสิ่งต่างๆมากมายที่เกี่ยวข้องกับ RDMA (เป็นการเข้าถึงหน่วยความจำโดยตรงจากระยะไกล) ที่จะใช้กับเครือข่าย (โดยเฉพาะกับ InfiniBand) และทำสิ่งเดียวกันนี้ ฉันพยายามที่จะบรรลุสิ่งที่เทียบเท่าโดยไม่มีเครือข่าย (เนื่องจากทั้งหมดอยู่ในกล่องเดียวกัน)
Bastien

RDMA เป็นแนวคิดเดียวกันคือการคัดลอกไบต์ข้ามเครือข่ายโดยไม่รบกวน CPU ทั้งสองด้าน มันยังไม่ทำงานในระดับกระบวนการ
MSalters

10

คำถามถูกถามเมื่อไม่นานมานี้ แต่คุณอาจสนใจhttps://github.com/peter-lawrey/Java-Chronicleซึ่งรองรับเวลาแฝงทั่วไปที่ 200 ns และปริมาณงาน 20 M ข้อความ / วินาที ใช้ไฟล์ที่แมปหน่วยความจำที่ใช้ร่วมกันระหว่างกระบวนการต่างๆ



6

หากคุณเคยพิจารณาใช้การเข้าถึงแบบเนทีฟ (เนื่องจากทั้งแอปพลิเคชันและ "เซิร์ฟเวอร์" ของคุณอยู่ในเครื่องเดียวกัน) ให้พิจารณาJNAซึ่งมีรหัสสำเร็จรูปน้อยกว่าสำหรับคุณที่จะจัดการ


6

การมาถึงช้า แต่ต้องการชี้ให้เห็นโครงการโอเพ่นซอร์สที่มีไว้สำหรับการวัดเวลาแฝงของ ping โดยใช้ Java NIO

สำรวจ / อธิบายเพิ่มเติมในบล็อกโพสต์นี้ ผลลัพธ์คือ (RTT ในหน่วยนาโน):

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

นี่เป็นไปตามแนวของคำตอบที่ยอมรับ ข้อผิดพลาด System.nanotime () (ประมาณโดยการวัดค่าอะไรเลย) วัดได้ที่ประมาณ 40 นาโนเมตรดังนั้นสำหรับ IPC ผลลัพธ์ที่แท้จริงอาจต่ำกว่า สนุก.


2

ฉันไม่รู้มากเกี่ยวกับการสื่อสารระหว่างกระบวนการแบบเนทีฟ แต่ฉันเดาว่าคุณต้องสื่อสารโดยใช้โค้ดเนทีฟซึ่งคุณสามารถเข้าถึงได้โดยใช้กลไก JNI ดังนั้นจาก Java คุณจะเรียกใช้ฟังก์ชันดั้งเดิมที่พูดถึงกระบวนการอื่น



0

คุณได้พิจารณาเปิดซ็อกเก็ตไว้เพื่อให้สามารถใช้การเชื่อมต่อซ้ำได้หรือไม่


ซ็อกเก็ตเปิดอยู่ การเชื่อมต่อยังมีชีวิตอยู่ตลอดเวลาที่แอปพลิเคชันทำงานอยู่ (ประมาณ 7 ชั่วโมง) ข้อความจะถูกแลกเปลี่ยนมากหรือน้อยอย่างต่อเนื่อง (สมมติว่าประมาณ 5 ถึง 10 ต่อวินาที) เวลาแฝงปัจจุบันอยู่ที่ประมาณ 200 ไมโครวินาทีเป้าหมายคือการโกน 1 หรือ 2 คำสั่งของขนาด
Bastien

เวลาแฝง 2 ms? ทะเยอทะยาน. เป็นไปได้ไหมที่จะเขียน C-stuff ไปยังไลบรารีที่ใช้ร่วมกันซึ่งคุณสามารถเชื่อมต่อกับ JNI ได้?
Thorbjørn Ravn Andersen

2ms คือ 2,000 ไมโครวินาทีไม่ใช่ 200 ทำให้ 2ms มีความทะเยอทะยานน้อยลง
thewhiteambit

-1

รายงานข้อบกพร่องของ Oracle เกี่ยวกับประสิทธิภาพของ JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI เป็นอินเทอร์เฟซที่ช้าดังนั้น Java TCP ซ็อกเก็ตจึงเป็นวิธีที่เร็วที่สุดสำหรับการแจ้งเตือนระหว่างแอปพลิเคชันอย่างไรก็ตามไม่ได้หมายความว่าคุณต้องส่งข้อมูลผ่านซ็อกเก็ต ใช้ LDMA เพื่อถ่ายโอนข้อมูล แต่อย่างที่คำถามก่อนหน้านี้ได้ชี้ให้เห็นการรองรับ Java สำหรับการแม็ปหน่วยความจำนั้นไม่เหมาะและคุณจะต้องใช้ไลบรารี JNI เพื่อเรียกใช้ mmap


3
ทำไม JNI ถึงช้า? พิจารณาว่าเลเยอร์ TCP ระดับต่ำใน Java ทำงานอย่างไรไม่ได้เขียนด้วย Java byte-code! (เช่นต้องมีช่องทางผ่านโฮสต์ดั้งเดิม) ดังนั้นฉันจึงปฏิเสธการยืนยันว่า Java TCP ซ็อกเก็ตเร็วกว่า JNI (อย่างไรก็ตาม JNI ไม่ใช่ IPC)

4
การโทร JNI ครั้งเดียวทำให้คุณเสียค่าใช้จ่าย 9ns (บน Intel i5) หากคุณใช้เฉพาะรุ่นดั้งเดิม จึงไม่ช้าขนาดนี้
Martin Kersten
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.