Package: diojit
DIO-JIT: General-purpose Python JITImportant:DIO-JIT now works for Python >= 3.8. We heavily rely on theLOAD_METHODbytecode instruction.DIO-JIT is not production-ready. a large number of specialisation rules are required to make DIO-JIT batteries-included.This document is mainly provided for prospective developers. Users are not required to write any specialisation rules, which means that users need to learn nothing but@jit.jitandjit.jit_spec_call.BenchmarkItemPY38JIT PY38PY39JIT PY39BF265.74134.23244.50140.34append323.9410.7022.2911.21DNA READ16.9614.8215.0314.38fib(15)11.631.5410.411.51hypot(str, str)6.193.876.534.29selectsort46.9533.8838.7129.49trans24.227.7923.237.71The bechmark item "DNA READ" does not show a significant performance gain, this is because "DNA READ" heavily usesbytearrayandbytes, whose specialised C-APIs
are not exposed. In this case, although the JIT can infer the types, we have to fall back to CPython's default behaviour, or even worse: after all, the interpreter can access internal things, while we cannot.Install InstructionsStep 1: Install Julia as an in-process native code compiler for DIO-JITThere are several options for you to install Julia:scoop(Windows)julialang.org(recommended for Windows users)jill.py:pip install jill && jill install 1.6 --upstream Officialjill(Mac and Linux only!):bash -ci "$(curl -fsSL https://raw.githubusercontent.com/abelsiqueira/jill/master/jill.sh)"Step 2: Install DIO.jl in JuliaTypejuliaand open the REPL, thenjulia># press ]pkg>addhttps://github.com/thautwarm/DIO.jl# press backspacejulia>usingDIO# precompileStep 3: Install Python Packagepip install git+https://github.com/thautwarm/diojitHow to fetch latest DIO-JIT?(if you have installed DIO)pip install -U diojit
julia -e "using Pkg; Pkg.update(string(:DIO));using DIO"Usage from Python side is quite similar to that from Numba.importdiojitfrommathimportsqrt@diojit.jit(fixed_references=["sqrt","str","int","isinstance"])defhypot(x,y):ifisinstance(x,str):x=int(x)ifisinstance(y,str):y=int(y)returnsqrt(x**2+y**2)specialized_hypot=diojit.jit_spec_call(hypot,diojit.oftype(int),diojit.oftype(int))specialized_hypot(1,2)# 30% faster than CPythonDIO-JIT is a method JIT driven by abstract interpretation and call-site specialisation.
Abstract interpretation is done by the compiler (jit.absint.abs).
You can register new specialisation rules(and see examples) from (jit.absint.prescr).We're able to optimise anything!Contribution Example: Add a specialisation rule forlist.appendPython Side:importdiojitasjitimporttimeitjit.create_shape(list,oop=True)@jit.register(list,attr="append")deflist_append_analysis(self:jit.Judge,*args:jit.AbsVal):iflen(args)!=2:# rollback to CPython's default codereturnNotImplementedlst,elt=argsreturnjit.CallSpec(instance=None,# return value is not statice_call=jit.S(jit.intrinsic("PyList_Append"))(lst,elt),possibly_return_types=tuple({jit.S(type(None))}),)Julia Side:import 'Py_ListAppend' symbolcalling convention for 'Py_ListAppend':You can either do step 2) at Python side(for users other than DIO-JIT developers):importdiojitasjitfromjit.runtime.julia_rtimportjl_evaljl_implemented_intrinsic=b"""function PyList_Append(lst::Ptr, elt::PyPtr)if ccall(PyAPI.PyList_Append, Cint, (PyPtr, PyPtr), lst, elt) == -1return Py_NULLendnothing # automatically maps to a Python NoneendDIO.DIO_ExceptCode(::typeof(PyList_Append)) != Py_NULL"""jl_eval(jl_implemented_intrinsic)You immediately get a >100%time speed up:@jit.jitdefappend3(xs,x):xs.append(x)xs.append(x)xs.append(x)jit_append3=jit.jit_spec_call(append3,jit.oftype(list),jit.Top)# 'Top' means 'Any'xs=[1]jit_append3(xs,3)print("test jit_append3, [1] append 3 for 3 times:",xs)# test jit func, [1] append 3 for 3 times: [1, 3, 3, 3]xs=[]%timeitappend3(xs,1)# 293 ns Â± 26.2 ns per loop (mean Â± std. dev. of 7 runs, 1000000 loops each)xs=[]%timeitjit_append3(xs,1)# 142 ns Â± 14.9 ns per loop (mean Â± std. dev. of 7 runs, 10000000 loops each)Why Julia?We don't want to maintain a C compiler, and callinggccor others will introduce cross-process IO, which is slow.
We prefer compiling JITed code with LLVM, andJulia is quite a killer tool for this use case.Current LimitationsSupport for*varargsand**kwargsare not ready: we do can immediately support them with very tiny JIT performance gain, but considering backward compatibility we decide not to do this.Exception handling is not yet supported inside JIT functions.Why?We haven't implemented the translation from exception handling bytecode to untyped DIO IR (jit.absint.abs.In_Stmt).Will support?Yes.In fact, now a callsite in any JIT function can raise an exception. It will not be handled by JIT functions, instead, it is lifted up to the root call, which is a pure Python call.Exception handling will be supported when we have efforts on translating CPython bytecode about exception handling into untyped DIO IR (jit.absint.abs.In_Stmt).P.S: This will be finished simultaneously with the support forforloop.Support forforloop is missing.Why?Firstly, in CPython,forloop relies on exception handling, which is not supported yet.Secondly, we're considering a fast path forforloop, maybe proposing a__citer__protocol for faster iteration for JIT functions, which requires communications with Python developers.Will support?Yes.This will be finished simultaneously with support for exception handling (fasterforloop might come later).Closure support is missing.Why?In imperative languages, closures usecellstructures to achieve mutable free/cell variables.However, a writable cell makes it hard to optimise in a dynamic language.We recommend usingtypes.MethodTypeto create immutable closuresï¼Œwhich can be highly optimised in DIO-JIT(near future).importtypesdeff(freevars,z):x,y=freevarsreturnx+y+zdefhof(x,y):returntypes.MethodType(f,(x,y))Will support?Still yes. However, don't expect much about the performance gain for Python's vanilla closures.Specifying fixed global references(@diojit.jit(fixed_references=['isinstance', 'str', ...]) too annoying?Sorry, you have to. We are thinking about the possibility about automatic JIT covering all existing CPython code, but the biggest impediment is the volatile global variables.Possibility?Recently we found CPython's newly(:)) added featureDict.ma_version_tagmight be used to automatically notifying JITed functions to re-compile when the global references change.More research is required.ContributionsAdd more prescribed specialisation rules atjit.absint.prescr.TODOBenchmarksCheckbenchmarksdirectory.