import gc, json, sys, time
perf = time.perf_counter
def bench_nbody(loops=40000):
PI = 3.141592653589793; SOLAR_MASS = 4 * PI * PI; DAYS = 0.01
bodies = {
'sun': ([0.0,0.0,0.0],[0.0,0.0,0.0],SOLAR_MASS),
'j': ([4.84143144246472090e+00,-1.16032004402742839e+00,-1.03622044471123109e-01],
[1.66007664274403694e-03*365.24,7.69901118419740425e-03*365.24,-6.90460016972063023e-05*365.24],9.54791938424326609e-04*SOLAR_MASS),
's': ([8.34336671824457987e+00,4.12479856412430479e+00,-4.03523417114321381e-01],
[-2.76742510726862411e-03*365.24,4.99852801234917238e-03*365.24,2.30417297573763929e-05*365.24],2.85885980666130812e-04*SOLAR_MASS),
'u': ([1.28943695621391310e+01,-1.51111514016986312e+01,-2.23307578892655734e-01],
[2.96460137564761618e-03*365.24,2.37847173959480950e-03*365.24,-2.96589568540237556e-05*365.24],4.36624404335156298e-05*SOLAR_MASS),
'n': ([1.53796971148509165e+01,-2.59193146099879641e+01,1.79258772950371181e-01],
[2.68067772490389322e-03*365.24,1.62824170038242295e-03*365.24,-9.51592254519715870e-05*365.24],5.15138902046611451e-05*SOLAR_MASS),
}
syst = list(bodies.values()); pairs = [(syst[i], syst[j]) for i in range(len(syst)) for j in range(i+1, len(syst))]
for _ in range(loops):
for (([x1,y1,z1],v1,m1),([x2,y2,z2],v2,m2)) in pairs:
dx=x1-x2; dy=y1-y2; dz=z1-z2
mag=DAYS*((dx*dx+dy*dy+dz*dz)**(-1.5))
b1m=m1*mag; b2m=m2*mag
v1[0]-=dx*b2m; v1[1]-=dy*b2m; v1[2]-=dz*b2m
v2[0]+=dx*b1m; v2[1]+=dy*b1m; v2[2]+=dz*b1m
for (r,v,m) in syst:
r[0]+=DAYS*v[0]; r[1]+=DAYS*v[1]; r[2]+=DAYS*v[2]
return syst[0][0][0]
def bench_fannkuch(n=9):
perm1=list(range(n)); count=list(range(n)); max_flips=0; m=n-1; r=n; check=0
perm=perm1[:]
while True:
while r != 1: count[r-1]=r; r-=1
perm=perm1[:]; flips=0; k=perm[0]
while k:
perm[:k+1]=perm[k::-1]; flips+=1; k=perm[0]
if flips>max_flips: max_flips=flips
while True:
if r==n: return max_flips
perm1.insert(r, perm1.pop(0)); count[r]-=1
if count[r]>0: break
r+=1
def bench_spectralnorm(n=200):
def A(i,j): return 1.0/((i+j)*(i+j+1)//2+i+1)
def mulAv(v):
return [sum(A(i,j)*v[j] for j in range(len(v))) for i in range(len(v))]
def mulAtv(v):
return [sum(A(j,i)*v[j] for j in range(len(v))) for i in range(len(v))]
u=[1.0]*n; v=[]
for _ in range(10):
v=mulAtv(mulAv(u)); u=mulAtv(mulAv(v))
vBv=sum(u[i]*v[i] for i in range(n)); vv=sum(v[i]*v[i] for i in range(n))
return (vBv/vv)**0.5
def bench_fib(n=29, loops=5):
def fib(k):
if k < 2: return k
return fib(k-1)+fib(k-2)
s=0
for _ in range(loops): s+=fib(n)
return s
def bench_binary_trees(depth=12):
def make(d):
if d==0: return (None,None)
return (make(d-1),make(d-1))
def check(node):
l,r=node
return 1 if l is None else 1+check(l)+check(r)
total=0
for _ in range(4):
for d in range(4,depth,2):
iters=2**(depth-d+4)
for _ in range(iters): total+=check(make(d))
return total
def bench_json_roundtrip(loops=1200):
import json as _j
data=[{"id":i,"name":"item%d"%i,"vals":[i,i*2,i*3],"ok":(i%2==0)} for i in range(200)]
s=0
for _ in range(loops):
text=_j.dumps(data); back=_j.loads(text); s+=len(back)
return s
def bench_dict_ops(loops=7000):
s=0
for _ in range(loops):
d={str(i): i for i in range(300)}
for k in d: s+=d[k]
return s
def bench_attr_method(loops=1200000):
class Vec:
__slots__=("x","y","z")
def __init__(s,x,y,z): s.x=x; s.y=y; s.z=z
def add(s,o): return Vec(s.x+o.x, s.y+o.y, s.z+o.z)
def dot(s,o): return s.x*o.x+s.y*o.y+s.z*o.z
a=Vec(1.0,2.0,3.0); b=Vec(4.0,5.0,6.0); acc=0.0
for _ in range(loops):
c=a.add(b); acc+=c.dot(a)
return acc
BENCHES = {
"nbody": (bench_nbody, {}),
"fannkuch": (bench_fannkuch, {}),
"spectralnorm": (bench_spectralnorm, {}),
"fib": (bench_fib, {}),
"binary_trees": (bench_binary_trees, {}),
"json_roundtrip":(bench_json_roundtrip, {}),
"dict_ops": (bench_dict_ops, {}),
"attr_method": (bench_attr_method, {}),
}
def run(repeat):
out={}
for name,(fn,kw) in BENCHES.items():
fn(**kw) # warmup / specialize
samples=[]
for _ in range(repeat):
gc.collect(); gc.disable()
t0=perf(); fn(**kw); samples.append(perf()-t0)
gc.enable()
samples.sort()
out[name]={"min":samples[0],"median":samples[len(samples)//2]}
return out
if __name__ == "__main__":
repeat=int(sys.argv[1]) if len(sys.argv)>1 else 5
print("RESULT "+json.dumps(run(repeat)))
Bug report
Bug description:
In
_Py_Deallocmarginis calculated regardless ofgc_flagflag value, when False we can easily short circuit. In x86 this fortunately is a noop due to the inlining however when compiled on MacOS Arm calculating the_PyThreadState_GETresults in a function call.Gating the margin calculation under the gc_flag led to a small speedup on various small benchmarks.
Bench
Details
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
_Py_Dealloc#154430