Here is my quick benchmark. I refrain from using Python for most scripting/prototyping task but really like Janet [0] - here is a comparison for printing the current time in Unix epoch:
$ hyperfine --shell=none --warmup 2 "python3 -c 'import time;print(time.time())'" "janet -e '(print (os/time))'"
Benchmark 1: python3 -c 'import time;print(time.time())'
Time (mean ± σ): 22.3 ms ± 0.9 ms [User: 12.1 ms, System: 4.2 ms]
Range (min … max): 20.8 ms … 25.6 ms 126 runs
Benchmark 2: janet -e '(print (os/time))'
Time (mean ± σ): 3.9 ms ± 0.2 ms [User: 1.2 ms, System: 0.5 ms]
Range (min … max): 3.6 ms … 5.1 ms 699 runs
Summary
'janet -e '(print (os/time))'' ran
5.75 ± 0.39 times faster than 'python3 -c 'import time;print(time.time())''
[0]: https://janet-lang.org/I can't say I've ever experienced this. Are you sure it's not related to other things in the script?
I wrote a single file Python script, it's a few thousand lines long. It can process a 10,000 line CSV file and do a lot of calculations to the point where I wrote an entire CLI income / expense tracker with it[0].
The end to end time of the command takes 100ms to process those 10k lines, that's using `time` to measure it. That's on hardware from 2014 using Python 3.13 too. It takes ~550ms to fully process 100k lines as well. I spent zero time optimizing the script but did try to avoid common pitfalls (drastically nested loops, etc.).
It's because of module imports, primarily and generally. It's worse with many small files than a few large ones (Python 3 adds a little additional overhead because of needing extra system calls and complexity in the import process, to handle `__pycache__` folders. A great way to demonstrate it is to ask pip to do something trivial (like `pip --version`, or `pip install` with no packages specified), or compare the performance of pip installed in a venv to pip used cross-environment (with `--python`). Pip imports literally hundreds of modules at startup, and hundreds more the first time it hits the network.
Either way, at least on my system with cached file attributes, python can startup in 10ms, so it's not clear whether you truly need to optimize much more than that (by identifying remaining bits to optimize), versus solving the problem another way (not statting 500 files, most of which don't exist, every time you start up).
`time pip3 --version` takes 230ms on my machine.
`time pip3 --version` takes ~200ms on my machine. `time go help` takes 25, and prints out 30x more lines than pip3 --version.
Probably a decent chunk of that actually is the Python runtime starting up. I don't know what all you `import` that isn't implied at startup, though.
Another chunk might be garbage collection at process exit.
This benchmark is a little bit outdated but the problem remains the same.
Interpreter initialization: Python builds and initializes its entire virtual machine and built-in object structures at startup. Native programs already have their machine code ready and need very little runtime scaffolding.
Dynamic import system: Python’s module import machinery dynamically locates, loads, parses, compiles, and executes modules at runtime. A compiled binary has already linked its dependencies.
Heavy standard library usage: Many Python programs import large parts of the standard library or third-party packages at startup, each of which runs top-level initialization code.
This is especially noticeable if you do not run on an M1 Ultra, but on some slower hardware. From the results on Rasperberry PI 3:
C: 2.19 ms
Go: 4.10 ms
Python3: 197.79 ms
This is about 200ms startup latency for a print("Hello World!") in Python3.
Anyway, your analysis of causes reads like something AI generated and pasted in. It's awkward in the context of the rest of your post, and 2 of the 3 points are clearly irrelevant to a "hello world" benchmark.
import requests
Takes 250ms on my i9 on python 3.13A go program with
package main
import (
_ "net/http"
)
func main() {
}
takes < 10ms.Whereas with Python, even in the latest version, you're already looking at atleast 10x the amount of startup latency in practice.
Note: This is excluding the actual time that is made for the network call, which can of course also add quiete some milliseconds, depending on how far on planet earth your destination is.
Compare:
import requests
print(requests.get("http://localhost:3000").text)
to package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, _ := http.Get("http://localhost:3000")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
I get: python3: 0.08s user 0.02s system 91% cpu 0.113 total
go 0.00s user 0.01s system 72% cpu 0.015 total
(different hardware as I'm at home).I wrote another that counts the lines in a file, and tested it against https://www.gutenberg.org/cache/epub/2600/pg2600.txt
I get:
python 0.03s user 0.01s system 83% cpu 0.059 total
go 0.00s user 0.00s system 80% cpu 0.010 total
These are toy programs, but IME that these gaps stay as your programs get biggerI believe in the past people have looked at putting the standard library in a zip file instead of splatted out into a bunch of files in a dirtree. In that case, I think python would just do a few stats, find the zipfile, loaded the whole thing into RAM, and then index into the file.
"If python was implemented totally different it might be fast" - sure, but it's not!
It's tooling agnostic and there are a couple ways to generate them, but the easiest it to just use pants build.
Pants also does dependency traversal (that's the main reason we started using it, deploying a microservices monorepo) so it only packages the necessary modules.
I haven't profiled it yet for cold starts, maybe I'll test that real quick.
https://www.pantsbuild.org/dev/docs/python/overview/pex
Edit: just ran it on a hello world with py3.14 on m3 macbook pro, about 100 +/-30 ms for `python -m hello` and 300-400 (but wild variance) for executing the pex with `./hello/binary.pex`.
I'm not sure if a pants expert could eke out more speed gains and I'm also not sure if this strategy would win out with a lot of dependencies. I'm guessing the time required to stat every imported file pales in comparison to the actual load time, and with pex, everything needs to be unzipped first.
Pex is honestly best when you want to build and distribute an application as a single file (there are flags to bundle the python interpreter too).
The other option is mypyc, though again that seems to mostly speed up runtime https://github.com/mypyc/mypyc
Now if I use `python -S` (disables `import site` on initialization), that gets down to ~15ms execution time for hello world. But that gain gets killed as soon as you start trying to import certain modules (there is a very limited set of modules you can work with and still keep speedup. So if you whole script is pure python with no imports, you could probably have a 20ms cold start).
For instance `uv run` has its own fair share of overhead.
$ hyperfine --warmup 10 -L py "uv run python,~/.local/bin/python3.14,/usr/local/bin/python3.12,~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11" "{py} -c 'exit(0)'"
Benchmark 1: uv run python -c 'exit(0)'
Time (mean ± σ): 58.4 ms ± 19.3 ms [User: 26.4 ms, System: 21.7 ms]
Range (min … max): 48.2 ms … 138.0 ms 50 runs
Benchmark 2: ~/.local/bin/python3.14 -c 'exit(0)'
Time (mean ± σ): 13.3 ms ± 6.9 ms [User: 8.0 ms, System: 2.5 ms]
Range (min … max): 9.9 ms … 53.7 ms 174 runs
Benchmark 3: /usr/local/bin/python3.12 -c 'exit(0)'
Time (mean ± σ): 16.4 ms ± 7.6 ms [User: 8.9 ms, System: 3.7 ms]
Range (min … max): 12.2 ms … 65.2 ms 152 runs
Benchmark 4: ~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11 -c 'exit(0)'
Time (mean ± σ): 18.6 ms ± 7.4 ms [User: 10.0 ms, System: 5.0 ms]
Range (min … max): 14.4 ms … 63.5 ms 138 runs
Summary
~/.local/bin/python3.14 -c 'exit(0)' ran
1.23 ± 0.86 times faster than /usr/local/bin/python3.12 -c 'exit(0)'
1.40 ± 0.92 times faster than ~/.local/share/uv/python/pypy-3.11.13-macos-aarch64-none/bin/pypy3.11 -c 'exit(0)'
4.40 ± 2.72 times faster than uv run python -c 'exit(0)'On my linux system where all the file attributes are cached, it takes about 12ms to completely start, run a pass statement, and exit.
Regarding cold-starts, I strongly believe V8 snapshots are perhaps not the best way to achieve fast cold starts with Python (they may be if you are tied to using V8, though!), and will have wide side effects if you go out of the standards packages included on the Pyodide bundle.
To put some perspective: V8 snapshots are storing the whole state of an application (including it's compiled modules). This means that for a Python package that is using Python (one wasm module) + Pydantic-core (one wasm module) + FastAPI... all of those will be included in one snapshot (as well as the application state). This makes sense for browsers, where you want to be able to inspect/recover everything at once.
The issue about this design is that the compiled artifacts and the application state are bundled into one piece artifact (this is not great for AOT designed runtimes, but might be the optimal design for JITs though).
Ideally, you would separate each of the compiled modules from the state of the application. When you do this, you have some advantages: you can deserialize the compiled modules in parallel, and untie the "deserialization" from recovering the state of the application. This design doesn't adapt that well into the V8 architecture (and how it compiles stuff) when JavaScript is the main driver of the execution, however it's ideal when you just use WebAssembly.
This is what we have done at Wasmer, which allows for much faster cold starts than 1 second. Because we cache each of the compiled modules separately, and recover the state of the application later, we can achieve cold-starts that are a magnitude faster than Cloudflare's state of the art (when using pydantic, fastapi and httpx).
If anyone is curious, here is a blogpost where we presented fast-cold starts for the application state (note that the deserialization technique for Wasm modules is applied automatically in Wasmer, and we don't showcase it on the blogpost): https://wasmer.io/posts/announcing-instaboot-instant-cold-st...
Note aside: congrats to the Cloudflare team on their work on Python on Workers, it's inspiring to all providers on the space... keep it up and let's keep challenging the status quo!
A modern machine shouldn’t take this long, so likely something big is being imported unnecessarily at startup. If the big package itself is the issue, file it on their tracker.
Real question : what would you do more with the spared time ? You are that in a hurry in your life ?
You can already lazy import in python, but the new system makes the syntax sweeter and avoids having to have in-function `import module` calls, which some linters complain about.
Use lazy/dynamic imports and you will see it drop .
(Side note this is why jj is awesome. A `jj log` is almost as fast as `ls`).