{"id":16346424,"url":"https://github.com/milesgranger/gilknocker","last_synced_at":"2026-01-07T10:19:50.410Z","repository":{"id":116638826,"uuid":"589915764","full_name":"milesgranger/gilknocker","owner":"milesgranger","description":"Is the GIL seeing someone else? How's about repetitively calling and seeing how long it takes to answer?","archived":false,"fork":false,"pushed_at":"2023-10-19T05:26:10.000Z","size":66,"stargazers_count":10,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-04-24T04:47:39.005Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/milesgranger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-17T08:41:07.000Z","updated_at":"2024-06-20T19:06:16.608Z","dependencies_parsed_at":null,"dependency_job_id":"26ec371d-5e4d-47ac-8368-048fc65cb67d","html_url":"https://github.com/milesgranger/gilknocker","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milesgranger%2Fgilknocker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milesgranger%2Fgilknocker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milesgranger%2Fgilknocker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milesgranger%2Fgilknocker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/milesgranger","download_url":"https://codeload.github.com/milesgranger/gilknocker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244092755,"owners_count":20396866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T00:35:18.604Z","updated_at":"2026-01-07T10:19:50.362Z","avatar_url":"https://github.com/milesgranger.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"## GIL Knocker\n\n\n`pip install gilknocker`\n\n\n[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)\n[![CI](https://github.com/milesgranger/gilknocker/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/milesgranger/gilknocker/actions/workflows/CI.yml)\n[![PyPI](https://img.shields.io/pypi/v/gilknocker.svg)](https://pypi.org/project/gilknocker)\n![PyPI - Wheel](https://img.shields.io/pypi/wheel/gilknocker)\n[![Downloads](https://pepy.tech/badge/gilknocker/month)](https://pepy.tech/project/gilknocker)\n\n\nWhen you thought the GIL was available, and you find yourself suspecting it might be spending time\nwith another. \n\nYou probably want [py-spy](https://github.com/benfred/py-spy), however if you're\nlooking for a quick-and-dirty way to slip in a GIL contention metric within a specific\nchunk of code, this might help you.\n\n### How?\n\nUnfortunately, there doesn't appear to be any explicit C-API for checking how busy\nthe GIL is. [PyGILState_Check](https://docs.python.org/3/c-api/init.html#c.PyGILState_Check) \nwon't really work, that's limited to the current thread. \n[PyInterpreterState](https://docs.python.org/3/c-api/init.html#c.PyGILState_Check) \nis an opaque struct, and the [PyRuntimeState](https://github.com/python/cpython/blob/main/Include/internal/pycore_pystate.h)\nand other goodies are private in CPython.\n\nSo, in ~200 lines of Rusty code, I've conjured up a basic metric that seems \nto align with what is reported by `py-spy` when running the same [test case](./tests/test_knockknock.py).\nThis works by spawning a thread which, at regular intervals, re-acquires the GIL and checks \nhow long it took for the GIL to answer.\n\nNote, the `polling_interval_micros`, `sampling_interval_micros`, and `sleeping_interval_micros` \nare configurable. \n\n- `polling_interval_micros`\n  - How frequently to re-acquire the GIL and measure how long it took to acquire. The more frequent, the\n    more likely the contention metric will represent accurate GIL contention. A good value for this is 1-1000.\n\n- `sampling_interval_micros`\n  - How _long_ to run the polling routine. If this is 1ms, then for 1ms it will try to re-acquire the GIL \n    at `polling_interval_micros` frequency. Defaults to 10x `polling_interval_micros`\n\n- `sleeping_interval_micros`\n  - How long to sleep between sampling routines. Defaults to 100x `polling_interval_micros`\n\n\n### Use\n\nLook at the [tests](./tests)\n\n```python\n\nfrom gilknocker import KnockKnock\n\n# These two are equivalent. \nknocker = KnockKnock(1_000) \nknocker = KnockKnock(\n  polling_interval_micros=1_000, \n  sampling_interval_micros=10_000, \n  sleeping_interval_micros=100_000\n)\nknocker.start()\n\n... smart code here ...\n\nknocker.contention_metric  # float between 0-1 indicating roughly how busy the GIL was.\nknocker.reset_contention_metric()  # reset timers and meteric calculation\n\n... some more smart code ...\n\nknocker.stop()\nknocker.stop()  # Idempodent stopping behavior\n\nknocker.contention_metric  # will stay the same after `stop()` is called.\n\nknocker.is_running  # If you're ever in doubt\n\n```\n\n### How will this impact my program?\n\nShort answer, it depends, but probably not much. As stated above, the more frequent the \npolling and sampling interval, the more likely non-GIL bound programs will be affected, since there is \nmore room for contention. In GIL heavy programs, the monitoring thread will spend most of its \ntime simply waiting for a lock. This is demonstrated in the [benchmarks](./benchmarks) testing.\n\nIn general, it appears that `polling_interval_micros=1_000` is a good tradeoff in terms of accurate\nGIL contention metric and the resulting `sampling_interval_micros=10_000` (defaults to 10x polling interval)\nis high enough to relax performance impact a bit when combined with `sleeping_interval_micros=100_000` \n(defaults to 100x polling interval); but feel free to play with these to conform to your needs.\n\nBelow is a summary of benchmarking two different \nfunctions, one which uses the GIL, and one which releases it. For `interval=None` this means \nno polling was used, effectively just running the function without `gilknocker`. Otherwise, \nthe interval represents the value passed to `KnockKnock(polling_interval_micros=interval)`\n\n`python -m pytest -v --benchmark-only benchmarks/ --benchmark-histogram`\n\n```\n------------------------------------------------------------------------------------ benchmark: 18 tests -------------------------------------------------------------------------------------\nName (time in s)                       Min               Max              Mean            StdDev            Median               IQR            Outliers     OPS            Rounds  Iterations\n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\ntest_bench[a_little_gil-100000]     1.5368 (2.07)     1.6596 (2.23)     1.5968 (2.14)     0.0476 (130.12)   1.5943 (2.14)     0.0719 (140.14)        2;0  0.6262 (0.47)          5           1\ntest_bench[a_little_gil-10000]      1.5321 (2.06)     1.5989 (2.14)     1.5618 (2.09)     0.0289 (78.95)    1.5610 (2.09)     0.0510 (99.52)         2;0  0.6403 (0.48)          5           1\ntest_bench[a_little_gil-1000]       1.5246 (2.05)     1.5298 (2.05)     1.5271 (2.05)     0.0019 (5.12)     1.5269 (2.05)     0.0021 (4.00)          2;0  0.6549 (0.49)          5           1\ntest_bench[a_little_gil-100]        1.5505 (2.09)     1.5543 (2.08)     1.5528 (2.08)     0.0014 (3.96)     1.5533 (2.08)     0.0018 (3.60)          2;0  0.6440 (0.48)          5           1\ntest_bench[a_little_gil-10]         1.5863 (2.13)     1.6074 (2.16)     1.5928 (2.14)     0.0088 (23.94)    1.5896 (2.13)     0.0111 (21.60)         1;0  0.6278 (0.47)          5           1\ntest_bench[a_little_gil-None]       1.5043 (2.02)     1.5067 (2.02)     1.5051 (2.02)     0.0011 (2.95)     1.5044 (2.02)     0.0016 (3.17)          1;0  0.6644 (0.50)          5           1\ntest_bench[a_lotta_gil-100000]      0.7450 (1.00)     0.7458 (1.0)      0.7455 (1.0)      0.0004 (1.0)      0.7457 (1.0)      0.0005 (1.0)           1;0  1.3413 (1.0)           5           1\ntest_bench[a_lotta_gil-10000]       0.7471 (1.00)     0.8104 (1.09)     0.7601 (1.02)     0.0281 (76.94)    0.7472 (1.00)     0.0168 (32.82)         1;1  1.3156 (0.98)          5           1\ntest_bench[a_lotta_gil-1000]        0.7436 (1.0)      0.7472 (1.00)     0.7463 (1.00)     0.0015 (4.11)     0.7470 (1.00)     0.0013 (2.54)          1;1  1.3400 (1.00)          5           1\ntest_bench[a_lotta_gil-100]         0.7558 (1.02)     0.7680 (1.03)     0.7640 (1.02)     0.0050 (13.56)    0.7644 (1.03)     0.0061 (11.97)         1;0  1.3089 (0.98)          5           1\ntest_bench[a_lotta_gil-10]          0.7542 (1.01)     0.7734 (1.04)     0.7649 (1.03)     0.0084 (23.05)    0.7669 (1.03)     0.0151 (29.45)         2;0  1.3074 (0.97)          5           1\ntest_bench[a_lotta_gil-None]        0.7437 (1.00)     0.8490 (1.14)     0.8006 (1.07)     0.0501 (137.15)   0.8074 (1.08)     0.0969 (189.03)        1;0  1.2490 (0.93)          5           1\ntest_bench[some_gil-100000]         1.4114 (1.90)     1.4131 (1.89)     1.4122 (1.89)     0.0007 (1.81)     1.4121 (1.89)     0.0010 (2.00)          2;0  0.7081 (0.53)          5           1\ntest_bench[some_gil-10000]          1.4115 (1.90)     1.4258 (1.91)     1.4167 (1.90)     0.0059 (16.03)    1.4141 (1.90)     0.0083 (16.19)         1;0  0.7058 (0.53)          5           1\ntest_bench[some_gil-1000]           1.4169 (1.91)     1.5793 (2.12)     1.4618 (1.96)     0.0690 (188.82)   1.4232 (1.91)     0.0769 (150.04)        1;0  0.6841 (0.51)          5           1\ntest_bench[some_gil-100]            1.4468 (1.95)     1.6261 (2.18)     1.5701 (2.11)     0.0752 (205.83)   1.5998 (2.15)     0.1004 (195.70)        1;0  0.6369 (0.47)          5           1\ntest_bench[some_gil-10]             1.5269 (2.05)     1.9894 (2.67)     1.7037 (2.29)     0.1895 (518.49)   1.7301 (2.32)     0.2692 (524.96)        1;0  0.5870 (0.44)          5           1\ntest_bench[some_gil-None]           1.4115 (1.90)     1.4267 (1.91)     1.4155 (1.90)     0.0063 (17.33)    1.4136 (1.90)     0.0053 (10.24)         1;1  0.7065 (0.53)          5           1\n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n```\n\n![](./benchmarks/histogram.svg)\n\n---\n\n### License\n\n[Unlicense](LICENSE) or [MIT](LICENSE-MIT), at your discretion.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmilesgranger%2Fgilknocker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmilesgranger%2Fgilknocker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmilesgranger%2Fgilknocker/lists"}