1 पॉइंट द्वारा GN⁺ 2024-03-12 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • CPython PR #116338 ने free-threaded build में PYTHON_GIL=0 या -X gil=0 के जरिए GIL को disable करने वाला बदलाव python:main में merge किया
  • runtime में GIL को फिर से enable करने की संभावना बनाए रखने के लिए GIL-संबंधित data structures को सामान्य तरीके से initialize किया जाता है, और startup पर एक flag सेट करके take_gil() और drop_gil() को जल्दी return कराया जाता है
  • शुरुआती जांच में PYTHON_GIL=0 सेटिंग के साथ thread का उपयोग न करने वाले कुछ tests और छोटे programs सामान्य रूप से चले, और बहुत basic thread programs कभी-कभी चले, लेकिन पूरा test suite test_asyncio में जल्दी crash हो गया
  • review के दौरान PYTHON_GIL tests, documentation, -X gil option, और sys.flags में इसका reflection जोड़ा गया, और PYTHON_GIL=1 के जरिए GIL enable को force करने के लिए setting handling भी ठीक की गई
  • आगे के काम को incompatible extensions load करते समय GIL को फिर से enable करने और GIL को default रूप से disable करने वाले मुद्दों में अलग किया गया, और यह बदलाव Python 3.13 के free-threaded build में GIL control surface जोड़ता है

merge किया गया बदलाव

  • CPython PR #116338, gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 बदलाव को कवर करता है
  • colesbury ने इसे 11 मार्च 2024 को python:main में merge किया
  • बदलाव का आकार 12 files, 163 lines added, 1 line removed के रूप में दिखाया गया
  • target feature सामान्य build के लिए नहीं, बल्कि free-threaded build में GIL को disable करने वाला execution option है

GIL disable करने का तरीका

  • free-threaded build में नीचे दी गई settings से GIL को disable किया जा सकता है
    • PYTHON_GIL=0
    • -X gil=0
  • runtime में GIL को फिर से enable किया जा सके, इसके लिए सभी GIL-संबंधित data structures को सामान्य रूप से initialize किया जाता है
  • असली disable startup पर flag सेट करके किया जाता है
    • इसी flag की वजह से take_gil() और drop_gil() जल्दी return करते हैं
  • review के दौरान PYTHON_GIL=1 होने पर enable_gil को सही तरह सेट करने वाला commit भी जोड़ा गया

tests और मौजूदा सीमाएँ

  • PYTHON_GIL=0 setting के साथ कुछ tests और छोटे programs की जांच की गई
    • जो tests और छोटे programs threads का उपयोग नहीं करते, वे सामान्य रूप से चलते पाए गए
    • बहुत basic thread programs कभी-कभी चले
  • पूरा test suite जल्दी crash हो गया, और स्थान test_asyncio के रूप में दर्ज किया गया
  • !buildbot nogil command से NoGIL-संबंधित builder tests कई बार schedule किए गए
    • x86-64 MacOS Intel ASAN NoGIL PR
    • x86-64 MacOS Intel NoGIL PR
    • ARM64 MacOS M1 Refleaks NoGIL PR
    • ARM64 MacOS M1 NoGIL PR
    • AMD64 Ubuntu NoGIL Refleaks PR
    • AMD64 Ubuntu NoGIL PR
    • AMD64 Windows Server 2022 NoGIL PR

review के दौरान जोड़ी गई अतिरिक्त scope

  • corona10 ने सुझाव दिया कि Lib/test/test_cmd_line.py में environment variable test जोड़ना उपयोगी होगा
  • इसके बाद नीचे दिए गए commits जोड़े गए
    • Add test for PYTHON_GIL in test_cmd_line
    • Set enable_gil properly when PYTHON_GIL=1
    • Don't add 'enable_gil' to test_embed in normal builds
  • colesbury का मानना था कि environment variable जोड़ते समय documentation भी करना अच्छा होगा
    • इसका आधार यह था कि --disable-gil configure flag पहले से documented है
    • documentation में यह शामिल होना चाहिए कि यह सिर्फ free-threaded build में उपलब्ध है, 0 GIL disable को force करता है, 1 GIL enable को force करता है, और यह Python 3.13 में नया है
  • इसके बाद Document PYTHON_GIL environment variable commit जोड़ा गया

-X gil option जोड़ना और अंतिम merge

  • Discord चर्चा के बाद environment variable के साथ उपयोग करने के लिए -X option भी जोड़ने का फैसला किया गया
  • PR title को केवल PYTHON_GIL=0 तक सीमित रूप से बदलकर PYTHON_GIL=0 or -X gil=0 तक शामिल किया गया
  • अतिरिक्त commits में नीचे की बातें शामिल थीं
    • Add -X gil option, add to sys.flags, modify test to cover env var… and option
    • Fix link to -X gil
    • Fix PYTHON_GIL versionchanged line
    • Clarify test_flags in normal builds
  • ericsnowcurrently, erlend-aasland, corona10, colesbury ने बदलाव को approve किया
  • merge commit 2731913 है, और merge के बाद vstinner ने इस बदलाव पर प्रतिक्रिया दी: “दिलचस्प और बहुत डरावना”

आगे का काम

  • follow-up issues के रूप में दो काम अलग किए गए
    • #116322: incompatible extensions load करते समय GIL को फिर से enable करने का काम
    • #116329: GIL को default रूप से disable करने का काम
  • मौजूदा PR GIL default बदलने के लिए नहीं है, बल्कि free-threaded build में user को environment variable या -X option के जरिए GIL की स्थिति control करने देने वाला बदलाव है

1 टिप्पणियां

 
GN⁺ 2024-03-12
Hacker News की राय
  • no-GIL काम में दिलचस्पी रखने वालों के लिए कुछ अतिरिक्त लिंक छोड़ रहा/रही हूँ: [0], [1]
    [0] Multithreaded Python without the GIL
    https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsD...
    [1] Github repo
    https://github.com/colesbury/nogil

  • यह देखने की उत्सुकता है कि मूल Python को कितना और तेज़ बनाया जा सकता है। इस समस्या को कम करने की कोशिश करने वाले टूल बहुत ज़्यादा हो गए हैं, जिससे Python का value proposition भी चुनौती में पड़ रहा है
    speed सुधारने वाले टूल के तौर पर Mojo, pytorch, triton, numba, taichi याद आते हैं। इस समस्या को हल करने की इतनी कोशिशें हैं कि पिछली बार जब एक इस्तेमाल करके देखना चाहा, तो विकल्पों की भरमार से overwhelmed हो गया/गई। आखिर में taichi चुना, और वह काफ़ी मज़ेदार और इस्तेमाल में आसान था, लेकिन उसका scope थोड़ा सीमित था

    • Mojo को Python ecosystem पर हमला माना जाना चाहिए, क्योंकि वह Python का superset है। Python इस्तेमाल किया जा सकता है, लेकिन वह खुद Python नहीं है
      Taichi सच में कम आंका गया है। यह Metal सहित सभी platforms पर चलता है, इसके बहुत उदाहरण हैं, और code लिखना भी आसान है। सबसे बढ़कर, यह ecosystem के साथ integrate होता है और मौजूदा ecosystem को replace नहीं करता
      https://github.com/taichi-dev
      Taichi से क्या किया जा सकता है यह दिखाने वाला बेहतरीन demo video: https://www.youtube.com/watch?v=oXRJoQGCYFg
      https://www.youtube.com/watch?v=WNh4Q7-OSJs
      https://www.taichi-lang.org/
  • https://peps.python.org/pep-0703/ में बताए गए biased reference counting तरीके में सिर्फ single-thread affinity क्यों है, और किसी दूसरे thread से access होने पर atomic increment/decrement क्यों चाहिए, यह समझना चाहता/चाहती हूँ
    दूसरे implementations में, जैसे biased reference counting लागू करने वाली कई Rust crates में, मैंने यह तरीका देखा है कि नए thread में ले जाते समय ही atomic increment किया जाता है, और वह thread फिर 0 तक पहुँचने तक non-atomic increment/decrement करता है, अंत में atomic decrement करता है। क्या इसकी वजह यह है कि यह मौजूदा system पर जोड़ा जा रहा है, इसलिए एक single PyObject है और उसे नए thread-local object की तरफ point कराने के लिए replace नहीं किया जा सकता?

    • आगे चलकर CPython में ownership transfer implement किया जा सकता है, लेकिन यह थोड़ा ज़्यादा पेचीदा है
      Rust में ownership transfer के लिए "move" language का हिस्सा है, लेकिन C या Python में इसके बराबर कोई concept नहीं है, इसलिए यह तय करना मुश्किल है कि ownership कब transfer करनी चाहिए और कौन सा thread नया owner होना चाहिए। heuristics इस्तेमाल किए जा सकते हैं। उदाहरण के लिए, किसी object को queue.SimpleQueue में डालते समय ownership छोड़ी या transfer की जा सकती है, लेकिन तब भी पहले से यह जानना मुश्किल है कि queue में पड़े object को कौन सा thread "get" करेगा
      performance benefit भी शायद छोटा होगा। कई objects सिर्फ एक ही thread से access होते हैं, और कुछ objects कई threads से access होते हैं, लेकिन ऐसे objects दुर्लभ हैं जो पहले सिर्फ एक thread से exclusively access हों और बाद में सिर्फ किसी दूसरे thread से exclusively access हों
  • पहले tranched bread की खबर पढ़ी, और अब यह भी? कमाल का दौर है
    Unladen Swallow project [1] के ठंडा पड़ जाने पर थोड़ा अफ़सोस हुआ था। Python को फिर से core optimization path पर लौटते देखकर अच्छा लग रहा है
    [1] https://en.wikipedia.org/wiki/CPython#Unladen_Swallow

  • इसे ऐसे समझा दें जैसे मैं पाँच साल का हूँ
    GIL क्या है, यह conceptually समझता/समझती हूँ। लेकिन इस बदलाव का असर क्या होगा? क्या अब overall performance improvement की उम्मीद करते हुए packages टूटने लगेंगे?

    • पहले GIL की वजह से सच में multi-threaded Python लगभग लिखा ही नहीं जाता था। threads का उपयोग मुख्यतः ऐसे कई tasks सँभालने के लिए होता था जो independent I/O पर block हो सकते थे, और यह बेशक आम और उपयोगी है, लेकिन CPU-focused Python code की performance में मदद नहीं करता था
      heavy CPU workload न भी हो, तब भी यह बदलाव उपयोगी हो सकता है। आजकल बहुत सा code Python की native asyncio language features से लिखा जाता है। यह NodeJS की तरह async/await के जरिए execution yield करते हुए single thread पर चलता है, और single thread से ही प्रति सेकंड हजारों requests तक का काफ़ी अच्छा throughput दे सकता है
      लेकिन बड़ी समस्या यह है कि कोई भी CPU task करते ही वह बाकी सभी coroutines को block कर देता है, जिससे तरह-तरह की अस्पष्ट समस्याएँ पैदा होती हैं और requests per second खराब हो जाता है। उदाहरण के लिए, किसी एक coroutine में random I/O timeout दिख सकता है, जबकि असली कारण यह हो सकता है कि पूरी तरह अलग coroutine ने थोड़ी देर CPU पकड़े रखा। ऐसा क्यों हो रहा है, observe करना भी बहुत मुश्किल है। asyncio blocking काम को main thread से बाहर ले जाने में मदद के लिए asyncio.to_thread() function [1] देता है, लेकिन GIL की वजह से CPU-focused काम को दूसरे coroutines में interfere करने से सच में अलग नहीं कर पाता
      [1] https://docs.python.org/3/library/asyncio-task.html#asyncio....
    • अगर कोई package GIL पर निर्भर करता है, तो GIL enabled हो जाता है। package नहीं टूटेगा
  • जिनके लिए जानना उपयोगी हो, GIL का मतलब Global Interpreter Lock है

  • यहाँ bigger picture को अच्छे से summarize करने वाला कोई resource है क्या?

  • आखिरकार कई tools के benchmarks का इंतज़ार है