Here I try out different ways of making arrays of tuples with Numba where the number of tuples that need to be produced is not known in advance. The fastest way is to make a list of tuples, and then convert this into a Numpy array before returning. The baseline shown in the last plot is the fastest possible way of pre-allocating a Numpy array with the right size upfront, but this cannot be used if the number of tuples is not known in advance.
import numba as nbimport numpy as npimport awkward as akprint(f"{nb.__version__=}")print(f"{ak.__version__=}")
nb.__version__='0.53.1'
ak.__version__='1.1.2'
@nb.njitdef make0(n): r = np.empty((n, 4))for i inrange(n):# simulate some work x = np.random.rand() y = np.random.rand() z = np.random.rand() t = np.random.rand() r[i,0] = x r[i,1] = y r[i,2] = z r[i,3] = treturn rmake0(3)
26.1 µs ± 1.51 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
@nb.njitdef make1(n): r = []for i inrange(n):# simulate some work x = np.random.rand() y = np.random.rand() z = np.random.rand() t = np.random.rand() r.append((x, y, z, t))return np.asarray(r)make1(3)
36.9 µs ± 1.55 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
@nb.njitdef make2(n): r = []for i inrange(n): ri = []# simulate some work x = np.random.rand() y = np.random.rand() z = np.random.rand() t = np.random.rand() ri.append(x) ri.append(y) ri.append(z) ri.append(t) r.append(ri)return np.asarray(r)make2(3)
419 µs ± 18.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
@nb.njitdef make3(n): r = []for i inrange(n):# simulate some work x = np.random.rand() y = np.random.rand() z = np.random.rand() t = np.random.rand() r.append(x) r.append(y) r.append(z) r.append(t)return np.array(r).reshape((n, 4))make3(3)
263 µs ± 10.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
import timeitimport matplotlib.pyplot as pltn = np.geomspace(1, 1000000, 20).astype(int)ys = []for fn in (make0, make1, make2, make3, make4, make5): y = []for ni in n: m, t = timeit.Timer(lambda : fn(ni)).autorange() y.append(t/m) ys.append(y)
for y, label inzip(ys, ("baseline","list+tuple+asarray","list+list+asarray","list+array+reshape","ArrayBuilder+tuple","ArrayBuilder+list")): plt.plot(n, np.divide(y, 1e-6), label=label)plt.legend()plt.xlabel("array length")plt.ylabel("time / μs")plt.loglog();