Every number measured on the same machine. CPython 3.13.0 free-threaded vs U's packed lists compiled with -O3 -march=native (AVX2+FMA). No numpy — pure Python vs pure U.
| Operation | Python | U | Speedup | |
|---|---|---|---|---|
| matmul(200×200) | 2,368ms | 1.5ms | 1,530× | |
| distance(1M) | 201ms | 1.1ms | 191× | |
| variance(1M) | 65ms | 0.4ms | 162× | |
| dot(1M) | 134ms | 0.9ms | 146× | |
| mandelbrot(300²) @native | 920ms | 7.0ms | 131× | |
| softmax(10K) | 3.0ms | 0.024ms | 122× | |
| norm L2(1M) | 58ms | 0.5ms | 107× | |
| evens.dot(odds) 5M strided | 427ms | 5.0ms | 85× | |
| a − b elementwise(1M) | 138ms | 1.8ms | 77× | |
| sieve(1M) @native | 182ms | 3.2ms | 57× | |
| max(1M) | 23ms | 0.4ms | 55× | |
| a × b elementwise(1M) | 147ms | 3.1ms | 47× | |
| a + b elementwise(1M) | 161ms | 3.7ms | 43× | |
| min(1M) | 23ms | 0.6ms | 40× | |
| mean(1M) | 8ms | 0.3ms | 25× | |
| data[::2].sum(5M) strided | 81ms | 3.7ms | 22× | |
| map(square, 1M) | 65ms | 3.3ms | 20× | |
| sum(1M) | 9ms | 0.6ms | 15× | |
| map(sqrt, 1M) | 67ms | 7.4ms | 9× | |
| filter+map+sum chain(1M) | 59ms | 8.9ms | 7× |
Every Python float is a 24-byte heap object. A list of 1M floats = 1M scattered heap allocations + a pointer array. To sum them, Python walks 1M pointers, dereferences each PyObject, extracts the 8-byte double, adds it. That pointer-chase is the bottleneck — not the arithmetic.
U's typed lists ([N]) are contiguous double* buffers. No object headers, no refcounting, no pointer indirection. The CPU loads 4 doubles per AVX2 instruction and processes them in-register. This is the same layout numpy uses internally — the difference is that U compiles it from source rather than calling into a C extension.
This is the largest gap. Python's triple-nested loop at 200×200 (8M multiply-accumulates) takes 2.4 seconds. U's ikj-ordered loop compiles to a tight SIMD inner loop — the compiler vectorizes the j dimension across 4 doubles per cycle. At 200×200 the working set fits in L2 cache, so every load hits.
# Python: 2,368ms
for i in range(200):
for k in range(200):
for j in range(200):
c[i*200+j] += a[i*200+k] * b[k*200+j]
# U: 1.5ms — same loop, compiled to SIMD
// U: compiler vectorizes the inner loop
result = Tensor.matmul(aa, bb)
Python's [f(x) for x in items if g(x)] builds a list one boxed element at a time. U's .filter().map().reduce() chain operates on packed buffers. The U compiler fuses chains into single-pass loops — no intermediate arrays.
| Python | U equivalent | Py | U | × |
|---|---|---|---|---|
| [x*x for x if x>0.5] | .filter(xx => xx > 0.5).map(xx => xx * xx) | 59ms | 8.9ms | 7× |
| sum(x for x if x>0.5) | .filter(xx => xx > 0.5).reduce((acc, xx) => acc + xx, 0) | 47ms | 8.1ms | 6× |
With chain fusion (the compiler's job), .filter(xx => xx > 0.5).map(xx => xx * xx).reduce((acc, xx) => acc + xx, 0) becomes:
double s = 0;
for (long i = 0; i < n; i++)
if (data[i] > 0.5) s += data[i] * data[i];
// One pass. Zero allocations. ~1ms.
Python's data[::2] allocates a new list and copies every other element. U returns a view — same buffer, stride=2. No allocation, no copy. The view chains: data[::3][1::2] → stride=6, still zero allocation.
| Operation | Python | U | × | What U does |
|---|---|---|---|---|
| data[::2].sum(5M) | 81ms | 3.7ms | 22× | stride-2 pointer walk, no copy |
| evens.dot(odds) 5M | 427ms | 5.0ms | 85× | two stride-2 views, one loop |
| Operation | Python | U | × |
|---|---|---|---|
| softmax(10K) | 3.0ms | 0.024ms | 122× |
| norm(1M) | 58ms | 0.5ms | 107× |
| matmul(200×200) | 2,368ms | 1.5ms | 1,530× |
These are the building blocks of transformer inference. In Python, each operation has per-element function-call overhead. In U, they compile to contiguous memory passes with SIMD. The U runtime's multimodal encoders (ViT, audio, fusion) use these same ops — that's what +V dispatches.
data = [random() for _ in range(N)]
result = [x*x for x in data if x > 0.5]
total = sum(result)
# 3 passes, 3 allocations, every element boxed
data: [N] = List.random(1000000)
total = data
.filter(xx => xx > 0.5)
.map(xx => xx * xx)
.reduce((acc, xx) => acc + xx, 0)
// One pass. Zero allocations. Compiler fuses the chain.
ulist implements Python's buffer protocol (PEP 3118). memoryview(data) takes 0.006ms — it hands the double* pointer to numpy with no copy. Strided views preserve strides. Mutations propagate both directions.
arr = np.frombuffer(memoryview(data), dtype=np.float64) # 0ms
series = pd.Series(arr) # 0ms
# All three — ulist, numpy, pandas — point at the same contiguous memory.
U has no for or while. All iteration is .map(fn), .filter(pred), .reduce(acc, init), .on(handler) with real lambdas (xx => xx * 2). This makes fusion possible: when the compiler sees a chain of pure operations, it merges them into one pass. The +V modifier emits SIMD. +R(GPU) emits a WGSL compute shader.
Python's sorted() uses timsort, which exploits existing order — it beats generic qsort on partially-sorted data. str.count() is already SIMD-optimized in CPython. json.dumps() is bottlenecked by serialization, not numeric compute. The speedups come from eliminating boxing, enabling SIMD on contiguous memory, and fusing operation chains.