]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-132952: Speed up startup by importing _io instead of io (#132957)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Mon, 28 Apr 2025 15:38:56 +0000 (08:38 -0700)
committerGitHub <noreply@github.com>
Mon, 28 Apr 2025 15:38:56 +0000 (08:38 -0700)
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/io.py
Lib/site.py
Lib/test/test_io.py
Lib/test/test_site.py
Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst [new file with mode: 0644]
Modules/_io/_iomodule.c
Python/pylifecycle.c

index 5485d0bd64f3f109019b90cf2b9abafd270633e0..e412db1de68f8b0dd1eb5fa0daec81a225cbda76 100644 (file)
@@ -1023,6 +1023,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(io));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_struct));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
index 3ce192511e387947c5a11e780ce2507cd3d298bd..2a6c2065af6bb90fe75df815812baaadfe819386 100644 (file)
@@ -514,6 +514,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(intern)
         STRUCT_FOR_ID(intersection)
         STRUCT_FOR_ID(interval)
+        STRUCT_FOR_ID(io)
         STRUCT_FOR_ID(is_running)
         STRUCT_FOR_ID(is_struct)
         STRUCT_FOR_ID(isatty)
index 5c95d0feddecbaa7d5ad14c51406d7778d419cf7..2368157a4fd18bd8b1c21081dd0e63f616373907 100644 (file)
@@ -1021,6 +1021,7 @@ extern "C" {
     INIT_ID(intern), \
     INIT_ID(intersection), \
     INIT_ID(interval), \
+    INIT_ID(io), \
     INIT_ID(is_running), \
     INIT_ID(is_struct), \
     INIT_ID(isatty), \
index a1fc9736d66618730bd4b13776a45959d6a02172..72c3346328a5527b9e8582b214f6c318b54c2711 100644 (file)
@@ -1844,6 +1844,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(io);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(is_running);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
index e9fe619392e3d90126b5c41fbdccbaacecb88632..63ffadb1d385f99897b9dc9adfccfcb9eff40947 100644 (file)
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -60,9 +60,6 @@ from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
                  IncrementalNewlineDecoder, text_encoding, TextIOWrapper)
 
 
-# Pretend this exception was created here.
-UnsupportedOperation.__module__ = "io"
-
 # for seek()
 SEEK_SET = 0
 SEEK_CUR = 1
index 9da8b6724e1cec5778bb9689487a367622debf99..5c38b1b17d5abdaaea1185c534798ee1ff6c91d1 100644 (file)
@@ -73,7 +73,7 @@ import sys
 import os
 import builtins
 import _sitebuiltins
-import io
+import _io as io
 import stat
 
 # Prefixes for site-packages; add additional prefixes like /usr/local here
index ac3b6d131e7dadc92c4c8b906803d378637610d1..545643aa455a5b3ca830bc375418105db6789907 100644 (file)
@@ -445,6 +445,25 @@ class IOTest(unittest.TestCase):
             self.assertRaises(exc, fp.seek, 1, self.SEEK_CUR)
             self.assertRaises(exc, fp.seek, -1, self.SEEK_END)
 
+    @support.cpython_only
+    def test_startup_optimization(self):
+        # gh-132952: Test that `io` is not imported at startup and that the
+        # __module__ of UnsupportedOperation is set to "io".
+        assert_python_ok("-S", "-c", textwrap.dedent(
+            """
+            import sys
+            assert "io" not in sys.modules
+            try:
+                sys.stdin.truncate()
+            except Exception as e:
+                typ = type(e)
+                assert typ.__module__ == "io", (typ, typ.__module__)
+                assert typ.__name__ == "UnsupportedOperation", (typ, typ.__name__)
+            else:
+                raise AssertionError("Expected UnsupportedOperation")
+            """
+        ))
+
     @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
     def test_optional_abilities(self):
         # Test for OSError when optional APIs are not supported
index 16cf25798a73a32901906d22e2697e9955d05b53..a7e9241f44d243f0a412778e7448fb590ecd9c8e 100644 (file)
@@ -8,6 +8,7 @@ import unittest
 import test.support
 from test import support
 from test.support.script_helper import assert_python_ok
+from test.support import import_helper
 from test.support import os_helper
 from test.support import socket_helper
 from test.support import captured_stderr
@@ -574,6 +575,17 @@ class ImportSideEffectTests(unittest.TestCase):
             code = e.code
         self.assertEqual(code, 200, msg="Can't find " + url)
 
+    @support.cpython_only
+    def test_lazy_imports(self):
+        import_helper.ensure_lazy_imports("site", [
+            "io",
+            "locale",
+            "traceback",
+            "atexit",
+            "warnings",
+            "textwrap",
+        ])
+
 
 class StartupImportTests(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst
new file mode 100644 (file)
index 0000000..2792ce3
--- /dev/null
@@ -0,0 +1,4 @@
+Speed up startup with the ``-S`` argument by importing the
+private ``_io`` module instead of :mod:`io`. This fixes a performance
+regression introduced earlier in Python 3.14 development and restores performance
+to the level of Python 3.13.
index bd4d994454dfcd00f697262ab4f81682c19f5cd9..50fe5d50c91a35ed4e176b9a17836b0d1cd9cffe 100644 (file)
@@ -661,6 +661,11 @@ iomodule_exec(PyObject *m)
         "UnsupportedOperation", PyExc_OSError, PyExc_ValueError);
     if (state->unsupported_operation == NULL)
         return -1;
+    if (PyObject_SetAttrString(state->unsupported_operation,
+                               "__module__", &_Py_ID(io)) < 0)
+    {
+        return -1;
+    }
     if (PyModule_AddObjectRef(m, "UnsupportedOperation",
                               state->unsupported_operation) < 0)
     {
index 1b9832bff17ba590508dbb4cf9d4150a0c539dbd..0871e147169328465191079c8c2cf1429e0d126c 100644 (file)
@@ -2755,7 +2755,7 @@ init_set_builtins_open(void)
         goto error;
     }
 
-    if (!(wrapper = PyImport_ImportModuleAttrString("io", "open"))) {
+    if (!(wrapper = PyImport_ImportModuleAttrString("_io", "open"))) {
         goto error;
     }
 
@@ -2800,7 +2800,7 @@ init_sys_streams(PyThreadState *tstate)
     }
 #endif
 
-    if (!(iomod = PyImport_ImportModule("io"))) {
+    if (!(iomod = PyImport_ImportModule("_io"))) {
         goto error;
     }