2 पॉइंट द्वारा GN⁺ 2024-10-03 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • उच्च contention की स्थिति में Mutex implementations के बीच का अंतर बहुत साफ़ दिखता है, और Cosmopolitan Libc का pthread_mutex_t Windows और Linux की प्रमुख implementations की तुलना में कम execution time और कम CPU उपयोग दिखाता है
  • Windows के 24-core Threadripper 29070WX टेस्ट में Cosmopolitan, Microsoft SRWLOCK से 2.75 गुना तेज़ था और CPU resources का 18 गुना कम उपयोग करता है
  • Linux के 96-core Threadripper Pro 7995WX पर यह glibc से 3 गुना और musl libc से 11 गुना तेज़ है, जबकि CPU time का अंतर इससे भी बड़ा है
  • MacOS M2 Ultra पर Apple Libc थोड़ा आगे है, और Cosmopolitan ARM environment में XNU के ulock system call पर निर्भर एक सरल algorithm इस्तेमाल करता है
  • performance की बुनियाद Google के nsync integration पर है, जिसमें CAS fast path, waiter queue, futex/ulock/WaitOnAddress(), starvation prevention और designated waker design मुख्य हैं

Contention Mutex benchmark का तरीका

  • टेस्ट में 30 threads बनाए जाते हैं, और हर thread एक ही global integer g_chores को 100,000 बार increment करता है
  • हर increment operation pthread_mutex_lock() और pthread_mutex_unlock() के बीच के बहुत छोटे critical section में चलाया जाता है
  • माप microseconds में लिए जाते हैं और तीन तरह के time अलग किए जाते हैं
    • wall time: program चलने का वास्तविक समय, जिसमें thread creation और join overhead शामिल है
    • user time: user space में खर्च हुआ CPU time
    • system time: kernel में खर्च हुआ CPU time
  • क्योंकि कई threads parallel में चलते हैं, इसलिए user time और system time का योग wall time से बड़ा हो सकता है
  • non-contention स्थिति में implementations के बीच performance अंतर आम तौर पर छोटा होता है, लेकिन contention स्थिति में Mutex design का अंतर बहुत स्पष्ट हो जाता है

Windows: SRWLOCK से तेज़ Cosmopolitan

  • Windows टेस्ट 24-core Threadripper 29070WX पर किया गया
  • Mark Waterman का MutexShootout high-contention scenario में Windows के SRWLOCK को सबसे मज़बूत implementation मानता है
  • उसी स्थिति में Cosmopolitan pthread_mutex_t ने SRWLOCK से कम wall time और कम CPU उपयोग दर्ज किया
implementation wall time user time system time
Cosmopolitan pthread_mutex_t 148,940µs 328,125µs 62,500µs
Microsoft SRWLOCK 410,416µs 5,515,625µs 1,640,625µs
Microsoft CRITICAL_SECTION 949,187µs 7,937,500µs 5,078,125µs
MSVC 2022 std::mutex 991,750µs 12,156,250µs 4,031,250µs
spin lock 1,165,435µs 24,515,000µs 15,000µs
Cygwin pthread_mutex_t 9,780,803µs 1,937,000µs 6,156,000µs
  • Cosmopolitan Mutex, Microsoft SRWLOCK से 2.75 गुना तेज़ है, और CPU resources का 18 गुना कम उपयोग करता है
  • Windows पर POSIX implementation देने वाले Cygwin Mutex की तुलना में यह 65 गुना तेज़ है
  • इस use case में Cygwin Mutex ने spin lock से भी खराब परिणाम दिया

Linux: wall time से भी बड़ा CPU time अंतर

  • Linux टेस्ट 96-core Threadripper Pro 7995WX पर किया गया
implementation wall time user time system time
Cosmopolitan pthread_mutex_t 36,905µs 44,511µs 23,492µs
glibc pthread_mutex_t 101,353µs 150,706µs 2,724,851µs
spin lock 202,423µs 4,694,749µs 2,000µs
Musl libc pthread_mutex_t 411,013µs 2,167,898µs 9,926,850µs
  • Cosmopolitan Mutex, glibc से 3 गुना और musl libc से 11 गुना तेज़ है
  • CPU time के आधार पर यह glibc से 42 गुना और musl libc से 178 गुना कम उपयोग करता है
  • ऐसे workload में जहाँ सभी threads को serialized काम करना पड़ता है, Cosmopolitan htop में ऐसा दिख सकता है जैसे सिर्फ़ एक core active हो
  • वहीं glibc और musl libc उसी स्थिति में CPU usage को काफी भर सकते हैं, जिससे उसी server पर कई workloads चलाने पर दबाव बढ़ता है

MacOS: Apple Libc थोड़ा आगे

  • MacOS टेस्ट M2 Ultra पर किया गया
implementation wall time user time system time
Apple Libc 52,263µs 43,202µs 911,009µs
Cosmopolitan pthread_mutex_t 54,700µs 63,055µs 1,003,674µs
  • MacOS M2 ARM64 पर Apple Libc, Cosmopolitan Mutex से थोड़ा तेज़ है
  • Cosmopolitan का सामान्य Mutex implementation इस platform पर अच्छी तरह काम नहीं करता
  • MacOS ARM पर Cosmopolitan, Ulrich Drepper के Futexes Are Tricky पर आधारित एक सरल algorithm इस्तेमाल करता है
  • यह तरीका भारी processing का ज़्यादातर हिस्सा XNU के ulock system call को सौंप देता है, और नतीजतन Apple implementation के लगभग बराबर performance देता है

Performance की बुनियाद: nsync integration

  • Cosmopolitan Mutex performance का मुख्य कारण Google की nsync library का integration है
  • nsync GitHub पर 371 stars वाली library है, जिसे Google के Mike Burrows ने लिखा है
  • Cosmopolitan integration के दौरान ये काम किए गए
    • nsync के Mutex unlock function में लंबे समय से छिपा bug खोजकर ठीक किया गया
    • AARCH64 पर C11 atomic operations के साथ port करके contended nsync Mutex को upstream nsync से 30% तेज़ बनाया गया
    • futex जैसी system integrations को नए सिरे से लिखा गया ताकि runtime portability संभव हो सके
    • इसे POSIX thread cancellation के साथ सहज रूप से काम करने लायक बनाया गया

nsync कैसे काम करता है

  • nsync lock को जल्दी हासिल करने के लिए शुरुआत में तुरंत optimistic CAS(compare and swap) आज़माता है
  • lock न मिलने पर calling thread को waiters की doubly linked list में जोड़ दिया जाता है
    • हर waiter के पास अपने लिए एक अलग independent cache line में semaphore होता है
    • wait state में जाने के बाद thread मुख्य lock को दोबारा नहीं छूता
    • यह कई cores द्वारा एक ही cache line छूने पर होने वाले communication overhead को कम करने में महत्वपूर्ण है
    • संबंधित पृष्ठभूमि के लिए Ulrich Drepper का What Every Programmer Should Know About Memory जुड़ा है
  • nsync threads को सुलाने के लिए operating system के futex का उपयोग करता है
    • MacOS में futex को ulock कहा जाता है
    • Windows में WaitOnAddress() futex की भूमिका निभाता है
    • Cosmo द्वारा समर्थित OS में सिर्फ़ NetBSD में futex नहीं है, वहाँ POSIX semaphores kernel space में implement होते हैं और हर semaphore के लिए नया file descriptor चाहिए होता है
  • nsync long wait concept के ज़रिए starvation से बचता है
    • अगर कोई waiter 30 बार जगाया गया लेकिन हर बार अंदरूनी रूप से lock लेने में विफल रहा, तो lock में एक bit जोड़ दिया जाता है ताकि अभी तक प्रतीक्षा न करने वाले threads lock न ले सकें
    • इस bit के रहते नए आने वाले threads का शुरुआती CAS तब तक fail होता है जब तक queue कुछ हद तक खाली न हो जाए
  • छोटे critical section वाले contention use cases में designated waker concept performance बढ़ाता है
    • जब कोई thread जागकर lock लेने की कोशिश करता है, तो मुख्य lock में एक bit set कर दी जाती है
    • nsync में unlock function की ज़िम्मेदारी अगला waiting thread जगाने की होती है
    • इस bit की वजह से unlock कर रहा thread, अगर कोई thread पहले से जागा हुआ है, तो दूसरे waiter को जगाने की ज़रूरत नहीं होती
  • संबंधित source code cosmopolitan/third_party/nsync/mu.c और cosmopolitan/libc/intrin/pthread_mutex_lock.c में है

वास्तविक service और validation code

  • Cosmo Mutex का live demo http://ipv4.games/ server पर देखा जा सकता है
  • यह service 2-core GCE VM पर चलती है और अब तक अधिकतम 49,131,669 IP आकार के botnet DDoS को झेल चुकी है
  • nsync की वजह से SQL queries को background threads में भेजना और threads के बीच message passing वाली संरचना इस्तेमाल करना संभव हुआ
  • status metrics /statusz पर देखे जा सकते हैं
  • benchmark code gettimeofday() से wall time मापता है और getrusage() से user time और system time मापता है
  • अंत में g_chores == THREADS * ITERATIONS जाँचकर verify किया जाता है कि सभी increment operations पूरे हुए या नहीं

Spin lock को देखते समय सावधानियाँ

  • non-contention स्थिति में Mutex implementations के बीच का अंतर छोटा होता है, और कुछ lines के spin lock बेहतर लग सकते हैं
  • लेकिन spin lock का उपयोग तभी करना चाहिए जब सचमुच कोई दूसरा विकल्प न हो
  • kernel जैसे बेहद low-level constraints वाले स्थानों में, जहाँ अधिक जटिल तरीका अपनाना मुश्किल हो, यह उपयोगी हो सकता है
  • nsync lock की internal implementation details में भी spin lock इस्तेमाल हो सकता है
  • अगर lock performance को सिर्फ़ wall time से देखा जाए तो spin lock अच्छा लग सकता है, इसलिए getrusage() से CPU time भी साथ में देखना चाहिए

1 टिप्पणियां

 
GN⁺ 2024-10-03
Hacker News की राय
  • नए mutex implementations और comparisons हमेशा दिलचस्प होते हैं, लेकिन यह benchmark करने का तरीका मुझे पसंद नहीं आया। यह लगभग microbenchmark जैसा दिखता है
    तेज locks को सच में deploy करने वाले लोग आम तौर पर बहुत बड़े multi-threaded programs को मुख्य performance test के रूप में इस्तेमाल करते हैं। complex workloads में, जहाँ critical section की लंबाई, competing threads की संख्या और contention का स्तर बदलता रहता है, mutex को तेज या धीमा बनाने वाले factors अलग दिखते हैं
    संदर्भ के लिए, मैंने WebKit का fast lock लिखा था, lock implementations के लिए ParkingLot abstraction का आविष्कार किया था (जो Rust और Unreal Engine में भी इस्तेमाल होता है), और पहले Java के लिए fast locks पर research और paper भी किया था

    • desktop app बनाने वाले के तौर पर जोड़ूँ तो, दर्जनों threads वाली, अक्सर चलने वाली app में मैं low-contention case के performance numbers देखना चाहूँगा
      real-time audio programmer के तौर पर, पहले से locked न होने वाले mutex को acquire करने की लागत मेरे लिए ज़्यादा महत्वपूर्ण है। हमारी app में यही situation भारी मात्रा में होती है। इसी तरह, N threads के contention की बजाय, failed होने वाले try-lock operation की लागत भी जानना चाहूँगा
      Cosmopolitan open source है, इसलिए मैं खुद measure भी कर सकता हूँ, लेकिन फिर भी यह कमी खलती है
    • मुझे भी यही लगा। mutex कई तरह के होते हैं, और किसी खास workload में कोई एक बेहतर होता है। DistributedMutex और SharedMutex याद आते हैं (https://github.com/facebook/folly/blob/main/folly/synchroniz..., https://github.com/facebook/folly/blob/main/folly/SharedMute...)
      hashmap की तरह, एक ही hashmap का हर संभव workload में बेहतर होना दुर्लभ है
    • इस style का mutex Python 3.13 के PyMutex में भी इस्तेमाल होने वाला है। 3.13 से पहले के PyThread_type_lock की तुलना में PyMutex कितना तेज है, यह दिखाने वाले real benchmarks मौजूद हैं
    • यह निश्चित रूप से microbenchmark है, और आम performance को represent न कर पाने की संभावना ज़्यादा है। यह page operating system benchmarking practices के लिए अच्छा standard पेश करता है। हालांकि यह academic side के हिसाब से थोड़ा ज़्यादा है: https://gernot-heiser.org/benchmarking-crimes.html
    • वह specific benchmark उलटे undesirable behavior को favor कर सकता है, जैसे pathological unfairness। optimal scheduling यह होगी कि पहले thread के सारे increment operations चलाए जाएँ, फिर दूसरे thread के सारे, क्योंकि इससे processors के बीच traffic minimum हो जाता है
      ऐसा mutex जो lock acquisition fail होने पर fixed time (जैसे 100µs) के लिए सो जाता है, लगभग हमेशा काम को batches में इकट्ठा कर देता है और इस behavior के करीब पहुँच जाता है, इसलिए benchmark में “जीत” सकता है। लेकिन real applications में जरा-सा contention भी हो तो ऐसा mutex भयानक होता है
      मेरा मतलब यह नहीं कि यह mutex खराब है या pthread mutex अच्छा है; मतलब यह है कि वह microbenchmark ऐसी चीज़ नहीं measure करता जिससे real application performance predict हो सके
  • “Cosmopolitan Mutex अच्छा इसलिए है क्योंकि इसमें nsync नाम की library इस्तेमाल हुई है” वाले हिस्से पर, nsync के बारे में मैंने पहली बार सुना, लेकिन Mike Burrows ने Google का production mutex implementation भी लिखा था: https://github.com/abseil/abseil-cpp/blob/master/absl/synchr...
    इसलिए मुझे हैरानी है कि यह mutex implementation benchmark से क्यों छूट गया। और अगर macOS पर __ulock को delegate किया जा रहा है, तो लगता है कि libc++ की atomic library में मौजूद wait(), notify_one() member functions से ही यह और सरलता से हासिल किया जा सकता है
    पहले Rust के mutex implementation को improve करने से जुड़ा एक बड़ा thread भी था: https://github.com/rust-lang/rust/issues/93740#issuecomment-... दिलचस्प बात यह है कि लगभग सभी popular mutex implementations की internal workings पर विस्तार से चर्चा है

    • जब मैं AV में आया, तब Mike पहले से ही legend थे। एक legend यह था कि जब भी search engine को और तेज़ होना होता, वे आते, कुछ core functions फिर से लिखते और अपने पुराने काम पर लौट जाते
      यह सच भी हो सकता है, लेकिन मैं सीधे verify नहीं कर सकता। वे efficiency पर बहुत ज़ोर देने वाले बेहद smart engineer थे। हालांकि हम एक server को लंबे समय तक चलाए नहीं रखते थे
    • Burrows Burrows-Wheeler transform, Bigtable, Dapper, Chubby आदि में भी शामिल रहे थे
    • वह Rust thread आखिर में वहाँ पहुँचता तो है, लेकिन मूल रूप से Mara के काम के बारे में है, इसलिए जनवरी 2023 में आई उनकी book का भी ज़िक्र है
      current Rust mutex implementation इस साल की शुरुआत में आया, और Linux पर शायद बहुत अलग न हो, लेकिन Windows और Mac पर मेरी जानकारी में यह नया काम है
      फिर भी Mara ने अन्य implementations के internals जो समझाए हैं, वे अब भी दिलचस्प हैं, लेकिन अपनी situation में यह पुरानी जानकारी तो नहीं है, इसकी जाँच कर लेना बेहतर है
    • Abseil का mutex implementation benchmark से इसलिए छूटा होगा कि वह C नहीं, बल्कि C++ implementation है। सिर्फ अनुमान है
    • लगता है Mike Burrows को ACM award भी मिला है, और वहाँ उनकी photo भी दिखती है
      https://awards.acm.org/award-recipients/burrows_9434147
  • “यह अभी नई C लाइब्रेरी है इसलिए कुछ हिस्से खुरदरे हैं, लेकिन यह इतनी तेज़ी से बेहतर हो रही है कि इसे production में न इस्तेमाल करना पेशेवर ज़िम्मेदारी से बचना लगने लगा है” — यह वाक्य काफ़ी अजीब है। Cosmopolitan प्रोजेक्ट की मैं काफ़ी कद्र करता हूँ, लेकिन इस तरह के बढ़ा-चढ़ाकर किए गए श्रेष्ठता के दावे आम तौर पर काफ़ी खराब red flag होते हैं

    • मुझे लगता है Justine के दावे कुल मिलाकर सही ही होते हैं। बस बढ़ा-चढ़ाकर और खुद को दिखाने वाली भाषा इस्तेमाल करना शायद उनकी style, या personality है
      यह भी समझ आता है कि कुछ लोगों को यह रूखा लग सकता है। पहले भी llamacpp में इसी तरह का drama हुआ था
    • Justine काफ़ी प्रतिभाशाली और creative व्यक्ति लगते हैं, लेकिन production में मैं कोई “नई” और “खुरदरे हिस्सों वाली” libc इस्तेमाल नहीं करना चाहूँगा
      production में सबसे पहली प्राथमिकता “बहुत तेज़ी से बेहतर होना” नहीं, बल्कि स्थिरता, predictability, reliability है। बेशक performance भी महत्वपूर्ण है। ज़्यादा तेज़ code infrastructure घटा सकता है, जिससे cost और environment दोनों के लिहाज़ से फायदा हो सकता है। लेकिन speed सबसे आख़िरी priority है
    • लंबे समय तक अकेले computer के सामने code करते रहने पर शायद social contact कम हो जाता है और कुछ हद तक अहंकार साथ आ जाता है। अगर आपको या आपके काम की अहमियत को reality check देने वाली कोई व्यवस्था न हो, तो उपलब्धियाँ प्रभावशाली होने के बावजूद व्यापक रूप से मिली मान्यता से भी ज़्यादा बड़ी लगने लग सकती हैं
      उदाहरण के लिए APE मुझे बहुत प्रभावशाली hacking लगता है, लेकिन यह आलोचना भी संभव है कि “अब यह सिर्फ़ एक platform पर unsafe नहीं, बल्कि कई platforms पर एक साथ unsafe हो सकता है?”
      tech field में जितना लंबा समय बिताते हैं, उतना समझ आता है कि पूरी तरह mutual benefit बहुत दुर्लभ है, और ज़्यादातर चीज़ें कुछ पाने और कुछ खोने वाले trade-off ही होती हैं
    • कम-से-कम मुझे तो यह मज़ाक जैसा लगा
    • क्या आपने सोचा है कि आपका और Justine का sense of humor अलग हो सकता है? यह भी नहीं समझ आता कि इसे यहाँ पोस्ट करने से किसकी मदद होगी
  • यह पूरी तरह side note है, लेकिन game developer के तौर पर मुझे सभी developer builds में debug work ज़्यादा करने वाले धीमे mutex पसंद आने लगे हैं। जैसे उनके पास debug name/ID हो, owner track करें, contention में लगे समय को profiler में report करें, और ownership change भी profiler में report करें
    games concurrency को अलग तरह से structure करते हैं, और lock से बचने के patterns भी evolve हुए हैं। लेकिन ऐसे patterns इस्तेमाल करना मुश्किल होता है और programmer को structure बदलना पड़ता है। ज़्यादातर code “चलो फिलहाल यहाँ lock लगा दें और milestone पार कर लें” से शुरू होता है
    तेज़ lock भी unpredictable तरीके से धीमे हो सकते हैं, और अगर real-time guarantee थी तो उसे तोड़ देते हैं। average में वे तेज़ हो सकते हैं, लेकिन tail latency गायब नहीं होती। “हमारा game अटक रहा है” को trace करने के लिए वापस आने वाला व्यक्ति मैं नहीं बनना चाहता, लेकिन आम तौर पर मैं ही वह व्यक्ति बन जाता हूँ
    इसलिए धीमे lock इस्तेमाल करना बेहतर है। वे lock जो profiler में बड़े लाल रंग में दिखते हैं। अगर दिख जाए कि वे चोट पहुँचा रहे हैं, तो refactor करके उन्हें हटा दें
    मुझे पता है यह मुश्किल मांग है। AAA production में profiler इस्तेमाल करना जानने वाले लोग उंगलियों पर गिने जा सकते हैं। कई productions देखने पर भी हमेशा यही था
    शिकायत के लिए माफ़ी, लेकिन उम्मीद है कि तेज़ concurrency primitives और algorithms पर research जारी रहेगी

    • और भी side note के तौर पर, Rust में game develop करना मुझे जिन वजहों से अच्छा लगता है, उनमें से एक यह है
      games में अगर संभव हो तो आप lock contention कभी नहीं चाहते, और कई मामलों में यह साबित किया जा सकता है कि lock लेना unnecessary है। उदाहरण के लिए हर frame phases में बँटा होता है, और किसी shared resource पर mutable access सिर्फ़ किसी खास phase में चाहिए होता है। जैसे render() से पहले का update() या asset hot reload
      scoped threads और Rust के borrowing rules इस्तेमाल करके structure ऐसा बनाया जा सकता है कि mutex की ज़रूरत ही न पड़े, और बाद में code बदलने पर जैसे ही उसकी ज़रूरत पड़ेगी, compiler सख्ती से error देगा — इस पर भरोसा किया जा सकता है
      जहाँ संभव हो, profiler spike की बजाय compile error मिलना हमेशा बेहतर है
    • पूरी तरह सहमत हूँ। deadlock detection या internal state inspection जैसी debugging सुविधाएँ आसानी से अपनी कीमत वसूल कर देती हैं। अगर आप lock इतनी बार acquire कर रहे हैं कि performance पर असर पड़े, तो design को फिर से देखना चाहिए। threads के बीच mutable state share करने से बचना चाहिए
  • एक तरफ़ Cosmo/APE/redbean वाली लाइन वाकई कमाल की लगती है, और उससे जुड़े लेखों के comments भी आम तौर पर सकारात्मक हैं; concept को ही खारिज करने वाली बातें भी ज़्यादा नहीं दिखतीं। लेकिन दूसरी तरफ़, मैंने लगभग कभी नहीं सुना कि कोई और इसे इस्तेमाल कर रहा हो
    यह सही है कि हर कोई अपना काम बड़े पैमाने पर शेयर नहीं करता, लेकिन इतने साल बीत गए हैं तो मुझे लगता था कि कुछ project retrospective पोस्ट तो दिखी होंगी। Cosmo/APE/redbean का जितना भी ज़िक्र मैंने देखा है, वह सब Justine की साइट से ही आया है
    इसलिए जिज्ञासा है। क्या इसमें कोई छिपा हुआ trap है? क्या यह performance पाने के लिए कुछ खराब हरकतें करने वाला tool है? क्या compilers या runtime की गहराई से जानकारी न होने के कारण मैं कोई tom7-style joke या trolling समझ नहीं पा रहा? या ये सचमुच बहुत चतुर tools हैं जो अभी व्यापक रूप से फैले नहीं हैं?

    • APE एक ऐसे चालाक trick पर चलता है जिसे कभी भी block किया जा सकता है, और OpenBSD में तो सचमुच block कर दिया गया
      cross-platform software बनाने वाले ज़्यादातर लोग ऐसा single executable नहीं चाहते जो हर platform पर चले; वे ऐसा single codebase चाहते हैं जो हर supported platform पर सही तरीके से काम करे
      उस नज़रिए से, Go जैसी languages, जिनमें CGO से बचने पर सभी targets के लिए cross-compile किया जा सकता है, काफ़ी सुखद हैं। लेकिन APE का तीन अलग-अलग तरीकों से executable हो जाने वाला जादू, चाहे कितना भी clever हो, यह भरोसा नहीं देता कि वह हमेशा चलता रहेगा, और ज़्यादातर मामलों में उसका practical फायदा भी बहुत कम है
      हर platform की अपनी packaging और signing requirements होती हैं, इसलिए platform-specific targets के लिए अलग-अलग compile करना ही बेहतर है
    • व्यक्तिगत रूप से, cosmo और ape बहुत clever लगते हैं, लेकिन जब साधारण tools पहले से ठीक काम कर रहे हों तो काम में इस तरह की cleverness की ज़रूरत नहीं होती
      उदाहरण के लिए, अगर आप पहले से project को अलग-अलग operating systems और platforms के लिए cross-compile कर सकते हैं, या ऐसी build infrastructure मौजूद है, तो हर जगह चलने वाली single binary बनाने का समाधान ढूँढने की कोई वजह नहीं है
      साथ ही APE कई operating systems पर चलने के लिए clever hacking इस्तेमाल करता है। अगर executable formats evolve होते हुए किसी दिन वह hack टूट गया तो? अगर उस बदलाव के हिसाब से APE को ठीक करने का समय किसी के पास न हुआ तो?
      इसके उलट gcc, clang, go, rust जैसे boring tools लगातार update होते और evolve होते operating systems पर भी चलते रहेंगे। इसलिए मैं बस boring विकल्प पर ही रहता हूँ। clever चीज़ों की परवाह न करने की वजह यह है कि boring चीज़ मेरे लिए बस ठीक से काम करती है
    • Mozilla का llamafile इसे इस्तेमाल करता है। यह model weights और executable को एक साथ bundle करता है ताकि cosmo/ape platform पर कहीं भी चल सके, और interaction के लिए redbean HTTP server भी शुरू कर देता है
      इसे bundled weights के बिना चलाकर filesystem से weights पढ़वाना भी संभव है। local LLM को “download करो और तुरंत चलाओ” बनाने का यह शायद सबसे आसान तरीका हो सकता है
    • Cosmopolitan हमेशा ऐसा लगता था जैसे कोई technical loophole हो, जो मज़ेदार blog post का अच्छा topic बनता है। अपनी inventive nature और setup के प्रति obsession के बल पर HN जैसी जगहों के front page पर लगभग guaranteed पहुँचने वाली चीज़
      लेकिन libc जैसी foundational technology के रूप में इस्तेमाल करने के लिए यह मुख्यतः मज़ेदार toy या छोटे personal projects में उपयोगी लगता है
      उस context में, जब इसे glibc, musl, msvcrt जैसी चीज़ों के serious alternative के रूप में पेश किया जाता है, तो थोड़ा अजीब लगता है। यह बहुत प्यारा hack है, लेकिन अगर मुझे किसी ऐसी चीज़ में यह मिले जिस पर मैं गंभीरता से निर्भर हूँ, तो मैं काफ़ी असहज हो जाऊँगा
    • Mozilla के पास Cosmopolitan libc आधारित Llamafile project है: https://github.com/Mozilla-Ocho/llamafile
      Hugging Face पर भी उस format में repackage किए गए popular models नियमित रूप से upload किए जाते हैं: https://huggingface.co/models?search=llamafile
      हालांकि यह छोटे models को जल्दी test करने से आगे कितना practical है, यह अलग सवाल है
  • अगर यह इतना अच्छा है, तो हैरानी होती है कि सभी C लाइब्रेरी ने वही ट्रिक क्यों नहीं अपनाई
    मेरा अनुमान है कि वे ट्रिक्स केवल किसी खास architecture, खास CPU मॉडल, खास workload या access pattern में ही हमेशा तेज़ होने की संभावना रखती हैं। supported सभी hardware पर अलग-अलग workloads को सही ढंग से benchmark करने पर शायद वही फायदा न निकले
    या फिर हो सकता है कि Cosmopolitan जिस pthread API semantics को implement करना चाहता है, वह सूक्ष्म रूप से अलग हो, और यह implementation specification का सख्ती से पालन न करता हो
    यह कल्पना करना मुश्किल है कि कई libc लेखक operating-system primitives पर नई research से अनजान रह गए होंगे

    • ऐसे projects में किसी एक specific API के अलावा भी दर्जनों priorities होती हैं। किसी individual API पर अटक जाना सीमित समय का अच्छा उपयोग नहीं है। और counterexample के तौर पर Linux की सामान्य libc में malloc और string routines को देख लीजिए
      glibc का malloc ठीक-ठाक कामचलाऊ है, लेकिन overall speed और scalability में अधिक modern alternatives से आसानी से पीछे रह जाता है। fragmentation ज्यादा है और समय के साथ बिगड़ता है, और MALLOC_ARENA_MAX जैसे कई tuning values भी हैं जिनका real workloads पर बड़ा असर पड़ता है। musl malloc performance के लिहाज से हर स्तर पर भयावह है। multi-threaded programs में musl allocator इस्तेमाल करने से performance इतनी खराब हो जाती थी कि इसे लगभग लापरवाही कहा जा सकता था
      musl में SIMD-optimized string comparison routines जैसी चीज़ें भी नहीं हैं। non-trivial programs में ऐसे कामों पर कितने CPU cycles खर्च होते हैं, यह जानकर आप चौंकेंगे; real profiles में भी यह साफ दिखता है, और इसे improve करने से लगभग हर program को broadly फायदा मिलता है। glibc की optimized routines अच्छी हैं, लेकिन फिर भी लगता है कि वे और तेज़ हो सकती हैं
      ये चीज़ें “सिर्फ एक architecture के लिए specialized और generalize न होने वाली optimization” नहीं हैं। खासकर ये दोनों areas लगभग सभी workloads में wall-clock time को 2~5 गुना घटाने और long-term working set utilization को काफी improve करने वाले, अच्छी तरह explore और समझे गए areas हैं। फिर इन्हें क्यों नहीं अपनाया गया? हमेशा की तरह, शायद करने को और काम थे, या musl की तरह top performance के बजाय simplicity को priority देने वाली conflicting priorities थीं
      मैं ऐसे projects को दोष नहीं दे रहा। कोई भी यह नहीं कहता कि “मेरा program बेहद धीमा है, कुछ भी ठीक से न कर पाने के लिए design किया गया है, और मुझे इस पर गर्व है।” बस यह सोच कि उन projects पर काम करने वालों ने designs में सिर्फ perfect Pareto frontier ही चुनी, बिल्कुल realistic नहीं है और यह ज्यादातर projects के असल में चलने के तरीके को नहीं दर्शाती
    • वजह politics, NIH syndrome, और पुराने maintainers हो सकते हैं
      glibc या C++ वाले equivalent में कुछ बदलने में अनंत समय लग जाता है
      synchronization primitives कई तरह के होते हैं, और pthreads उनमें से सिर्फ कुछ को support करता है। खुद को उसी तक सीमित करने पर आम तौर पर portability के बदले performance छोड़नी पड़ती है
    • मुझे आश्चर्य है कि “यह कल्पना करना मुश्किल है कि कई libc लेखक operating-system primitives पर नई research से अनजान रह गए होंगे” व्यंग्य है या नहीं
      libc maintainers के बारे में नहीं जानता, लेकिन कुछ चीज़ें maintain करने वाले के रूप में मैं latest research implement करने की कोशिश नहीं करता। मैं stability बनाए रखने और performance acceptable है यह सुनिश्चित करने की कोशिश करता हूँ। research implementations मेरे “maintenance” budget से बाहर हैं
    • सोच रहा हूँ कि pthread mutex implementation बदलने में ABI considerations होते हैं या नहीं
    • “अगर यह इतना अच्छा है, तो सभी C libraries ने वही trick क्यों नहीं अपनाई?” सवाल से यह joke याद आता है
      एक आदमी और एक statistician सड़क पर चल रहे थे और उन्हें 50 euro का note दिखा। statistician चलता रहा, आदमी रुका और बोला, “देखिए, ज़मीन पर पैसे पड़े हैं।” तब statistician ने कहा, “नकली होगा। असली होता तो कोई पहले ही उठा चुका होता,” और चलता रहा। दूसरा आदमी वह पैसा उठा ले गया
  • threads और mutexes computer science में सबसे ज्यादा जटिलता पैदा करने वाली चीज़ों में हैं। किसी नए implementation को तब तक हमेशा skeptically देखना चाहिए, जब तक वह कई सालों तक बड़े scale पर इस्तेमाल न हो चुका हो
    ऐसे threading mechanisms के bugs अक्सर सबसे गहन review से भी बच निकलते हैं। 90s के mid में जब Java आया, तो Solaris के तरह-तरह के thread और mutex bugs उजागर हुए थे
    हमें सबसे तेज़ mutex implementation नहीं, बल्कि भरोसेमंद implementation चाहिए

    • mutex सबसे “जटिल” चीज़ों से काफी दूर है। इसे efficiently implement करने के तरीके भी बहुत ज्यादा नहीं हैं। ज्यादातर cases में, खासकर read path में, इससे बचना ही सबसे अच्छा है
  • यह code mutex lock performance नहीं, बल्कि mutex contention benchmark करता है। अगर आप इस तरह lock इस्तेमाल कर रहे हैं, तो अपने code का फिर से मूल्यांकन करना चाहिए
    हर thread g_chores increment करते समय mutex को lock और unlock करता है। इससे mutex को बार-बार acquire और release करने का overhead पैदा होता है, और यह प्रति thread 100,000 बार दोहराया जाता है
    यह overhead lock mechanisms के बीच असली performance differences को ढक देता है, क्योंकि benchmark real work के बजाय lock contention से dominated है। ऐसा benchmark बेकार है

  • मैं Justine और उनके काम का fan हूँ, लेकिन यह शायद mutex benchmark test case के तौर पर सबसे कम interesting case है। कई threads का लगातार उसी mutex पर hammer करना ऐसी स्थिति है जिससे शुरू में ही बचना चाहिए
    इसलिए कौन-सा mutex implementation इस case को सबसे अच्छी तरह handle करता है, यह मुझे खास interesting नहीं लगता

    • जानना चाहूँगा कि mutex के अच्छे benchmark test cases के रूप में आप क्या सोचते हैं
    • मैं lock या semaphore जिन ज्यादातर cases में इस्तेमाल करता हूँ, वे बहुत महंगे resource के आसपास होते हैं। उस resource का usage lock के performance overhead को दबा देता है
    • तो क्या measure करना चाहिए? uncontended case important है और baseline बनता है, लेकिन उसके अलावा mutex की कमजोरी ठीक यही जगह है। अगर contention ठीक से handle नहीं होता, तो hardware idle रहता है, scheduler work बढ़ता है, या kernel entries ज्यादा होती हैं
      एक important बात छूट गई थी: contention की स्थिति में खराब performance वाला lock memory network में hotspot बनाने जैसे बहुत negative system-level effects पैदा कर सकता है, और यह भी यहाँ सामने आएगा
    • “कई threads को same mutex पर लगातार hammer नहीं करना चाहिए” वाली assessment से पूरी तरह सहमत होना मुश्किल है
      मुझे कुछ cases याद आते हैं जहाँ कई threads एक ही mutex पर इकट्ठा होते हैं। आसान example के तौर पर list या dictionary जैसे data structures को concurrently भरना है
      message passing से भी किया जा सकता है, लेकिन वह ज्यादा memory इस्तेमाल कर सकता है और shared location पर write करने के लिए इंतज़ार करने से धीमा हो सकता है
  • production speed, efficiency, या साफ तौर पर “clever hacking” के बारे में नहीं है
    अगर रविवार सुबह 3 बजे खराब system ठीक करने के लिए बुलाए न जाने की guarantee के लिए efficiency का 50% त्यागना पड़े, तो मैं हर बार वही चुनाव करूँगा
    production reliability के बारे में है, और reliable code लिखना “fast” code लिखने से 10 गुना ज्यादा कठिन है