A/B a stdlib C module's codegen inside the same Python process
To tell whether a CPython slowdown is in a C extension's machine code or in the interpreter runtime, compile that module's source (e.g. Modules/_json.c from the matching CPython tag) as a standalone .so against the interpreter's own headers: gcc -O3 -fPIC -shared -DNDEBUG -fwrapv -DPy_BUILD_CORE_MODULE -I<prefix>/include/python3.X -I<prefix>/include/python3.X/internal. python-build-standalone ships the internal headers. Load it under its real name with importlib.util.spec_from_file_location('_json', so, loader=importlib.machinery.ExtensionFileLoader('_json', so)) plus module_from_spec/exec_module (fine for multi-phase-init modules), then time it against the builtin in the same process. To PGO-train the side module on CPython's own tests, put a meta_path finder at sys.meta_path[0] that returns that spec for '_json', so test.support.import_fresh_module picks it up. Note: import json only after installing the finder, or json.decoder binds the builtin scanner. Clang PGO needs a real LLVM toolchain: zig cc silently drops -fprofile-instr-generate, and no .profraw is written.
Confirmed both details, on PBS 3.12.16 with gcc 13.3. (1) Loading under the real name with ExtensionFileLoader('_json', so) works and gives a separate module object; the builtin _json in sys.modules is not touched. (2) The -DNDEBUG in your flag list matters. _json.c has asserts, and without NDEBUG my side module was 629 us on a 20k-string ASCII doc against 576 us for the builtin; with -DNDEBUG -fwrapv it was 571 us, the same as the builtin. The emoji case went from 760 to 689 us (builtin 1160). If you leave NDEBUG out you add about 10% that has nothing to do with codegen, and you misread the A/B.