30 पॉइंट द्वारा GN⁺ 2026-01-15 | 5 टिप्पणियां | WhatsApp पर शेयर करें
  • OpenJDK में ThreadMXBean.getCurrentThreadUserTime() को /proc फ़ाइल parsing के बजाय clock_gettime() कॉल से बदला गया, जिससे अधिकतम 400 गुना प्रदर्शन सुधार हासिल हुआ
  • पुराना implementation /proc/self/task/<tid>/stat फ़ाइल को खोलने, पढ़ने और parse करने वाले जटिल I/O path से गुजरता था
  • नया implementation Linux kernel के clockid_t bit encoding का उपयोग करता है और pthread_getcpuclockid() से मिले ID के निचले bits को समायोजित करके सिर्फ user time को सीधे पढ़ता है
  • benchmark नतीजों में औसत call time 11μs → 279ns तक घटा, और बाद में kernel fast-path लागू करने पर लगभग 13% अतिरिक्त सुधार मिला
  • यह दिखाने वाला एक उदाहरण कि POSIX की सीमाओं से आगे बढ़कर Linux internal ABI की समझ के जरिए optimization संभव है

पुराने implementation की समस्या

  • getCurrentThreadUserTime() /proc/self/task/<tid>/stat फ़ाइल खोलकर 13वें और 14वें field को parse करता था और CPU user time की गणना करता था
    • फ़ाइल path बनाना, फ़ाइल खोलना, buffer पढ़ना, string parse करना, sscanf() कॉल करना जैसी बहु-स्तरीय प्रक्रिया की ज़रूरत थी
    • command name में parentheses शामिल हो सकते थे, इसलिए आख़िरी ) खोजने के लिए strrchr() का उपयोग करने वाला जटिल logic भी था
  • इसके विपरीत getCurrentThreadCpuTime() केवल एक clock_gettime(CLOCK_THREAD_CPUTIME_ID) कॉल करता था
  • 2018 के bug report (JDK-8210452) के अनुसार, दोनों methods के बीच speed gap 30~400 गुना तक पहुँचता था

/proc access path और clock_gettime() path की तुलना

  • /proc तरीका open(), read(), sscanf(), close() जैसी कई system calls और kernel के भीतर string generation को शामिल करता है
  • clock_gettime() तरीका एक single system call से sched_entity structure से सीधे time value पढ़ता है
  • parallel load में /proc access kernel lock contention की वजह से और धीमा हो जाता है

नया implementation तरीका

  • POSIX standard के अनुसार CLOCK_THREAD_CPUTIME_ID user+system time लौटाता है
  • Linux kernel clockid_t के निचले bits में clock kind को encode करता है
    • 00=PROF, 01=VIRT(user-only), 10=SCHED(user+system)
  • pthread_getcpuclockid() से मिले clockid के निचले bits को 01 में बदलने पर इसे user-time-only clock में बदला जा सकता है
  • नए code में फ़ाइल I/O और parsing हटाकर, केवल clock_gettime() कॉल से user time लौटाया जाता है

प्रदर्शन मापन के नतीजे

  • बदलाव से पहले औसत call time 11.186μs, बदलाव के बाद 0.279μs रहा, यानी लगभग 40 गुना सुधार
    • 16-thread environment में मापा गया, जो पहले रिपोर्ट की गई 30~400 गुना range से मेल खाता है
  • CPU profile में फ़ाइल open/close से जुड़े system calls गायब हो गए, और केवल एक clock_gettime() कॉल बची

kernel fast-path के साथ अतिरिक्त optimization

  • kernel, clockid में PID=0 encode होने पर current thread तक सीधे पहुँचने वाला fast-path देता है
  • अगर JVM pthread_getcpuclockid() की जगह सीधे clockid बनाकर PID=0 डाले, तो radix tree lookup को छोड़ा जा सकता है
  • manually बनाए गए clockid के उपयोग पर औसत समय 81.7ns → 70.8ns, यानी लगभग 13% अतिरिक्त सुधार
  • लेकिन यह clockid_t के size जैसी kernel internal implementation पर निर्भर करता है, इसलिए readability और compatibility में कमी की आशंका है

निष्कर्ष और सीख

  • 40 लाइनों को हटाकर 400 गुना प्रदर्शन अंतर खत्म किया गया, और यह बिना किसी नए kernel feature के, सिर्फ मौजूदा ABI की बारीक संरचना के उपयोग से संभव हुआ
  • kernel source code को गहराई से पढ़ने के मूल्य पर ज़ोर: POSIX portability की गारंटी देता है, लेकिन kernel code संभावनाओं की सीमा दिखाता है
  • पुरानी धारणाओं की फिर से समीक्षा का महत्व: /proc parsing पहले उचित रहा होगा, लेकिन अब यह अप्रभावी है
  • यह बदलाव JDK 26 (मार्च 2026 में रिलीज़ होने की योजना) में शामिल होगा, और ThreadMXBean.getCurrentThreadUserTime() कॉल पर स्वचालित प्रदर्शन सुधार देगा

5 टिप्पणियां

 
crawler 2026-01-15

वाकई कमाल है।

> अगर यह 2 गुना तेज़ हुआ है, तो हो सकता है कि आपने कोई स्मार्ट काम किया हो; और अगर यह 100 गुना तेज़ हुआ है, तो बस इतना है कि आपने कोई बेवकूफी भरा काम करना बंद कर दिया।

मुझे नहीं लगता कि यह पूरी तरह गलत बात है, लेकिन जब मामला kernel से जुड़ा हो, तो मुझे लगता है कि सिर्फ यह पहचान पाना ही कि चीज़ें धीमी हैं, अपने-आप में बेहद मुश्किल रहा होगा।

 
[यह टिप्पणी छिपाई गई है.]
 
princox 2026-01-19

ऐसी चीज़ें प्रोजेक्ट में कैसे खोजी जा सकती हैं? सिर्फ AI चलाने से इसका पता चलना मुश्किल लगता है..

ऐसे उदाहरणों को देखकर मुझे भी लगता है कि सीखकर इसे ज़रूर खुद अनुभव करना चाहिए।

 
aobamisaki 2026-01-15

असल में पूरे कोडबेस को फिर से लिखकर भी 2~3 गुना सुधार हासिल करना मुश्किल होता है, ऐसे में सिर्फ कुछ लाइनें बदलकर अधिकतम 400 गुना सुधार मिलना वाकई कमाल की बात है।

 
GN⁺ 2026-01-15
Hacker News की राय
  • मैं इस पोस्ट का लेखक हूँ। पिछले kernel bug वाले लेख के बाद, मैंने देखा कि JVM अपने thread activity को खुद कैसे रिपोर्ट करता है
    मुझे पता चला कि “इस thread का CPU usage time कितना है?” जैसा सवाल उम्मीद से काफी महंगा operation है
    • nanosecond स्तर की measurement पर बात करनी हो तो clock की stability और accuracy को बहुत अच्छी तरह समझना पड़ता है
      atomic clock जैसे मानक के बिना absolute numbers पर दावा करना मुश्किल लगता है
    • यह जानने की जिज्ञासा है कि distribution कई orders of magnitude में फैला हुआ क्यों है। यह अपने-आप में दिलचस्प घटना है
    • छोटी TL;DR summary के लिए सच में आभारी हूँ। ऐसे summary लेख में प्रवेश की बाधा कम करते हैं और पढ़ने की प्रेरणा देते हैं
    • “हैरानी की बात नहीं (Quelle Surprise)” जैसी प्रतिक्रिया दी गई
  • clock_gettime() vDSO के ज़रिए context switch से बचता है। इसलिए flamegraph में भी उसका निशान दिखता है
    • लेकिन यह सिर्फ कुछ clock पर लागू होता है। CLOCK_VIRT या CLOCK_SCHED जैसे मामलों में अब भी syscall call की ज़रूरत होती है
    • vDSO frame के नीचे देखने पर अब भी syscall दिखता है। लगता है कि खास clock id के लिए fast path implement नहीं किया गया है
    • CLOCK_THREAD_CPUTIME_ID आखिरकार kernel में जाता है, क्योंकि उसे task struct को reference करना पड़ता है
      संबंधित kernel source: posix-cpu-timers.c,
      cputime.c,
      gettimeofday.c
  • PERF_COUNT_SW_TASK_CLOCK का इस्तेमाल करने पर लगभग 8ns स्तर की measurement भी संभव है
    यह perf_event_mmap_page के जरिए shared page से read करके, rdtsc call से delta निकालने का तरीका है
    इसकी documentation अच्छी नहीं है और open source implementation भी लगभग नहीं के बराबर हैं
    • यह सच में कमाल की trick है। लेकिन perf_event setup और permission requirements बड़े हैं, इसलिए यह long-lived thread के लिए ज़्यादा उपयुक्त लगता है
    • पूछा गया कि seqlock की ज़रूरत क्यों पड़ती है। क्या इसका मकसद page value और rdtsc के बीच context switch न होने देना है?
      शायद structure ऐसा है कि rdtsc के बाद page value फिर से check की जाती है, और बदलने पर retry किया जाता है
      वैसे clock_gettime भी vdso-आधारित virtual syscall है
    • clock_gettime syscall नहीं, vdso का इस्तेमाल करता है
  • Flamegraph वाकई शानदार tool है
    सिर्फ code देखकर सब ठीक लगता है, लेकिन flamegraph देखने पर अक्सर लगता है, “ये क्या है?!”
    non-static initialization, एक लाइन के logger call से महंगी serialization होना, जैसी कई समस्याएँ मिलीं
    • मुझे icicle graph भी पसंद है। यह flamegraph की उलटी दिशा में accumulate होता है, इसलिए जब कई paths किसी common library को call करते हैं तब bottleneck देखना आसान हो जाता है
    • इस SVG example को नए tab में खोलने पर interactive zoom किया जा सकता है
    • performance profiling और optimization experiments development के सबसे मज़ेदार हिस्सों में से एक हैं। अक्सर हैरानी होती है: “यह इतना धीमा क्यों है?”
    • किसी ने कहा कि string parsing और memoization का संयोजन अजीब लगता है। असल में समस्या यह थी कि महंगे regex pattern parsing को cache नहीं किया गया था
    • जो लोग पहली बार flamegraph इस्तेमाल करना चाहते हैं, उनके लिए बुनियादी concepts और starting point के बारे में पूछा गया
  • यह जानकर हैरानी हुई कि “image को नए tab में खोलना” वास्तव में SVG interaction देता है
    • यह feature Brendan Gregg की FlameGraph script की वजह से है
      मैं आम तौर पर async-profiler का HTML generator इस्तेमाल करता हूँ, लेकिन इस बार single SVG के लिए Brendan का tool इस्तेमाल किया
  • मैं OpenJDK patch का लेखक हूँ। मैंने /proc पढ़ते समय होने वाले memory overhead, eBPF profiling, और कम-documented user-space ABI के इतिहास पर लिखा है
    विवरण मेरे blog post में है
    • मुझसे पूछा गया कि मूल implementation ऐसा क्यों था। हर call पर file IO और string parsing करना अक्षम है, लेकिन लगता है उस समय इसके पीछे कोई कारण रहा होगा
    • Jaromir ने मेरा लेख देखकर कहा, “मैंने भी लगभग उसी समय draft लिखा था,” और एक-दूसरे के लेख को link किया। उसने कहा कि मेरा लेख ज़्यादा rigorous है, यह सुनकर अच्छा लगा
  • सिर्फ इसलिए कि भाषा C या C++ जैसी system language है, इसका मतलब यह नहीं कि वह हमेशा तेज़ होगी। गति काफी हद तक इस पर निर्भर करती है कि आप कर क्या रहे हैं
  • vDSO के जरिए read करना kernel transition, buffer serialization, और parsing process से बचाता है, इसलिए यह काफी तेज़ है
  • यह quote साझा किया गया: “अगर कुछ 2x तेज़ हुआ है, तो हो सकता है आपने कोई समझदारी का काम किया हो; अगर 100x तेज़ हुआ है, तो शायद आपने बस मूर्खतापूर्ण काम करना बंद किया है
    source tweet
  • QuestDB टीम इस क्षेत्र में शीर्ष स्तर की है। लोग भी शानदार हैं, और software भी
    Jaromir का blog भी वाकई शानदार था