- CPython का नया tail-call interpreter शुरू में pyperformance औसत में 10~15% सुधार जैसा दिखा, लेकिन baseline समायोजित करने पर वास्तविक सुधार configuration के अनुसार लगभग 1~5% तक सिमट गया
- बड़ा सुधार नई implementation के अपने प्रभाव से अधिक LLVM 19 regression को बायपास करने का परिणाम था; मुख्य कारण यह था कि Clang 19 पुरानी computed goto dispatch को ठीक से पुनरुत्पादित नहीं कर पाया
- Intel Raptor Lake i5-13500 पर clang19 build, clang18 से 1.09x धीमा था और clang19.tc 1.03x तेज़, लेकिन Apple M1 MacBook Air पर clang19 1.12x धीमा और clang19.tc भी 1.00x धीमे स्तर पर ही रहा
- LLVM की tail duplication limit के कारण indirect jump की संख्या clang18 के 332 से घटकर clang19 में 3 रह गई, जिससे computed goto आधारित interpreter की इच्छित संरचना लगभग समाप्त हो गई
- tail-call तरीका अब भी अर्थपूर्ण सुधार देता है, लेकिन
musttailजैसे, compiler optimization को स्पष्ट रूप से अनिवार्य करने वाले तरीके performance-sensitive code के लिए अधिक मज़बूत हो सकते हैं
baseline effect जो performance improvement जैसा दिखा
- CPython प्रोजेक्ट ने लगभग एक महीने पहले bytecode interpreter की नई implementation strategy को merge किया
- शुरुआती नतीजों में अलग-अलग platforms और pyperformance benchmarks पर औसतन 10~15% performance improvement दिखा
- बाद की analysis में पता चला कि यह बड़ा सुधार मुख्यतः LLVM 19 regression को संयोगवश बायपास करने का परिणाम था
- GCC, clang-18, या specific tuning flags वाले LLVM 19 से तुलना करने पर सुधार लगभग 1~5% तक घट गया
- tail-call interpreter खुद वास्तविक speedup देता है, लेकिन सुधार उतना बड़ा नहीं है जितना शुरुआती आँकड़े संकेत दे रहे थे
- अगर build clang-19 या उसके बाद के version से किया गया हो, तो पुराना path वास्तव में 10~15% धीमा रहा हो सकता है
- Simon Willison ने
python-build-standalonebuild और Python 3.13 की तुलना में 10% speedup को reproduce किया
- Simon Willison ने
benchmark setup और मुख्य आँकड़े
- कई CPython builds की तुलना Intel server और Apple M1 MacBook Air पर की गई
- Intel server Hetzner पर चलने वाला Raptor Lake i5-13500 था
- सभी builds में LTO और PGO का उपयोग किया गया
- build reproduction के लिए nix configuration का उपयोग किया गया
- तुलना के targets ये थे
clang18: Clang 18.1.8, computed gotogcc: GCC 14.2.1, computed goto, केवल Intel परclang19: Clang 19.1.7, computed gotoclang19.tc: Clang 19.1.7, नया tail-call interpreterclang19.taildup: Clang 19.1.7, computed goto और regression workaround के लिए-mllvmtuning flag
clang18को baseline मानकर pyperformance औसत परिणाम इस प्रकार रहे- Raptor Lake i5-13500:
clang19: 1.09x धीमाclang19.taildup: 1.01x तेज़clang19.tc: 1.03x तेज़gcc: 1.02x तेज़
- Apple M1 MacBook Air:
clang19: 1.12x धीमाclang19.taildup: 1.02x धीमाclang19.tc: 1.00x धीमा
- Raptor Lake i5-13500:
- tail-call interpreter ने clang-18 की तुलना में कुछ speed improvement दिखाया, लेकिन clang-19 पर जाने से आई performance drop उससे बड़ी थी
clang18.tcको मापा नहीं जा सका- क्योंकि tail-call interpreter Clang 19 में नए जोड़े गए compiler feature पर निर्भर करता है
- इस सीमा के कारण स्थिति समझने के लिए अधिक benchmark combinations की ज़रूरत पड़ी
LLVM 19 regression से dispatch का ढहना
- पारंपरिक bytecode interpreter
whileloop के भीतरswitchstatement से opcode को handle करता है- compiler आमतौर पर
switchको jump table और indirect jump में compile करता है
- compiler आमतौर पर
- बहुत पहले से यह ज्ञात है कि हर opcode body में dispatch logic को duplicate करने से ऐसे interpreters तेज़ हो सकते हैं
- loop की शुरुआत में लौटने के बजाय, अगला instruction decode करने और jump table index करने की logic हर opcode में अलग रखी जाती है
- C compilers label के address लेकर उसे computed goto के रूप में उपयोग करने की सुविधा देते हैं, और tail-call कार्य से पहले CPython भी इसी तरह का interpreter loop इस्तेमाल करता था
- Clang/LLVM compiler performance कारणों से computed goto के कई
gotoको अंदरूनी रूप से एक indirectbr LLVM instruction में merge करता है- इसके बाद code generation चरण में tail duplication करके branch logic को हर स्थान पर फिर से duplicate किया जाता है
- यह flow LLVM की पुरानी blog post में high-level रूप से documented है
- LLVM 19 ने कुछ मामलों में गंभीर compile time या memory usage बढ़ने से बचने के लिए tail duplication pass limit जोड़ी
- CPython में इस limit की वजह से Clang ने dispatch jump को merged अवस्था में छोड़ दिया
- परिणामस्वरूप computed goto आधारित implementation का उद्देश्य लगभग निष्प्रभावी हो गया
- यह समस्या समान interpreter loops वाली दूसरी language implementations में पहले पहचानी गई थी, लेकिन इसका CPython पर प्रभाव ज्ञात नहीं था
- object code को disassemble करके indirect jump की संख्या गिनी जाए तो अंतर साफ़ दिखता है
- clang18 build का
_PyEval_EvalFrameDefault:jmp *332 - clang19 build का
_PyEval_EvalFrameDefault:jmp *3
- clang18 build का
computed goto की अस्पष्ट स्थिति
- tail duplication logic में बदलाव से regression हुआ, यह इस बात से पुष्टि हुई कि fix के बाद clang-18 स्तर का performance वापस आ गया
- लेकिन regression का आकार पूरी तरह समझाया नहीं गया
- ऐतिहासिक रूप से opcode dispatch duplication को interpreter को 20% से 100% तक तेज़ करने वाला बताया गया है
- आधुनिक processors के बेहतर branch predictors के साथ, हालिया शोध इसे लगभग 2~4% के छोटे सुधार के रूप में दिखाता है
- Python configuration option के रूप में एक पुराने single
switchstatement वाले interpreter को भी support करता हैclang18.nocg: clang18 की तुलना में 1.01x तेज़clang19.nocg: clang18 की तुलना में 1.02x धीमाclang19: clang18 की तुलना में 1.09x धीमा
clang19.nocg,clang19से तेज़ निकला, यह एक और उलटफेर था- Clang 18 या उचित flags वाले Clang 19,
switchआधारित interpreter में भी dispatch logic को हर opcode body में duplicate कर देते हैं
- Clang 18 या उचित flags वाले Clang 19,
- indirect jump count की तुलना में भी यह अंतर दिखता है
clang18: 332clang18.nocg: 306clang19.nocg: 3clang19: 3
- आधुनिक Clang में computed goto interpreter पूरी तरह अनावश्यक complexity हो सकता है
- क्योंकि compiler
switchआधारित code में भी वही transformation कर सकता है - दूसरी ओर, computed goto खुद भी उस transformation की guarantee देने के लिए पर्याप्त नहीं था
- क्योंकि compiler
- GCC 14.2.1 ने
switchको duplicate नहीं किया, लेकिन computed goto इस्तेमाल करने पर इच्छित behavior लागू किया
fixes और workaround
- LLVM pull request 114990 इस लेख के प्रकाशित होने के तुरंत बाद merge हो गया और regression को fix कर दिया
- merge से पहले के benchmarks में भी यह पुष्टि हुई कि यह fix अपेक्षित performance वापस लाता है
- fix से पहले के release में regression लाने वाले PR द्वारा जोड़ा गया tuning option इस्तेमाल कर tail duplication stop threshold को बदला जा सकता है
- clang-19 में इस limit को बहुत बड़े मान पर सेट करने से समान behavior वापस पाया जा सकता है
- LTO builds में यह option pass करना जटिल है
- tail duplication code generation के दौरान होता है, और LTO build में code generation compile time नहीं बल्कि link time पर होता है
- इसलिए यह flag सिर्फ compiler को नहीं बल्कि
lldको भी देना पड़ता है
- उपयोग की गई configuration का उदाहरण
./configureचरण मेंOPTऔरLDFLAGSको-mllvm -tail-dup-pred-size=5000देना था
benchmarking ने baseline problem उजागर की
- benchmarks किसी specific builds के बीच performance difference को सही माप सकते हैं, लेकिन उसे “सामान्य performance improvement” तक बढ़ाने के लिए अतिरिक्त assumptions चाहिए
- tail-call interpreter benchmark ने पुराने computed goto interpreter की तुलना में 10~15% तेज़ परिणाम दिखाए, लेकिन व्यापक निष्कर्ष निकालने के लिए baseline जटिल था
- performance कार्य में किस baseline से तुलना की जा रही है यह बार-बार कठिन प्रश्न बनता है
- सैद्धांतिक रूप से best-known approach समझ लेने पर भी, वास्तविक OS, compiler options, और flags को ठीक से tune करना अलग समस्या है
- पुराने hardware या पुनरुत्पादन में कठिन scale से आए public benchmarks सीधे comparison के लिए उपयुक्त नहीं हो सकते
- machine learning papers में भी algorithmic improvement का दावा होने पर अक्सर “क्या किया गया” से पहले “किस baseline से तुलना की गई” महत्वपूर्ण प्रश्न बन जाता है
- खराब tune किए गए baseline से तुलना करने पर प्रभावशाली परिणाम आसानी से मिल सकते हैं
optimizing compiler और musttail
- computed goto का मामला दिखाता है कि optimizing compiler से अपेक्षाएँ एक-दूसरे से टकरा सकती हैं
- compiler को programmer की मंशा का सम्मान करते हुए समान behavior बनाए रखना चाहिए
- साथ ही code को तेज़ बनाने के लिए उसे जटिल और कम intuitive transformations भी करने चाहिए
- clang-19 ने computed goto interpreter को program behavior के स्तर पर सही compile किया, लेकिन optimization intent से पूरी तरह अलग output बनाया
- Clang के दूसरे versions ने साधारण
switch()आधारित interpreter पर भी वही optimization लागू किया जिसकी अपेक्षा थी - source code स्तर का computed goto और machine code स्तर की dispatch duplication लगभग परस्पर स्वतंत्र अवधारणाएँ लगती हैं
- execution result समान होने के कारण मौजूदा tools इस अंतर को लगातार व्यक्त करने में कठिनाई महसूस करते हैं
- tail-call interpreter musttail attribute पर आधारित है
musttailपारंपरिक observable program behavior को नहीं बदलता, लेकिन यह optimizer के साथ बातचीत के अधिक करीब है- compiler को specific optimization करने में सक्षम होना चाहिए, और अगर वह optimization न हो तो compilation fail होना चाहिए
- ऐसा तरीका compiler के विकसित होते रहने पर भी performance-sensitive code को अधिक मज़बूती से लिखने की शैली बन सकता है
- यह भी देखने लायक है कि क्या
[[clang::musttailduplicate]]जैसी कोई काल्पनिक attribute interpreterwhileloop के computed goto को replace कर सकती है
nix से मिली reproducibility और सीमाएँ
nixकई Python interpreter builds को manage करने में बहुत मददगार रहा- प्रयोग के दौरान
gcc,clang-18,clang-19,clang-20चार compilers और अलग-अलग flag combinations के साथ दर्जनों Python interpreters build और benchmark किए गए
- प्रयोग के दौरान
nixकी मदद से parallel versions को reproducible और isolated तरीके से बनाए रखा जा सका- यह भरोसा रहा कि कौन-सा build किस compiler और flags से आया
- build matrix की definition भी छोटे abstractions से manage की जा सकी
- bug fix patch लागू किए हुए custom LLVM को build करना, और उसी compiler से Python build चलाना, लगभग 10 lines of code में संभव था
- कमियाँ भी रहीं
nixसामान्य software usage से कुछ अलग तरीके से काम करता है, इसलिए यह पूरी तरह नकारना कठिन है कि इन अंतरों का benchmark या निष्कर्षों पर प्रभाव पड़ा हो- उदाहरण के लिए nix डिफ़ॉल्ट रूप से projects को कुछ hardening flags के साथ build करता है, और शुरुआती चरण में पता चला कि इन flags का tail-call interpreter पर असंतुलित प्रभाव पड़ता है
- Nix की scalability और customization मज़बूत हैं, लेकिन कोई खास customization तरीका खोजने के लिए काफ़ी trial-and-error और
nixpkgssource की पड़ताल करनी पड़ी
1 टिप्पणियां
Hacker News की राय
मैं वह PR लेखक हूं जिसने CPython में tail-call interpreter जोड़ा
सबसे पहले, इस समस्या की वजह खोजने में लगभग एक महीना लगाने के लिए Nelson का धन्यवाद
मुझे, और शायद CPython टीम को भी, उम्मीद नहीं थी कि baseline में इस्तेमाल किए गए compiler में ऐसा bug होगा, और बड़ी गलती करने के लिए मैं बेहद शर्मिंदा हूं और माफी चाहता हूं
मैंने माफी वाली पोस्ट भी डाली है: https://fidget-spinner.github.io/posts/apology-tail-call.htm...
इसे 10~15% performance improvement के रूप में घोषित किया गया था, लेकिन bug-free compiler पर यह 1~5% के करीब है; और वह संख्या भी पूरी तरह गलत नहीं थी, बस कुछ खास conditions में ही सही थी
improvement बनाया, उसे measure किया, और PR की review भी हुई, तो करने लायक काम तो किया ही गया. बस measurement में इस्तेमाल हुए clang version की वजह से numbers ने गलतफहमी पैदा कर दी; यह ऐसी reasonable गलती लगती है जिसमें कोई भी फंस सकता है
फिर भी इसने meaningful performance improvement दिया और compiler regression भी खोज निकाला, इसलिए गलत numbers उसकी तुलना में छोटी बात लगते हैं. मुझे यह भी साफ नहीं कि इससे असल में किसे नुकसान हुआ, और यह माफी मांगने लायक बात नहीं लगती
Python interpreter जैसे पुराने system में अगर 3~5% कायम रहता है, तो वह अपने-आप में बड़ी उपलब्धि है और उस पर गर्व किया जा सकता है
करीब 30 साल बाद, लंबे समय से मौजूद systems में meaningful performance improvement, खासकर 1% से ज्यादा improvement, पर पहले संदेह ही होता है
असली improvement कभी-कभी होता है, लेकिन आम नहीं है; अक्सर सिर्फ समय को कहीं और शिफ्ट किया गया होता है, इसलिए benchmark में पकड़ में नहीं आता. साथ ही benchmarks effect को isolate करने के लिए controlled environment में किए जाते हैं, जबकि real software VM या desktop पर तमाम दूसरी चीजों के साथ चलता है
मैंने कई बार देखा है कि isolated environment में साफ तौर पर बड़ा दिखने वाला improvement production environment में जाते ही गायब हो जाता है या negative हो जाता है
CPython को कई environments को target करना पड़ता है, इसलिए यह और मुश्किल है, और कोई single production target भी नहीं है जिसके लिए कहा जा सके कि “अगर production में तेज नहीं है, तो सच में तेज नहीं है.” ऐसी दुनिया में performance improve करना सचमुच कठिन है
आखिरकार performance tuning और measurement बहुत कठिन हैं, और माफी मांगने लायक बात बस इतना सीखना है कि यह सच है
उम्मीद है कि आप गलत होने से नहीं डरेंगे. वैसे भी हर कोई गलत होता है. अभी की तरह “लगता है यह हमने गड़बड़ किया” कहें, और देखें कि इसे कैसे handle करना है और आगे कैसे बचना है
[1] यह सिर्फ performance में नहीं, लोगों की processes में भी आम है. उदाहरण के लिए, अगर कोई code review tool team कहे कि “हमने code review time 15% घटाकर सबका workflow तेज कर दिया,” तो असल में संभव है कि system के दूसरे हिस्सों में ज्यादा काम पैदा हो गया हो, overall flow तेज न हुआ हो, और 15% सिर्फ उस जगह शिफ्ट हो गया हो जिसे measure नहीं किया गया
सिद्धांत रूप में, ऐसा control-flow graph और profile हो तो compiler के पास पारंपरिक
switch()-based interpreter के लिए optimal code बनाने की पर्याप्त जानकारी होनी चाहिए. लेकिन व्यवहार में जब function इतना बड़ा और connected होता है, तो आपको compiler से लड़ना पड़ता हैवह उन अहम variables को spill कर देता है जिन्हें आप registers में रखना चाहते हैं, stack frame manipulation को ऊपर खींच लाता है जिसे आप fallback function call के आसपास सीमित रखना चाहते थे, और branch prediction की वजह से जिन identical code paths को अलग रखना चाहते थे उन्हें merge कर देता है. यह दस्ताने पहनकर piano बजाने जैसा महसूस हो सकता है
यहां भी वही “identical code path merging” हुआ, और “buggy” compiler ने identical paths को merge करके performance खराब कर दी
“fixed” compiler अब ऐसा नहीं करता, लेकिन वह fix भी आखिरकार compiler की internal heuristics को adjust करने जैसा ही है. इसकी कोई guarantee नहीं कि यह compiler या दूसरे compilers आगे भी अपनी heuristics को हमारे पक्ष में बनाए रखेंगे
इसके उलट, tail-call interpreter desired machine-code pattern को interpreter में ही express कर सकता है.
musttail,noinline,preserve_noneattributes को साथ इस्तेमाल करने पर problem को इस तरह constrain किया जा सकता है कि optimizer heuristics का असर काफी कम होइसलिए tail-call interpreter का फायदा सिर्फ 3~5% performance improvement से ज्यादा है, और कुछ compilers में यह उससे भी बड़ा reliable performance improvement हो सकता है
क्या ऐसी चीजों को रोकने के लिए benchmarks को improve किया जा सकता है?
[0] https://github.com/faster-cpython/benchmarking-public
बेंचमार्किंग को सही तरीके से करना सचमुच बेहद मुश्किल है। गुमराह करने वाले फैक्टर बहुत ज़्यादा होते हैं
हाल ही में मुझे लगा कि मैंने किसी algorithm को लगभग 15% तेज़ बनाने का तरीका ढूंढ लिया है। कम से कम सभी benchmarks तो यही कह रहे थे
लेकिन जब मैंने test harness में तेज़ function की एक copy डाल दी और असल में उसे call किए बिना केवल मूल धीमे version को ही call किया, तब भी वह 15% तेज़ था। यानी जो code चला ही नहीं, उसने मूल code को तेज़ बना दिया
जाहिर है, यह code और memory layout का मामला था; कुछ चीज़ें खिसकीं और CPU cache में बेहतर तरीके से फिट हो गईं
यह जानना सच में कठिन है कि मिली हुई speedup code के वास्तव में “बेहतर” होने से आई है या फिर कहीं बेहतर alignment किस्मत से मिल गया
Casey Muratori Substack पर इसी विषय पर एक बहुत रोचक series लिख रहे हैं
असल स्थिति में गहराई से जाकर उसे सामने लाने वाले लेखक की तारीफ करनी चाहिए। Python 3.14 का tail-call interpreter अब भी एक अच्छा improvement है, और language runtime में कुछ प्रतिशत का सुधार मुश्किल से हासिल होने वाली उपलब्धि है
हालांकि यह कोई जादुई 15% free lunch नहीं था
ज्यादा अहम बात यह है कि इस घटना ने benchmarking rigor और कई environments में test करने की अहमियत को अच्छी तरह दिखाया। इसने एक compiler bug भी उजागर किया, जिससे सभी को फायदा हो सकता है
यह ऐसी deep analysis है जो अगली बार किसी बड़े performance improvement के दावे को फिर से verify करने पर मजबूर करती है। सोचने लायक बड़ा सवाल यह है कि अभी मौजूद असंख्य “X% faster” results में से कितने असल में benchmark artifacts या अज्ञात regressions की वजह से हैं
आगे ऐसे pitfalls से बेहतर तरीके से बचने के लिए हमें क्या करना चाहिए?
क्या compiler को खुद benchmark नहीं किया जाता? क्या compiler side या Python side के मौजूदा benchmarks उस compiler का इस्तेमाल नहीं कर रहे थे?
यह इस बात का अच्छा उदाहरण है कि C को “machine के करीब” या “portable assembly” कहना कितना सही नहीं है। आधुनिक optimizers, observable effects न होने पर logic को बेझिझक बदल देते हैं
लेख में भी कहा गया था कि “clang-19 computed-goto interpreter को इस मायने में ‘सही’ compile करता है कि resulting binary सभी expected values देती है, लेकिन साथ ही उसका output optimization intent से पूरी तरह उलटा है। ऊपर से, दूसरे compiler versions ‘naive’
switch()-based interpreter पर optimization लागू करते हैं और ठीक वही optimization implement कर देते हैं जिसे source code को दोबारा लिखकर हम ‘इरादा’ कर रहे थे”C में
a += 1पर आप भरोसा कर सकते हैं कि यह numeric value बढ़ाता है, लेकिन C++ में वही expression memory allocate कर सकता है, call stack unwind कर सकता है, या कोई अज्ञात काम कर सकता है। इसी तरहa = "a"C में सिर्फ pointer assignment है, लेकिन C++ में memory allocation वगैरह हो सकता है“C portable assembly है” इस expression का मतलब यह नहीं कि हर statement सीधे equivalent machine code में compile होता है
compiler ने loop structure से छेड़छाड़ की और इस वजह से tail-call interpreter पूरा का पूरा उतना प्रभावी नहीं रहा जितना announcement में बताया गया था—यह हैरानी की बात नहीं
बहुत paranoid interpreter implementations फिर से सीधे assembly लिखने लगते हैं। LuaJIT इस बात के लिए प्रसिद्ध है कि उसने architecture के बीच portable रहते हुए बहुत efficient assembly loop implementation बनाने के लिए macro system implement किया। इसलिए ऐसी चीज़ों को छूकर देखना मजेदार भी है
कुछ साल पहले popular interpreter loop implementation तरीकों पर एक लेख और tests भी बनाए थे:
https://github.com/vkazanov/bytecode-interpreters-post
आधुनिक branch predictors, अगर execution stretch पर्याप्त लंबा हो और interpreted code का व्यवहार स्थिर हो, तो single indirect jump को भी लगभग सही-सही predict कर सकते हैं
इस पर real hardware और specific simulated branch predictors दोनों में study करने वाला paper है: https://inria.hal.science/hal-01100647/document
इस project में किए गए experiments भी anecdotal तौर पर इसी निष्कर्ष का समर्थन करते हैं। लेख में नहीं डाल पाया/पाई, लेकिन hardware CPU counters और
perf statसे कुछ interpreters देखे, और branch misprediction dominant factor के रूप में नहीं दिखीPython build performance को evaluate करना बेहद मुश्किल है। वजह यह है कि performance सुधारने वाली build techniques बहुत ज्यादा हैं
हाल में astral वालों ने भी यह दिखाते हुए यही समस्या देखी कि conda-forge build ज्यादातर builds से स्पष्ट रूप से तेज़ है:
https://github.com/astral-sh/python-build-standalone/pull/54...
मौजूदा दूसरी build optimizations के साथ इस्तेमाल करने पर tail-call interpreter कैसा behave करता है, यह जानने की उत्सुकता है
लेखक ने genetic algorithm के जरिए कई compilers और optimization flag combinations आज़माए
संबंधित चर्चा:
https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-... --> https://news.ycombinator.com/item?id=42999672 (66 points | 25 days ago | 22 comments)
https://blog.reverberate.org/2025/02/10/tail-call-updates.ht... --> https://news.ycombinator.com/item?id=43076088 (124 points | 18 days ago | 92 comments)
अच्छा लेख है। एक बारीक बात ध्यान में आई
संदर्भित लेखों में से एक, https://simonwillison.net/2025/Feb/13/python-3140a5/ में लिखा था कि “3.14.0a5 benchmark में 3.13 से 1.12 गुना तेज था, मेरे बेहद overloaded M2 MacBook Pro पर”
यह हिस्सा काफी उलझाने वाला है। क्या इसका मतलब है कि benchmark ऐसे कंप्यूटर पर चलाया गया था जो दूसरे processes की वजह से overloaded था? तो क्या नतीजे पूरी तरह भरोसेमंद नहीं होंगे?
मुझे लगा था कि ऐसे benchmark बाहरी variables हटाने के लिए बहुत controlled environment में किए जाते हैं
CPython पक्ष के लिए https://github.com/faster-cpython/benchmarking-public देखें
यहां कुछ लोग 10% को “बड़ा” और 1% को “normal” कह रहे हैं, लेकिन double-recursive Fibonacci की partial inlining जैसी optimization वास्तविक workload और समय को exponential रूप से घटा सकती है
दो अंकों वाले arguments में 10x से ज्यादा, यानी हजारों percent तक भी संभव है। सख्ती से कहें तो यह problem size के बजाय recursive depth के अंतर के हिसाब से exponential है [1]
C compiler भी code inlining metrics पर बहुत संवेदनशील प्रतिक्रिया दे सकते हैं, इसलिए वह जबरदस्त speedup वास्तव में दिखता है या नहीं, यह code के रूप पर बहुत निर्भर हो सकता है
इसलिए समस्या का एक हिस्सा यह है कि CPU बहुत sophisticated और complex हो गए हैं, लेकिन दूसरा पहलू यह है कि
-O0या-O1से आगे के compiler भी sophisticated और complex हो गए हैंयह लेख अच्छा है और पढ़ने लायक है, लेकिन यह उन अनगिनत उदाहरणों में से एक भी है जहां दो complex चीजें interact करती हैं तो बहुत चौंकाने वाले नतीजे आ सकते हैं। यह बात computing के बाहर भी सच है
लोग यह सबक कितनी भी बार दोहराया जाए, चीजों को जरूरत से ज्यादा simplify करने की मजबूत प्रवृत्ति रखते हैं
इसके अलावा, लेख में कम से कम दो CPU—Intel और Apple M1—और दो compiler—gcc और clang—का इस्तेमाल है, लेकिन असली deployment environment में Intel, AMD, ARM की कहीं ज्यादा generations और implementations, और दूसरे compiler हो सकते हैं। यानी कुल complexity के बहुत छोटे हिस्से का ही sample लिया गया है
ज्यादा scientific होने के लिए, खासकर “1.01x” जैसे अंतर में, time measurements में किसी न किसी रूप में error bars होने चाहिए। mean का standard deviation, या ऐसे मामले में minimum value का standard deviation शायद बेहतर हो [2]
measurement error घटाने के लिए OS में CPU core fixed scheduling भी शायद जरूरी होगी
[1] https://stackoverflow.com/questions/360748/computational-com...
[2] https://github.com/c-blake/bu/blob/main/doc/tim.md
हाल ही में मैंने Python 3.9 से 3.13 तक benchmark किया, और 3.11 तक performance लगातार बेहतर होती गई
लेकिन Python 3.12 और 3.13 3.11 से करीब 10% धीमे थे
मुझे लगा कि मेरा खुद का benchmark पर्याप्त अच्छा नहीं है, फिर भी इसे core service में deploy करके देखा, और collect किए गए metrics में भी वही बदलाव दिखा
क्या किसी और ने भी यही समस्या देखी है?
[0]: https://github.com/python/cpython/issues/123540