Linux pipe เร็วแค่ไหน? (2022)
(mazzo.li)- ติดตาม throughput ของ Linux pipe ด้วย
perfและการวิเคราะห์ path ใน kernel จาก implementation แบบง่ายที่ใช้write/readที่ 3.7GiB/s จนปรับขึ้นเป็น 62.5GiB/s ในท้ายที่สุด - จุดเริ่มต้นของคอขวดอยู่ที่ pipe ทำงานเป็น ring buffer ที่เก็บ reference ของหน้า 4KiB และต้องจ่ายต้นทุนทั้งการคัดลอก การจัดสรรหน้า lock และการรอ·ปลุก
vmspliceและspliceลดการคัดลอกระหว่างหน่วยความจำผู้ใช้กับ kernel buffer ทำให้ throughput เพิ่มขึ้นเป็นประมาณ 12.7GiB/s และ 32.8GiB/s ตามลำดับ- หลังจากนั้น ต้นทุนในการแปลงช่วง virtual address เป็น
struct pageเด่นชัดขึ้น และเมื่อใช้ 2MiB huge page ภาระการไล่ page table ลดลง จึงเพิ่มขึ้นเป็น 51.0GiB/s - การ optimize ขั้นสุดท้ายอย่าง
SPLICE_F_NONBLOCKและ busy loop ทำให้ throughput สูงขึ้นอีก แต่จะยึดครอง CPU core ตลอดเวลาระหว่างรอสถานะพร้อม
เป้าหมายการทดลองและ performance อ้างอิง
- เป้าหมายคือดูว่า Unix pipe บน Linux ถูก implement อย่างไร และค่อย ๆ optimize โปรแกรมทดสอบที่เขียนและอ่านข้อมูลผ่าน pipe ทีละขั้น
- สภาพแวดล้อมการทดลองคือ CPU Intel Skylake i7-8550U และ Linux 5.17 โดย implementation ภายในของ Linux เปลี่ยนแปลงอยู่เสมอ ดังนั้นตัวเลขอาจต่างกันตามสภาพแวดล้อม
- จุดเริ่มต้นคือโปรแกรมที่ใช้ system call
writeและreadเพื่อส่ง buffer ขนาด 256KiB ผ่าน pipe ซ้ำ ๆwriteเขียน buffer ขนาด 256KiB เดิมซ้ำไปเรื่อย ๆreadอ่านรวม 10GiB แล้วพิมพ์ throughput- โค้ดอยู่ที่
pipes-speed-test
- โปรแกรม FizzBuzz ที่ optimize อย่างมากซึ่งใช้เป็นตัวเปรียบเทียบ ให้ output ประมาณ 36.2GiB/s เมื่อวัดด้วย
pv- FizzBuzz ใช้ block ขนาด 256KiB ซึ่งเท่ากับขนาด L2 cache
- การทดลองนี้ก็ใช้ block 256KiB เช่นกัน แต่ไม่ได้คำนวณเนื้อหา output เพื่อดูค่าที่ใกล้ขีดจำกัดของ pipe IO
- ผลของ implementation แรกเป็นดังนี้
./write | ./read- 3.7GiB/s, buffer 256KiB, ทำซ้ำ 40960 ครั้ง, ส่งข้อมูลรวม 10GiB
เหตุผลที่ write และ read ช้า
- เมื่อตรวจด้วย
perf record -gและperf reportพบว่าเวลาฝั่งwriteประมาณ 47% ถูกใช้ในpipe_write - ภายใน
pipe_writeนั้นcopy_page_from_iterและ__alloc_pagesมีสัดส่วนสูง- การคัดลอกข้อมูล
- การจัดสรรหน้า
- scheduling
- การรอ·ปลุก
- การ acquire/release lock
- Linux pipe คือ ring buffer ที่เก็บ reference ของหน้า
pipe_inode_infoมีhead,tailและ arraypipe_bufferpipe_bufferมีstruct page *page,offset,len- ขนาดหน้าแบบปกติของ x86-64 คือ 4KiB
- จำนวน slot ของ ring buffer โดยค่าเริ่มต้นคือ 16 slot และ pipe 8 slot ในตัวอย่างสามารถเก็บได้สูงสุด 32KiB
headแทนปลายเขียน และtailแทนปลายอ่าน- ถ้า pipe เต็ม
writeจะถูก block - ถ้า pipe ว่าง
readจะถูก block
- ถ้า pipe เต็ม
- โดยทั่วไป
pipe_writeทำงานตามลำดับดังนี้- ถ้า pipe เต็มอยู่แล้ว ให้รอจนกว่าจะมีพื้นที่ว่าง
- ถ้า buffer ที่
headปัจจุบันชี้อยู่ยังมีพื้นที่ ให้เติมก่อน - ถ้ามี slot ว่างและยังมีข้อมูลเหลือ ให้จัดสรรหน้าใหม่ เติมข้อมูล และอัปเดต
head
pipe_readทำงานกลับกัน คือ consume หน้า, free หน้าที่อ่านหมดแล้ว และอัปเดตtail- ใน path นี้ แต่ละหน้าถูกคัดลอกสองครั้ง
- ครั้งหนึ่งจากหน่วยความจำผู้ใช้เข้า kernel
- อีกครั้งจาก kernel กลับไปยังหน่วยความจำผู้ใช้
- การคัดลอกเกิดเป็นหน่วยหน้า 4KiB และมีการ sync กับการจัดสรร·free หน้าแทรกอยู่ระหว่างทาง
- บนเครื่องเดียวกัน การอ่าน RAM แบบ sequential ด้วย thread เดียวได้ประมาณ 16GiB/s และเมื่อคำนึงถึงความซับซ้อนของ path ของ pipe แล้ว การที่
write/readช้ากว่าประมาณ 4 เท่าก็ไม่น่าแปลกใจ
ลดการคัดลอกด้วย vmsplice และ splice
- ใน IO ความเร็วสูง ต้นทุนการคัดลอก ระหว่างหน่วยความจำผู้ใช้กับ kernel buffer อาจกลายเป็นคอขวดได้
- Linux มี system call สำหรับการย้ายข้อมูลแบบไม่คัดลอกที่เกี่ยวข้องกับ pipe
vmspliceระบุ buffer ที่จะใส่เข้า pipe ด้วย array ของstruct iovec- ค่าที่ return คือจำนวน byte ที่เข้า pipe จริง
- เนื่องจากข้อจำกัดของขนาด ring buffer ของ pipe ขนาดที่ขอทั้งหมดอาจไม่เข้าไปในครั้งเดียว
vmspliceเชื่อมหน่วยความจำผู้ใช้เข้ากับ pipe โดยไม่คัดลอก ดังนั้นต้องระวังอย่า reuse buffer ก่อนที่ฝั่งอ่านจะ consume ข้อมูลนั้น- โปรแกรม FizzBuzz ใช้ double buffering เพื่อเรื่องนี้
- แบ่ง buffer 256KiB เป็นสองครึ่ง ครึ่งละ 128KiB
- ตั้งขนาด pipe เป็น 128KiB เพื่อให้มี 32 slot เมื่อคิดตามหน้า 4KiB
- เติม buffer สองครึ่งสลับกัน แล้วใส่เข้า pipe ด้วย
vmsplice
- โปรแกรมทดสอบนี้ไม่ได้เขียนเนื้อหา buffer ซ้ำจริง ๆ แต่ยังคงโครงสร้าง double buffering ไว้คล้ายกับที่โปรแกรมสร้าง output จริงต้องใช้
- เมื่อเปลี่ยน
writeเป็นvmsplicethroughput เพิ่มเป็น 12.7GiB/s- ปริมาณการคัดลอกลดลงครึ่งหนึ่ง
- ดีขึ้นมากกว่า 3 เท่าเมื่อเทียบกับ
write/read
- เมื่อเปลี่ยนฝั่งอ่านเป็น
spliceด้วย จะตัดการคัดลอกทั้งหมดออกและเพิ่มขึ้นถึง 32.8GiB/s - อย่างไรก็ตาม ต้องระวังเรื่องความปลอดภัยของ
vmsplice- ถ้าหน้าถูก splice อีกครั้ง อายุการใช้งานอาจยืดออกไป
- ยังไม่ชัดเจนว่าปลอดภัยหรือไม่แม้ไม่มี
SPLICE_F_GIFT - การใช้ pipe แบบ zero-copy อย่างปลอดภัยต้องระวังเป็นพิเศษ
iov_iter_get_pages และต้นทุนการแปลงหน้า
- หลังใช้
vmspliceและspliceแล้ว ในperfเวลาของ pathvmspliceดูสูงขึ้นอย่างชัดเจนiov_iter_get_pages__mutex_lock.constprop.0add_to_pipe
iov_iter_get_pagesแปลง ช่วง virtual memory ของstruct iovecที่ส่งให้vmspliceเป็นรายการstruct pageที่ pipe สามารถเก็บได้- process ใช้ virtual address ไม่ใช่ physical memory address
- CPU แปลง virtual address เป็น physical address ผ่าน page table
- ขนาดหน้าปกติของ x86-64 คือ 4KiB
- page table ของ x86-64 อธิบายได้ว่าเป็นโครงสร้าง tree 4 ระดับ โดยแต่ละระดับเป็น 512-way
pipe_bufferของ pipe อ้างอิงstruct pagestruct pageเป็นโครงสร้างหลักที่ kernel ใช้จัดการ physical page และ metadata ที่เกี่ยวข้อง- ดังนั้น
vmspliceต้องแปลงช่วง virtual memory ที่ป้อนเข้ามาเป็น reference ของ physical page
- เวลาภายใน
iov_iter_get_pagesส่วนใหญ่ถูกใช้ในget_user_pages_fast- สำหรับ buffer 128KiB และหน้า 4KiB ต้องได้หน้าทั้งหมด 32 หน้า
- ด้วย implementation ของโค้ด pipe จะเรียกครั้งละ
nr_pages = 16และทำซ้ำหากจำเป็น แต่จำนวนหน้า spliced ทั้งหมดคือ 32 หน้า
get_user_pages_fastเดิน page table ด้วย software คล้ายกับที่ CPU ทำ เพื่อรวบรวมstruct page- ต้องเพิ่ม reference count ของ
struct pageเพื่อไม่ให้ physical page ถูก reuse ระหว่างที่ caller ใช้หน้าอยู่- หลังจากนั้นต้องลด reference count ด้วย
put_page
- หลังจากนั้นต้องลด reference count ด้วย
- ถ้าเติม buffer ด้วย
memsetตอนเริ่มต้น page table entry จะถูกสร้างไว้ล่วงหน้า จึงหลีกเลี่ยง slow path ของget_user_pages_fastได้- ถ้าไม่ทำ
memsetในตัวอย่าง throughput ลดลงเป็น 25.0GiB/s - เมื่อใช้ huge page ปรากฏการณ์นี้ไม่ได้เกิดขึ้นเหมือนกัน
- ถ้าไม่ทำ
ลดต้นทุนการจัดการหน้าด้วย huge page
- x86-64 รองรับ 2MiB และ 1GiB huge page นอกเหนือจากหน้าปกติ 4KiB
- การทดลองต่อจากนี้ครอบคลุมเฉพาะ 2MiB huge page
- 1GiB page ค่อนข้างหายากและถือว่าเกินจำเป็นสำหรับงานนี้
- huge page ลดต้นทุนการจัดการด้วยการแทนช่วงหน่วยความจำเดียวกันด้วยจำนวนหน้าที่น้อยกว่า
- เมื่อแปลง virtual address เป็น physical address ขั้นของ page table ก็ลดลงหนึ่งขั้นด้วย
- อาจช่วยลดภาระ TLB ของ CPU ได้ด้วย
- คอขวดโดยตรงของการทดลองนี้ไม่ใช่ hardware page table walk แต่เป็น path ฝั่ง software ของ kernel อย่าง
get_user_pages_fast - บน Linux สามารถขอใช้ huge page ได้โดยจัดสรรหน่วยความจำที่จัดแนวกับ 2MiB แล้วเรียก
madvise(..., MADV_HUGEPAGE) - เมื่อนำ huge page มาใช้ throughput เพิ่มเป็น 51.0GiB/s
- เหตุผลของ performance ที่ดีขึ้นไม่ใช่เพราะ
struct pageชี้ไปยังหน้า 2MiB หนึ่งหน้าโดยตรง- โค้ด kernel โดยทั่วไปสมมติว่า
struct pageชี้ไปยังขนาดหน้ามาตรฐานของ architecture ปัจจุบัน - huge page ถูกแทนด้วย head
struct pageและ tailstruct pageหลายตัว - 2MiB huge page อาจถูกแทนด้วย
struct pageได้สูงสุด 512 ตัวเมื่อคิดตาม 4KiB
- โค้ด kernel โดยทั่วไปสมมติว่า
- แต่หลังจากหา entry แรกได้แล้ว สามารถสร้าง
struct pageตัวถัด ๆ ไปด้วย loop ง่าย ๆ ได้ จึงลดต้นทุนการไล่ page table หลายครั้ง - Kernel ตั้งแต่ Linux 5.17 เป็นต้นมามี
struct folioที่ระบุ head page อย่างชัดเจน ช่วยลดความจำเป็นในการตรวจ head/tail ตอน runtime
ลดต้นทุนการ sync ด้วย busy loop
- หลังใช้ huge page แล้ว ใน
perfเวลาในwait_for_spaceและ__wake_up_common_lockเด่นชัดขึ้น- ต้นทุนการรอพื้นที่ที่เขียนได้
- ต้นทุนการปลุกฝั่งอ่าน
- หากต้องการเลี่ยงต้นทุนการ sync นี้ สามารถใช้
SPLICE_F_NONBLOCKกับvmspliceและspliceได้- ถ้าเขียนเข้า pipe ไม่ได้ จะ return
EAGAINทันที - caller จะวน busy loop จนกว่าจะพร้อม
- ถ้าเขียนเข้า pipe ไม่ได้ จะ return
- เมื่อใช้ busy loop throughput เพิ่มขึ้นถึง 62.5GiB/s
- ต้นทุนก็ชัดเจนเช่นกัน
- ระหว่างรอให้
vmspliceหรือspliceพร้อม จะยึดครอง CPU core เต็ม ๆ - เป็นวิธีแลกการใช้ CPU มากขึ้นเพื่อให้ได้ latency หรือ throughput
- ระหว่างรอให้
- สุดท้าย synthetic benchmark นี้ถูกปรับปรุงจากประมาณ 3.5GiB/s เป็นประมาณ 65GiB/s
รายละเอียดที่เหลือและประเด็นเชิงปฏิบัติ
- กระบวนการ optimize ดำเนินไปโดยดู output ของ
perfควบคู่กับ source code ของ Linux - หัวข้อที่กล่าวถึงเชื่อมโยงกับประเด็น high-performance programming ที่กว้างกว่า pipe และ splicing เอง
-
การทำงานแบบไม่คัดลอก
- ring buffer
- paging และ virtual memory
- synchronization overhead
- ในโค้ดจริง มีการจัดสรร buffer สองตัวแยกกันเพื่อลดการแข่งขันบน page table
get_user_pagesเพิ่ม reference count ของ page table entry และput_pageลดลง- ถ้า buffer สองตัวใช้ page table entry คนละตัว การแข่งขันในการแก้ reference count จะลดลง
- การทดสอบรันโดยใช้
tasksetpin process./writeและ./readไว้กับสอง core - repository ยังมี synthetic benchmark สำหรับ
get_user_pages_fastด้วย - สามารถวัดความต่างของความเร็วตามการใช้หรือไม่ใช้ huge page ได้
- splicing ยังคงเป็นแนวคิดที่คลุมเครือและอันตราย และ issue ที่เกี่ยวข้องยังเป็นภาระต่อเนื่องสำหรับ kernel developer
-
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ถ้าผมเข้าใจถูก
vmspliceเมื่อใช้ทั้งฝั่งอ่านและฝั่งเขียนพร้อมกัน จะดูใกล้เคียงกับ กลไก shared memory ขนาดเล็ก ระหว่างสองโปรเซสกล่าวคือทั้งสองโปรเซสต้องระมัดระวังอย่างยิ่งว่าเมื่อไรจะอ่านและเขียนบัฟเฟอร์ และหลังใช้แล้วจะคืนอย่างไร เป็นวิธีที่เร็วแต่ก็น่ากลัวในเวลาเดียวกัน และน่าเสียดายที่ implementation แบบซื่อ ๆ ที่ใคร ๆ ก็น่าจะเขียนนั้น ช้ากว่า ประสิทธิภาพที่เป็นไปได้ 20 เท่า
vmspliceเป็นกลไก shared memory ขนาดเล็กระหว่างสองโปรเซส มันรองรับแค่ zero-copy จากหน่วยความจำของผู้ใช้ไปยัง pipe ส่วนทิศทางตรงกันข้ามยังเกิดการ copyดูรายละเอียดที่ https://mazzo.li/posts/fast-pipes.html#fn10
สงสัยว่ามี ไลบรารีประมวลผลข้อมูล ที่ abstract pipe, socket, file และ memory พร้อมทำ optimization แบบนี้ให้ด้วยหรือไม่
อยากรู้ว่ามีไลบรารีแบบนั้นใน C, C++, Rust หรือภาษา systems อื่น ๆ ไหม เพราะไม่คุ้นกับ API อย่าง
splice()และvmsplice()ที่กล่าวถึงในบทความ เลยสงสัยว่าเวลาสร้างแอปพลิเคชันระดับล่าง จะมีไลบรารีที่ใช้ optimization เหล่านี้ให้อัตโนมัติเมื่อทำได้หรือเปล่า ยังสงสัยด้วยว่าlibuv,tokio,Nettyจัดการสิ่งนี้ให้อัตโนมัติบน Linux หรือไม่ จากที่ค้นคร่าว ๆ ดูเหมือนว่าอาจจะเป็นไปได้โค้ดระดับสูงใช้ความสามารถแบบนี้ค่อนข้างน้อย เพราะมันมีวัตถุประสงค์ค่อนข้างเฉพาะ และต้องทำให้เฉพาะทางกับ Linux ถ้าแค่ย้ายข้อมูลบน Linux โดยไม่ต้องมองเข้าไปในข้อมูล
spliceก็มีประโยชน์ แอปอย่าง TCP/UDP proxy ต้องการมันแน่นอน แต่ไม่ค่อยเหมาะกับ HTTP server ธรรมดา ถ้ากำลังสร้างแอปแบบนี้ คุณจะเจอคีย์เวิร์ดอย่าง zero copy บ่อย ๆ และspliceจะเป็นหนึ่งในผลลัพธ์แรก ๆ ที่เห็นtokioอยู่ ไม่ใช่อัตโนมัติ แต่อาจน่าสนใจ: https://lib.rs/crates/tokio-spliceดูเหมือนจะไม่ค่อยเป็นที่รู้จักนอกวงการวิจัย และการเขียน implementation ของ Cosh ที่มีประสิทธิภาพน่าจะใช้เวลาพอสมควร สรุปคือโหมดการส่งมีสามแบบคือ move, share, copy ตัวอย่างเช่น การส่งแบบ move คือการส่งมอบข้อมูลที่ผู้ส่งมีสิทธิ์อ่าน/เขียนไปให้ผู้รับโดยสมบูรณ์ และอาจ implement ได้ด้วยการ remap หน่วยความจำเสมือนใน page table นอกจากนี้ยังมีคุณสมบัติ strong/weak ที่ระบุว่าผู้ส่งและผู้รับเชื่อใจได้ว่าร่วมมือกันหรือไม่ หรือต้องแยกจากกันอย่างเข้มงวดด้วยการ remap สิทธิ์หน่วยความจำเสมือน พูดตรง ๆ ผมไม่แน่ใจว่ามันจะ optimize ได้ดีพอจนเทียบกับสิ่งอย่าง pipe ที่ optimize สุดขีดได้อย่างน่าเชื่อถือหรือไม่ และอาจกลายเป็นปัญหาแบบ “คอมไพเลอร์ที่ฉลาดพอ” ก็ได้ แต่ก็คิดว่าคุ้มค่าที่จะลอง
[1] https://barrelfish.org/publications/trios14-baumann-cosh.pdf
การอภิปรายก่อนปี 2022: https://news.ycombinator.com/item?id=31592934
ข้อเท็จจริงที่น่าประหลาดใจซึ่งบังเอิญได้รู้เมื่อ 4 ปีก่อนคือ การใช้ pipe ของ Linux อาจทำให้เกิด พฤติกรรมแบบไม่กำหนดแน่นอน ได้
https://www.gibney.org/the_output_of_linux_pipes_can_be_inde...
echoออกมาจริง ๆ เลย(echo red; echo green 1>&2) | echo blueสร้าง subshell สองตัวโดยมีสัญลักษณ์|คั่นกลาง subshell เป็นโปรเซสลูกของ shell ปัจจุบัน จึงสืบทอดคุณสมบัติสำคัญอย่างตาราง file descriptor ที่เปิดอยู่ subshell ทั้งสองทำงานพร้อมกัน และ shell แม่ก็แค่wait()รอให้โปรเซสลูกทั้งหมดจบเท่านั้น โดยทั่วไปคาดเดาไม่ได้ว่าโปรเซสลูกตัวไหนจะทำงานก่อน และบนระบบ multicore ก็อาจทำงานพร้อมกันจริง ๆ ได้ standard output ของ subshell ฝั่งซ้ายเชื่อมกับปลายเขียนของ pipe ส่วน standard input ของ subshell ฝั่งขวาเชื่อมกับปลายอ่าน แต่echo blueไม่อ่าน input และทำแค่ output ดังนั้นจึงไม่มีอะไรถูกอ่านจาก pipe เลยecho green >&2ส่ง standard output ไปยังปลายทางที่ standard error ชี้อยู่ ไม่ใช่ pipe สุดท้ายecho greenกับecho blueจึงเขียนไปยังไฟล์เดียวกัน ซึ่งน่าจะเป็น terminal โดยตรง ทำให้เกิด race condition และลำดับจะต่างกันตามว่าตัวไหนถูก schedule ก่อนถ้าไม่เป็นแบบนั้น pipeline ก็ไม่มีประโยชน์ เช่น ใน pipeline ที่ดาวน์โหลดไฟล์ tar ด้วย
curlแล้วแตกไฟล์ทันที หากต้องรอให้curlจบก่อนแล้วค่อยรันtarก็จะเกิดปัญหาอย่างต้องเอาไฟล์ tar ชั่วคราวขนาดใหญ่ไปเก็บไว้ที่ไหนtarต้องทำงานไปพร้อมกับตอนที่curlกำลังรันอยู่ เพื่อให้ buffer เล็กและทำงานได้เร็ว control flow เพียงอย่างเดียวระหว่างโปรแกรมใน pipeline คือผ่าน standard input และ standard output ในตัวอย่างนี้เขียนไปที่ standard error ดังนั้นแน่นอนว่าไม่ได้อยู่ใน control flow ที่กำหนดแน่นอนเสริมอีกนิด เพื่อหลีกเลี่ยงความสับสน “Indeterministic” เป็นศัพท์ทางปรัชญา ส่วนศัพท์วิทยาการคอมพิวเตอร์คือ “nondeterministic”
0. https://blog.superpat.com/zero-copy-in-linux-with-sendfile-a...
คำสั่งนี้น่าจะถูกทำให้ดูประหลาดโดยตั้งใจ และถ้าเป็น code reviewer ก็คงต้องเอียงคอสงสัยแน่ ๆ มี
echo redแต่ไม่ได้ถูกส่งต่อไปที่ไหนเลย อาจเป็นมุก “red herring” ก็ได้echo greenไปที่ standard error จึงจะเห็นได้ก็ต่อเมื่อมันจบก่อนecho blueเท่านั้น ลำดับที่แน่นอนขึ้นกับ output buffering ซึ่งขึ้นกับว่า time slice ไหนถูกจัดสรรก่อน และเปลี่ยนไปตามจำนวน CPU กับโหลด ดังนั้นจึงไม่กำหนดแน่นอน แต่เป็นแบบเดียวกับที่topไม่กำหนดแน่นอนสรุปคือ หากสมมติว่าโปรแกรมทั้งสองถูกเขียนมาให้เหมาะสมที่สุดเท่าที่เป็นไปได้ ความเร็วสูงสุดของ pipe จะใกล้เคียงกับความเร็วที่คอร์หนึ่งของระบบสามารถอ่านและเขียนได้
โดยพื้นฐานแล้ว kernel จะ map หน้า physical memory เดียวกันจาก standard output ของโปรแกรมหนึ่งไปยัง standard input ของอีกโปรแกรมหนึ่ง ดังนั้นงานจึงเป็น zero-copy หรือในสถานการณ์ที่เหมาะสมน้อยกว่า ก็ใกล้เคียงกับการ copy ครั้งเดียวที่รวดเร็ว เมื่อรู้ข้อเท็จจริงนี้แล้ว การทำงานประสิทธิภาพสูงมากด้วย shell script ที่เชื่อมเครื่องมือสองตัวขึ้นไปด้วย pipe ก็ทั้งคุ้มค่าและชวนขำ มันเป็นหนึ่งในเครื่องมือที่มีประโยชน์ที่สุดในกล่องเครื่องมือ
spliceหรือvmspliceเท่านั้น system call เฉพาะ Linux เหล่านี้ใช้งานยาก โดยเฉพาะvmspliceโปรแกรมส่วนใหญ่และ shell filter ส่วนใหญ่ไม่ได้ใช้สิ่งนี้ ยกเว้นตัวอย่างเด่น ๆ อย่าง
pvจึงต้องจ่ายต้นทุนจากการ copy เข้า kernel memory แล้ว copy ออกมาอีกครั้งแอปพลิเคชันที่กำลังสร้างอยู่อาจถูกทำให้สะอาดกว่าได้ด้วย pipe+process หรือ green/user-space thread ขึ้นกับลักษณะโหลด อาจสะดวกน้อยกว่า แต่ message passing มักดีกว่า นรกของ deadlock
บทความนี้พูดถึงวิธีทำให้ pipe ของ Linux เร็วขึ้น แต่แนวทางอื่นอย่าง shared memory หรือ message queue ก็ยังอาจเร็วกว่าได้
ในระบบที่ต้องย้ายข้อมูลจำนวนมากอย่างรวดเร็ว ขั้นตอนเพิ่มเติมของ pipe อาจทำให้ช้าลงได้ แม้เวลาหลาย thread แชร์ข้อมูลกัน pipe ก็อาจสร้างปัญหามากกว่าวิธีอื่น ดังนั้นการปรับปรุงในบทความอาจไม่ได้ช่วยมากนักในสถานการณ์จริงที่ความเร็วสำคัญ
io_uringก็มีข้อดีแต่ในการสื่อสารสองทาง ไม่ว่าฝั่งไหนก็ต้องมีการแจ้งเตือนว่าข้อมูลพร้อมแล้ว อาจไม่อยากเผา CPU ด้วยการ polling และก็ไม่แน่ใจว่าทางเลือกเหล่านั้นจัดการ synchronization นั้นได้เร็วกว่า pipe อย่างไร
รู้อยู่แล้วเรื่องอย่าง page table แต่พอเชื่อมโยงเข้ากับ การวิเคราะห์ประสิทธิภาพ ผ่าน
perfก็เห็นชัดขึ้นว่ามันเป็นแกนกลางของ throughput แค่ไหนpipe ยอดเยี่ยมมาก จริง ๆ แล้วไม่ค่อยสำคัญเท่าไรว่าโปรเซสอื่นจะอยู่บน CPU อื่นหรืออยู่บนเครื่องอื่น
https://github.com/nathants/s4/blob/master/examples/nyc_taxi...
pipe เร็วพอสำหรับการประกอบ
cat,sed,awk,cut,grep,uniq,jqฯลฯ ซ้ำ ๆ