6 ms·
Show HN: Nimic – Pure Python as a systems language with AOT compilation
- dima-quant 3mo agonimic is a lightweight pure Python package that emulates Nim types and constructions, making it straightforward to transpile to Nim and compile AOT. Key principle: nimic code is valid Python that runs unmodified in CPython and also transpiles to equivalent Nim code. Because nimic code is just standard Python with type hints and ctypes shims, it is a fully valid CPython script, so you can use the Python REPL during development, drop a breakpoint in the middle of a heavy algorithmic loop and inspect the variables natively. Zero Lock-In: You don't need a special runtime engine. If the Nim compiler is not available, your script still runs (albeit slower than standard Python due to some emulation overhead) on any machine with Python installed. Seamless Distribution: You can use this to develop high-performance logic natively in Python, debug it with Python tooling, and then compile to a native executable or C-extension via Nim. Why Nim? Its syntax maps well to Python, it is rather clear how to emulate its constructions in Python, and its performance is comparable to C (as it compiles to C). Port of the "trace-of-radiance" Nim project to nimic can be found in "ndsl_raytracer" in my GitHub repo (dima-quant). With the compiled executable the render time for a single 512x288 scene dropped from many hours in Python to just 10 minutes on a single M1 CPU core. The repo also includes nimic ppm to mp4 converter. Similar projects: - Pyccel (https://github.com/pyccel/pyccel https://github.com/pyccel/pyccel): Python extension language using accelerators - SPy (https://github.com/spylang/spy https://github.com/spylang/spy) is a variant of Python specifically designed to be statically compilable while retaining a lot of the "useful" dynamic parts of Python. - Codon (https://github.com/exaloop/codon https://github.com/exaloop/codon) is a high-performance Python implementation that compiles to native machine code without any runtime overhead. It is still work in progress, e.g. there is no JIT and multiprocessing support yet, but now I'm not sure what functionality would be best to implement next. Any suggestions?
- IshKebab 3mo agoI would start with a human-written README and benchmarks?
- dima-quant 3mo agoYeh, indeed, the README was mostly generated, though the introduction is largely human-written :-) Is some specific description missing? I can provide more practical code examples directly in README. The benchmarks are mostly on runtime performance I assume?
- IshKebab 3mo ago> Is some specific description missing? No, the problems are a) you get the same exact AI "voice" that is tedious to keep reading, b) it's verbose and focuses on the wrong things (the whole Module Architecture section doesn't belong there), and c) it's a sign that it's slop and not well tested. > The benchmarks are mostly on runtime performance I assume? You tell me! You (or Claude) make performance claims - "aiming to get C-level performance without leaving Python". Does it actually get anywhere near that claim?
- dima-quant 3mo agoFully fair, I'd need to spend more time on the text. The nimic was mostly tested on the raytracer project dima-quant/ndsl_raytracer, and the performance was indeed quite near the C-level.
- graemep 3mo agoSo the big advantage of nimic is that code remains valid Python and all valid Python cam be compiled? The transpile to a language that transpiles to C approach is unusual. Downsides of that other than slower compilation?
- dima-quant 3mo agoNot quite "all valid Python." Just to be clear: nimic is a strict, statically typed subset of Python. At this stage, the downside of having Nim as an intermediate step is not really a slower compilation, as Nim compiler is very fast, but more like: debugging executable, might involve line numbers of the intermediate Nim or C source code; the need to install Nim compiler for development; memory management (currently it is ORC/ARC or manual in Nim, no borrow checker yet)
- graemep 3mo agoSorry, missed the word "subset"!
- delijati 3mo agoso it is what https://github.com/mypyc/mypyc https://github.com/mypyc/mypyc was supposed to be ... i can just use write my python code add some type hints and it should run faster ... like cython but without learning a new dsl?
- dima-quant 3mo agoYeah, indeed, I actually read about mypyc a few years ago before starting nimic. Though the goal is very similar, mypyc does not support compilation to a native executable and the performance gains were somehow limited to 2x-4x, as reported in https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-1/ https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-p...
- deleted 3mo ago[deleted]
- dec0dedab0de 3mo agocython uses type hints now
- YuechenLi 3mo agoIf it's possible and you intended for it to be a long-term project, I would suggest looking at designing an MIR for the Python to lower to and then lowering it to Nim, and that is probably the most valuable feature you can add to Nimic. That's what Rust had to learn the hard way, and that's how LLVM works, and having an intermediate representation means your compiler is going run into a lot less edge cases during development.
- dima-quant 3mo agoCurrently, Nim fits quite well with Python but there is a project to directly emit LLVM IR from Nim and MIR might be relevant there: https://github.com/arnetheduck/nlvm https://github.com/arnetheduck/nlvm
- YuechenLi 3mo agoOh, I wasn't referring to emitting LLVM IR directly from Nim, but that you should consider adding an MIR layer between your Nimic subset of Python and Nim instead of emitting Nim from Python directly. Otherwise, compilation would get difficult and adding new supported Python will get painful.
- actionfromafar 3mo agoSimilar idea as Shedskin which is a (large subset of) Python to C++ transpiler https://github.com/shedskin/shedskin https://github.com/shedskin/shedskin
- dima-quant 3mo agoGood suggestion, now I recall I heard about this project before. Good to see it is in active development.
- vshulcz 3mo agoThe line I'd push on is "valid Python that runs unmodified in CPython." True at the syntax level, but the speedup isn't really coming from Nim as a backend, it comes from how much of Python's dynamism you can pin down at compile time. Refcounting semantics, __getattr__, values that change type at runtime, isinstance-based dispatch, monkeypatching in tests: the moment you statically commit to any of those, you've defined a subset, and the subset is the actual product, not the transpiler. I've spent a fair bit of time generating specialized straight-line code for hot Python paths, killing the per-call attribute and dict lookups the interpreter does. The lesson was that dispatch-bound code claws back most of its overhead without ever leaving CPython. Where AOT-to-native actually pulls ahead is numeric and loop-bound work, where the interpreter loop and boxing dominate. Your 512x288 render is exactly that case, which is why it looks so strong. So the benchmark I'd want isn't render time, it's what fraction of a real module transpiles with no rewrites. That number tells me whether this is a systems language or a fast path I have to hand-shape around. Codon and Shedskin both hit that wall. Curious where Nimic draws the line.
- dima-quant 3mo agoTrue, nimic is a statically typed Python subset without much of its dynamism with the aim to be foremost an efficient systems language. Though in nimic, without using isinstance, the instance-based dispatch is realised via variant types. Yes, emulating the Nim constructions in Python was the hardest part, while making the transpiler was straightforward. Indeed, the AOT compilation leads to great speed-ups for heavy custom numeric calculations that cannot be easily vectorized in numpy, such as the raytracing logic. In cases when most of calculations are performed in an external module (written in e.g. C, Rust or Cython etc) the performance gain might be much less, but the added value here is that the high performance module itself can be written in nimic, keeping the codebase purely in Python and consolidating the codebase. When starting with a "pythonic" code, rewrites in nimic can be substantial but so would be a rewrite in a systems language like C or Rust. Besides allowing to optimise the fast path, nimic provides low level functionality, such as pointers to pointers and bitwise operations that actually executes within CPython, for example, as in mp4 muxer implementation in dima-quant/ndsl_raytracer/src/nraytracer/minimp4.py
- frumiousirc 3mo agoWhat is "AOT"?