tail -f /some/log/file | grep thing1 | grep thing2जैसी पाइपलाइन, जहां धीरे-धीरे आने वाले output को कई commands से जोड़ा जाता है, असल में रुकी नहीं होती; बीच की command output को buffer में जमा कर रही होती है, इसलिए खाली दिख सकती हैgrepऔर कई programsisattyसे जांचते हैं कि stdout terminal है या नहीं; terminal होने पर line buffering, और pipe या file होने पर लगभग 8KB block buffering का उपयोग करते हैंtail,cat,teeऐसे उदाहरण हैं जो output buffering नहीं करते, लेकिनgrep --line-buffered,sed -u,tcpdump -l,jq -u,tr -uजैसे buffering कम करने के विकल्प command के हिसाब से अलग होते हैंCtrl-Cसे pipeline तोड़ने परtcpdumpजैसे program के buffer में मौजूद output गायब हो सकता है;kill -TERM $PIDसे बंद करने पर buffer flush होकर output दिख सकता है- व्यावहारिक समाधान हैं: जल्दी खत्म होने वाली command में बदलना,
grep --line-buffered, singleawkया जटिलgrep,stdbuf,unbufferआदि, लेकिन हर एक के काम करने की शर्तें और side effects जांचने चाहिए
पाइप अटका हुआ क्यों दिखता है
- जब log file में lines धीरे-धीरे जुड़ती हैं, तो नीचे दी गई pipeline में matching result होने पर भी output न दिख सकता है
tail -f /some/log/file | grep thing1 | grep thing2
- वजह pipe खुद नहीं, बल्कि बीच में मौजूद
grep thing1है, जो result तुरंत लिखने के बजाय buffer में store करता है - अगर program हर बार तुरंत लिखे, तो system calls बढ़ जाते हैं; इसलिए performance के लिए वह कुछ data इकट्ठा करने के बाद pipe या file में लिखता है
- इस उदाहरण में
grep thing1लगभग 8KB output जमा होने तक इंतजार कर सकता है, और धीमे log में यह condition practically कभी पूरी नहीं हो सकती
terminal और pipe में output का तरीका अलग होता है
tail -f file | grep thingठीक काम करता है, लेकिन उसके बाद दूसराgrepजोड़ने पर output रुका हुआ लग सकता हैgrepऔर कई programsisattyfunction से जांचते हैं कि stdout terminal है या नहीं- stdout terminal हो तो line buffering इस्तेमाल होती है और line-by-line तुरंत output आता है
- stdout pipe या file हो तो block buffering इस्तेमाल होती है और एक निश्चित size से ज्यादा data जमा होने पर output आता है
- इसलिए अगर
grepसीधे terminal पर लिखता है तो line तुरंत दिखती है, लेकिन अगली command से जुड़े pipe में लिखने पर दिख नहीं सकती - buffer size program के हिसाब से अलग होता है
grepमें libc buffering संभालता है, और libc का sizeBUFSIZvariable से define होता है- glibc में इसकी definition stdio.h में है
- terminal पर लिखते समय 8KB output buffer न इस्तेमाल करना कोई भौतिक नियम नहीं है; program चाहे तो ऐसा implement कर सकता है, लेकिन वह बेहद अजीब behavior के करीब होगा
हर command का buffering behavior अलग होता है
- output buffering मुश्किल इसलिए है क्योंकि user को याद रखना पड़ता है कि कौन-सी command pipe output में buffering करती है
- output buffering न करने वाली commands के उदाहरण ये हैं
tailcattee
- pipe में लिखते समय output buffering करने वाली आम commands और उसे कम करने के तरीके ये हैं
grep:--line-bufferedsed:-uawk:fflush()functiontcpdump:-ljq:-utr:-ucut: buffering disable नहीं की जा सकती
sortजैसी commands, जिन्हें काम करने के लिए पूरा input मिलने के बाद ही आगे बढ़ना होता है, उनमें buffering practically मायने नहीं रखती- Mac OS और GNU versions दोनों test करने की कोशिश की गई, लेकिन variants बहुत हैं, इसलिए कुछ गलतियां हो सकती हैं
programming languages का default output भी buffer होता है
- कुछ programming languages का default
printoutput भी pipe में लिखते समय buffer होता है - language के हिसाब से disable करने के तरीके ये हैं
- C:
setvbuf - Python:
python -u,PYTHONUNBUFFERED=1,sys.stdout.reconfigure(line_buffering=False),print(x, flush=True) - Ruby:
STDOUT.sync = true - Perl:
$| = 1
- C:
- यह default behavior batch processing में default output functions को तेज बनाने के लिए design किया गया लगता है
- output के तरीके के हिसाब से buffering बदल सकती है
- C++ में
cout << "hello\n"pipe में लिखते समय buffer होता है cout << "hello" << endloutput को flush करता है
- C++ में
Ctrl-C और file redirection में फर्क
- अगर नीचे की तरह
tcpdumpoutput कोgrepसे जोड़ते समय-lछूट जाए, तो output buffer में रह सकता हैsudo tcpdump -ni any port 53 | grep example.com
- आदर्श रूप से उम्मीद की जा सकती है कि
Ctrl-Cदबाने परtcpdumpbuffer flush करेगा औरgrepsearch करके छूटा हुआ output दिखा देगा - असल में programs बंद होते समय
tcpdumpbuffer में मौजूद output lost हो जाता है straceसे जांचने पर दिखा किgrepकोtcpdumpसे पहलेSIGINTमिलता है, इसलिए अगरtcpdumpflush करने की कोशिश भी करे, तोgrepपहले ही खत्म हो चुका हो सकता है- workaround के तौर पर
tcpdumpका PID ढूंढकरkill -TERM $PIDचलाने सेtcpdumpbuffer flush कर सकता है और output दिख सकता है - file redirection भी buffering करता है
sudo tcpdump -ni any port 53 > output.txt
- हालांकि file redirection में,
Ctrl-Cसे buffer content पूरी तरह उड़ जाने वाली समस्या के विपरीत, experience में program बंद होने से पहले buffer content अक्सर file में लिख जाता है - यह behavior हमेशा भरोसेमंद है या नहीं, यह साफ नहीं है
buffering से बचने के पांच तरीके
-
जल्दी खत्म होने वाले program में बदलना
- धीरे-धीरे pipe में लिखने वाली स्थिति से बचकर, जल्दी खत्म होने वाली command में बदला जा सकता है
- उदाहरण इस तरह है
cat /some/log/file | grep thing1 | grep thing2 | tail
- यह original
tail -fcommand जैसा behavior नहीं है, लेकिन जटिल buffering problem से बच सकता है
-
grepका line buffer option इस्तेमाल करनाgrepमें buffering से बचने के लिए flag है- उदाहरण इस तरह है
tail -f /some/log/file | grep --line-buffered thing1 | grep thing2
-
awkया ज्यादा जटिलgrepमें मिलाना- कई
grepइस्तेमाल करने वाली स्थिति को singleawkमें बदला जा सकता हैtail -f /some/log/file | awk '/thing1/ && /thing2/'
- या ज्यादा जटिल regular expression वाले
grepसे लिखा जा सकता हैtail -f /some/log/file | grep -E 'thing1.*thing2'
awkभी buffering करता है, इसलिए यह तरीका काम करे, इसके लिएawkpipeline की last command होना चाहिए
- कई
-
stdbufइस्तेमाल करनाstdbufLD_PRELOADका इस्तेमाल करके libc की buffering बंद करता है- output buffering बंद करने का उदाहरण यह है
tail -f /some/log/file | stdbuf -o0 grep thing1 | grep thing2
LD_PRELOADbased solutions की तरह reliability सीमित है- static binary में काम नहीं करता
- अगर program libc buffering इस्तेमाल नहीं करता, तो काम न कर सकता है
- Mac OS पर हमेशा काम नहीं करता
- संबंधित explanation के लिए Harry Marr का How stdbuf works है
-
unbufferइस्तेमाल करनाunbuffer programprogram output को TTY जैसा होने के लिए force करता है, ताकि वह सामान्य TTY की तरह कम buffering करे और color output आदि इस्तेमाल करे- उदाहरण इस तरह है
tail -f /some/log/file | unbuffer grep thing1 | grep thing2
stdbufके विपरीत यह हमेशा काम करता है, लेकिन unwanted side effects हो सकते हैं- जैसे
grep thing1matching results में color जोड़ सकता है
- जैसे
unbufferexpectpackage में आता है
जहां समस्या आम तौर पर दिखती है और environment variable idea
- यह समस्या मुख्य रूप से उन programs में दिखती है जो pipe में data धीरे-धीरे भेजते हैं
- उदाहरण ये हैं
tcpdumptail -fkubectl logsजैसे log watching तरीके- धीमे calculation का output
- Python के
PYTHONUNBUFFEREDकी तरह buffering बंद करने के लिए कोई standard environment variable हो तो अच्छा हो सकता है - यह idea Mark Dominus के 2018 के blog post और follow-up post से मिला
- नाम के उदाहरण के तौर पर
NO_COLORकी तरहNO_BUFFERहो सकता है - design मुश्किल है
- NETBSD में ज्यादा control देने वाले
STDBUF,STDBUF1जैसे environment variables हैं - ज्यादातर developers शायद relatively छोटे edge case के लिए कई environment variables implement नहीं करना चाहेंगे
- NETBSD में ज्यादा control देने वाले
- यह भी जिज्ञासा है कि क्या ऐसे programs हैं जो output buffer को 1 second जैसे interval पर automatically flush करते हैं, लेकिन ऐसा कोई program याद नहीं आता और इसके नुकसान हो सकते हैं
जिन बातों को शामिल नहीं किया गया
- line buffering और completely unbuffered output का फर्क शामिल नहीं है
- stderr buffering और stdout buffering का फर्क शामिल नहीं है
- यह सामग्री सिर्फ program के अंदर होने वाली buffering पर चर्चा करती है
- operating system का TTY driver भी कभी-कभी थोड़ी buffering करता है
- pipe में लिखने की स्थिति के अलावा output flush करने के दूसरे कारण शामिल नहीं हैं
1 टिप्पणियां
Hacker News की राय
buffered access लगभग हमेशा byte threshold तक पहुँचने पर, या कम से कम 1 byte मौजूद होने पर कुछ समय बाद flush करने वाले “threshold या timeout” तरीके पर होना चाहिए
इसी तरह की समस्या सुलझाने के लिए hardware interface में यह एक आम तरीका है
इस स्थिति में user space में buffering करने वाली library को जब data पहली बार buffer में डाला जाए, तभी उपयुक्त timer सेट करना चाहिए। timeout value को argument के रूप में लिया जा सकता है, या इंसान को छोटा महसूस होने वाला लगभग 1~100ms रखा जा सकता है, या
{bandwidth / threshold}के अनुपात में रखा जा सकता है, या इस तरह तय किया जा सकता है कि system call overhead कुल समय के 0.1% से अधिक न होयह तरीका सिर्फ write पर नहीं, read पर भी लागू होता है। अगर batch read या merged read किया जा रहा है, तो ऐसा ही तरीका चाहिए, लेकिन इसके लिए data channel में “pending data” को efficiently query करने या notification पाने का तरीका होना चाहिए, इसलिए यह channel design पर ज़्यादा निर्भर करता है। hardware में interrupt coalescing जैसी तकनीक आम है
I/O error सिर्फ write के समय ही नहीं बल्कि किसी भी समय आ सकता है, और timer की वजह से कई system call रुक सकते हैं, न कि सिर्फ वहाँ जहाँ program ने खुद timer लगाया हो या जहाँ signal पहुँचा हो
अगर application और libc दोनों timer सेट करें तो भ्रम पैदा हो सकता है। आजकल kernel timer API शायद पहले की तुलना में बेहतर है, इसलिए यह बात कम प्रासंगिक हो सकती है, लेकिन अगर application किसी महत्वपूर्ण हिस्से में थोड़ी देर के लिए signal block कर दे तो I/O timer भी प्रभावित होगा
signal handling के समय और तरीके की वजह से I/O structure access को और सावधानी से करना होगा
io_uring और user space timer इस्तेमाल करें तो यह कहीं बेहतर scale करता है, लेकिन तेज़ छोटे writes को बड़ी संख्या में support करने के लिए फिर भी कुछ tricks चाहिए। उदाहरण के लिए, लगभग 10 लाख बार प्रति सेकंड से ऊपर जाते ही timer management की लागत दिखने लगती है, और 10 करोड़ writes प्रति सेकंड तक पहुँचने के लिए काफ़ी अजीब तकनीकों की ज़रूरत पड़ी थी
समस्या की जड़ यह है कि interactive होना चाहिए वाली चीज़ और ऐसे contract का मिश्रण हो गया है जिसमें interactivity मानी ही नहीं गई। उदाहरण के लिए
tailके follow output को pipe में भेजनामुझे नहीं लगता कि यहाँ कोई वास्तविक समस्या है जिसे हल करना चाहिए। hardware analogy लें तो यह बारिश का पानी जमा करने वाली टंकी जैसा है, जिसे सिर्फ भर जाने पर ही आगे भेजा जाता है। पता नहीं कौन-सा उदाहरण ध्यान में रखा गया है, लेकिन मेरी जानकारी में hardware में time-based flush आम नहीं है
प्रस्तावित बदलाव contract को बहुत ज़्यादा जटिल बना देगा
समस्या semantics से ज़्यादा semantics के बारे में न जानने में है
मैंने NIX systems के साथ 20 साल से अधिक काम किया है, और मुझे पता है कि ऐसा होता है, फिर भी हर बार आउटपुट क्यों नहीं आ रहा यह समझते-समझते काफी देर puzzling करने के बाद ही याद आता है
“हाल की पोस्टें काफ़ी लंबी होती जा रही हैं, क्या buffering पर 3000 शब्दों का लेख सच में कोई पढ़ना चाहेगा” — इस हिस्से पर, व्यक्तिगत रूप से मैं तो पढ़ना चाहूँगा
कभी-कभी लंबा लेख search optimization के लिए अनावश्यक भराव भी हो सकता है
TLDR और NTLDR, यानी “लंबा था लेकिन पढ़ लिया” सेक्शन भी रखे जा सकते हैं
अच्छा होगा अगर पूरे system का CPU idle होते ही सभी buffers flush हो जाएँ
buffering मूलतः CPU बचाने की तकनीक है। अगर CPU असीमित हो, तो हर buffer 1 byte का होगा। buffer efficiency के लिए data इकट्ठा करके batch में process करने का तरीका है
लेकिन जब CPU idle हो जाए, तब “बाद में करने वाला काम” बचा नहीं होना चाहिए। जैसे ही kernel scheduler idle state में जाए, उसे सभी process को buffer flush करो वाला signal भेजना चाहिए
वह सारा काम करके फिर buffer flush के लिए system call भी करवाना पड़ेगा। शायद ऐसा mechanism जोड़ा जा सकता है जिसमें kernel को user space buffer का पता हो और idle होने पर वह वहीं से सीधे उठा ले
क्या यह कुछ हद तक io_uring https://man7.org/linux/man-pages/man3/io_uring_register_buff... जैसा है?
यह लेख unbuffered और line buffering — इन दो अलग चीज़ों को गड़बड़ा रहा है
unbuffered mode बेवजह performance खराब करता है, और अगर कई source एक ही pipe में लिख रहे हों तो गलत output पैदा कर सकता है। बहुत लंबी lines तो वैसे भी मिल जाएँगी, लेकिन वास्तविक दुनिया में ज़्यादातर output lines formatting/control characters और supplementary plane characters के साथ भी 4096 bytes से छोटी होती हैं
line buffering terminal का default है, और pipe में भी अक्सर यही desired behavior होता है। हर command को
stdbuf -oL -eLके तहत चलाने से यह हो जाता है। जो दुर्लभ programs line के बीच update चाहते हैं, उन्हें वैसे भी manual flush करना पड़ता है, इसलिए यहाँ भी वे सही चलेंगेstdbufअसल में यह करता है, ऐसा समझा जा सकता है:env -i \command -v stdbuf` -oL -eL `command -v env``मैंने पहले इस समस्या पर एक लेख लिखा था: https://world-playground-deceit.net/blog/2024/09/bourne_shel...
non-buffering command के बारे में यह implementation-dependent है, या
catके मामले में गलत भी हो सकता है। https://pubs.opengroup.org/onlinepubs/9799919799/utilities/c... और-uदेखें। POSIX ने इसे नियंत्रित करने का कोई आधिकारिक तरीका नहीं दिया, यह काफ़ी दर्दनाक हैएक और चीज़ जिसका ज़िक्र नहीं हुआ, वह है input buffering, और यह ऐसा अजीब नतीजा देती है:
$ seq 5 | { v1=$(head -1); v2=$(head -1); printf '%s=%s\n' v1 "$v1" v2 "$v2"; }v1=1v2=इस मामले में समाधान
stdbuf -i0 head -1का उपयोग करना हैsocketpairजैसी जगह से पढ़ने वाला process, लिखने वाले process पर ऐसी पाबंदियाँ लागू कर सकता है।ptrace()जैसे भारी-भरकम hack के मामले को छोड़करpipe buffer size को adjust करना शायद संभव हो, लेकिन standard C I/O को उसका पालन करना चाहिए, ऐसी कोई परंपरा मुझे नहीं पता
वैसे भी इस मामले में
stdbufमददगार नहीं लगता:$ ./a | stdbuf -i0 -- cat#include#includeint main(void) {for (;;) {printf("n");usleep(100000);}}buffering होने के अच्छे कारण हैं। स्क्रीन पर output दिखाना, buffer में लिखने की तुलना में काफ़ी ज़्यादा धीमा होता है
characters को एक-एक करके output करना बेहद अक्षम है
यह बहुत पुरानी समस्या है, और UART के साथ काम करते समय अक्सर सामने आती है। संभावित समाधान कई हैं: line-based तरीका, जिसमें newline जैसे special character से output के अंत को चिह्नित किया जाता है; length-based तरीका, जिसमें 8KB जैसी लंबाई पूरी होने तक इंतज़ार किया जाता है; और time-based तरीका, जिसमें हर X millisecond पर output किया जाता है
हर तरीके के अपने फ़ायदे और नुकसान हैं, और कौन-सा सबसे अच्छा है यह application पर निर्भर करता है। लेख में जहाँ कहा गया है कि कुछ programs buffering का उपयोग नहीं करते, मुझे वह गलत लगता है। वे programs बस साफ़ तौर पर length-based तरीका नहीं अपनाते
line-based approach ऐसा ही एक तरीका है, लेकिन इसमें इस बात पर सहमति चाहिए कि कौन-सा character इस्तेमाल होगा। आमतौर पर वह newline होता है
/dev/nullपर इतनी अधिक system calls करना भर भी performance को काफ़ी गिरा सकता हैमैं 35 साल से ज़्यादा समय से Unix इस्तेमाल कर रहा हूँ, लेकिन यह कैसे काम करता है इसे मैंने कभी पूरी तरह नहीं समझा
buffering के व्यवहार को कई systems और components में समग्र रूप से समझाने वाला यह विवरण अच्छा लगा, और मैंने इसमें से निश्चित रूप से कुछ सीखा
“pipe में Ctrl-C दबाने पर buffer की सामग्री गायब हो जाती है” वाले हिस्से पर, मेरा ख़याल है कि ज़्यादातर programs SIGINT पर buffer को flush करेंगे
लेकिन shell में ऐसा होने के लिए SIGINT केवल pipeline के पहले program को देना होगा, और शायद वास्तविक व्यवहार ऐसा नहीं है
sigintमिलता है और बाकी कोsigpipe