Overview
This embedded content is from a site (www.youtube.com, flickr.com, etc) that does not comply with the Do Not Track (DNT) setting now enabled on your browser.
Clicking through to the embedded content will allow you to be tracked by the embed provider.
The new Turbo in CircuitPython helps when the board spends time calculating: making neopixel effects, drawing fractals, processing audio, filtering sensor readings, or preparing lots of pixels. Those projects can get smoother animation, quicker responses, or room to do more things at once.
With Turbo, it's easier, better, and now even faster to make LED light up costumes that also reacts to sound at the same time, a sensor dashboard with animated graphics, or a tiny game doing physics while drawing the screen. turbo speeds up the busy Python parts. It won't make a slow sensor or display connection faster, and the rest stays familiar Python. How did this all come about?
This started in March, when Slug's text-rendering code became something we could study and build from. I knew it was time to see how much more we could get out of these little boards.
One experiment led to another. Better-looking type, bigger images, animation... Then the question underneath all of it: what happens if we keep the friendly Python program and give its busiest loop a faster way to run?
That's Turbo. Your computer turns selected functions into instructions the chip can run directly. Python still handles the rest. We have measured speedups, real display captures, and examples you can pull apart to see what happened.
None of this arrived alone. CircuitPython, MicroPython, PyMCU, compiler tools, open hardware, and people sharing their work gave us pieces to connect. The Bao experiments take that idea somewhere else, handing calculations to four helper cores.
Now we get to make those paths easier to use, compare results, and find the next useful thing. Maybe that's smoother animation, a responsive instrument, or an idea we haven't tried yet.
That's what I like about open source. Someone shares a piece, someone else sees a possibility, and we get to keep building it together. Go Blinka!
Page last edited September 12, 2026
Text editor powered by tinymce.
Measured Speed
Start with a Mandelbrot calculation
A Mandelbrot image gives us a useful job for Turbo: repeat a small calculation for each pixel, then turn the results into a picture. We tried this with fixed-point math, which represents fractional values using scaled integers. It gives the board plenty of repeated arithmetic to work through.

An actual capture from the earlier PyMCU-based Feather RP2040 DVI Mandelbrot experiment, with 152 × 96 samples enlarged for display. Its on-screen render time belongs to that recording. The timed comparison below came from a separate Metro RP2040 run.
One board. One workload. Three execution modes.
For the comparison, we gave the same fixed-point Mandelbrot function to three execution modes: ordinary Python bytecode, Native, and Viper. Each calculated a 160 × 120 image, allowing up to 64 iterations per pixel. The table shows how long the calculation took and the value it returned, so we can compare the time without changing the job.
| Execution mode | Recorded median time | Speedup over bytecode | Returned value |
|---|---|---|---|
| Python bytecode | 8,335.3 ms | 1.00× | 407644 |
| Native | 4,778.0 ms | 1.74× | 407644 |
| Viper | 422.9 ms | 19.71× | 407644 |
The board doing this timed work was an Adafruit Metro RP2040. We also used a Feather RP2040 DVI for experiments that sent pictures to a display, including the capture above. They play different roles here: the Metro supplied these timings, and the Feather lets us see a visual example.
The test record calls the Metro's processor target armv6m. That is the instruction-set family used by its Cortex-M0+ CPU cores, and it tells the compiler which instructions the chip can run. The record also identifies CircuitPython firmware 10.3.0-42-g3cdb20693f. Firmware matters because it supplies the Python runtime and native-module loader; these measurements belong to that build, even though the installation page now points to newer official firmware.
These are recorded results from September 8, 2026, reviewed for this guide. We have not rerun this benchmark for the draft. All three modes returned 407644. The stopwatch covers the computation, so screen refresh, file loading and the rest of an application need their own measurements.
Recorded CLI output · Benchmark source · Firmware context
The returned value 407644 is the sum of the calculated iteration counts. Matching it catches many mistakes, but two different shopping baskets can have the same total. Likewise, different pixel values can produce the same sum. Compare the complete output when you can, and use more than one input before trusting a changed function.
Source: the benchmark's return value.
It is the speedup of this measured Mandelbrot computation. A project also spends time moving pixels, reading files, talking to sensors, and waiting for hardware. Measure those separately. The interesting payoff is time returned to the rest of your program: more room for interaction, a larger calculation, or another update.
A faster cook cannot make the kettle boil sooner. For an illustrative program that spends 50 ms computing and 50 ms waiting for hardware, making the computation 20 times faster gives 2.5 + 50 = 52.5 ms overall: about 1.90×, not 20×. This is a made-up example to explain the limit, not another Turbo measurement.
Another way to speed up the work
Before compiling a loop, check whether ulab already has the operation you need. Its array math, statistics, FFTs and filters run in native code, so one call can process a batch of values without a Python loop. Jeff Epler's RMS benchmark is a useful example. Turbo is useful when the work is a custom loop with its own branches and rules: it can compile that loop, including its control flow. The ulab documentation lists its available operations.
Our Mandelbrot test keeps the same fixed-point, per-pixel calculation and compares bytecode, Native and Viper. Each pixel repeats arithmetic until it escapes or reaches the iteration limit. An ulab version could organize pixels into arrays and masks, but that would be a separate implementation to check and measure. We haven't benchmarked one here, so this chart does not establish how Turbo compares with ulab. Jeff's Metro M4 results belong to his separate workload.
The board in this test
The recorded times above came from the Metro RP2040 below, running the exact firmware identified with the chart. You do not need hardware to read or compare the results.
Page last edited September 12, 2026
Text editor powered by tinymce.
How Turbo Works
Compile on the computer. Run on the board.
Start with a function in a module that your main program imports. Turbo uses mpy-cross on your computer to compile the selected function for the board's CPU architecture. It puts the compiled code in a .mpy file, and compatible CircuitPython firmware loads it when you import the module. Compile the function once, then copy the prepared module to the board. The board can call that function over and over, running every required loop without asking the computer to compile it again. Recompile when you change the function or target architecture.
The animation below follows the current Turbo toolchain.
Source code is the Python you write. CircuitPython normally compiles it into bytecode, a compact instruction stream that its virtual machine executes. Machine code contains instructions the target CPU executes directly. The interpreter itself is also software running on that CPU; native code removes interpreter work from the compiled function, but may still call runtime helpers.
AOT means ahead of time: Turbo compiles on your computer before the board imports the module. And .mpy is the lunchbox, not the lunch. It is a file container that can hold bytecode or native code. A filename ending in .mpy alone does not promise a speedup. Native contents must match the board's architecture and firmware.
Sources: MicroPython's compiler, the .mpy format, Turbo's toolchain.
Keep the fast function small
Native mode compiles control flow while retaining Python objects. Viper can also use machine-word integers and pointers, which can remove much more overhead from a suitable inner loop. The Mandelbrot example uses integer fixed-point math for this reason.
Keep code.py as the coordinator. Put the work to accelerate in an imported module, such as src/pixels.py. The Turbo shim places lib/turbo/<arch>/ ahead of src/ in the import search path, so an installed native module is found first. Import turbo before importing that module.
Keeping the source in a separate directory matters: a .py beside a same-named .mpy can take priority. The provided layout preserves the readable source without accidentally selecting it first.
Choose work that can benefit
Start with something you can time: a pixel transform, a pass through a buffer, or an integer calculation. Measure the function before changing it, and check whether a built-in CircuitPython operation already does the job. If most of the time is spent waiting for a sensor, a file, or the display, work on that part of the project too.
Source fallback is selected at import time when the native path or module is absent. It is useful for portability and comparison; it does not recover from a faulty native module. Native code needs the right architecture and compatible firmware.
Counting money in cents lets you represent $1.50 as the integer 150. This Mandelbrot example uses the same idea with 4,096 steps per unit: 12 fractional bits, because 2**12 = 4096. So 1.5 is stored as 6144. The scale is agreed in advance; the point stays in its assigned seat.
For x*x and y*y, the code shifts right by 12 after multiplication to restore the scale. Viper's machine-word integers make this useful for the loop, but range and rounding still matter. On RP2040, these integers are 32 bits; they do not automatically grow to hold larger values as Python integers do. Check intermediate values as well as final answers.
Sources: the actual fixed-point loop, Viper's integer rules.
CUDA is NVIDIA's parallel computing platform and programming model. In its usual host/device model, CPU code launches work on an NVIDIA GPU. A kernel is a function launched for execution on the GPU, usually across many threads organized into blocks and grids.
Imagine sending a tray of similar jobs to a kitchen with many cooks. You still have to divide the work, make the ingredients available, and collect the results. Data movement and coordination count toward the bill.
Turbo's compiled functions here run on the microcontroller's CPU. The RP2040 has no NVIDIA GPU, and these examples do not use CUDA. The shared idea is choosing a suitable part of a program to accelerate. The hardware and execution model differ.
Sources: NVIDIA's CUDA programming model, RP2040 specifications.
Try a different kind of optimization
Jeff Epler's Game of Life example shows another useful approach: change how the Python code accesses and copies data. His comments explain why flat bitmap indexes can help, how two buffers reduce copying, and what that costs in readability. It is a useful example to study before choosing a function to compile.
Page last edited September 12, 2026
Text editor powered by tinymce.
Device Demos
Here are some examples of projects that were only possible with Turbo support in circuitpython
Put the extra time to work
Now for some pixels! These frames come from recorded DVI output of our Feather RP2040 DVI experiments. Each combines native helpers with data formats and memory handling chosen for the job.
These recorded demos used custom firmware and Native or Viper code. Current official UF2 downloads with Turbo support are on the Try Turbo page. Look at how the work is divided between the computer and the board; the 19.71× number belongs to the separate Metro RP2040 Mandelbrot test.
Letters become windows

Impossible Type turns prepared letter masks into windows onto an animated texture. The fonts are rasterized on the computer; a Viper compositor writes the pixels on the microcontroller. This is a useful division of work for animated labels, instrument displays, and visual experiments. Real DVI capture at 13.5 seconds.
A large image, one row at a time

Impossible Image streams a 320 × 4096 RGB PNG whose decoded pixels total 3.75 MiB. Streaming keeps the image from having to fit in RAM all at once. A compiled helper handles selected pixel-processing work. Real DVI capture at 65 seconds.
Animation is a whole pipeline
The animation capture above uses a 76,800-byte framebuffer per track. The recorded six-loop run maintained its default 12 fps with frame-result checks. The current single-framebuffer display can tear.
These examples suggest directions to explore: animated type, compact dashboards, streaming art, and visualizations. Each new project needs its own memory budget and timing measurements.
You can read a long book without laying every page on your desk. Streaming uses the same trick: process a piece, then reuse working memory for the next piece. The 3.75 MiB figure describes the image's decoded pixels, not RAM occupied all at once. The decoder and display still need their own working buffers.
Hardware in these captures
These recorded experiments used the Feather RP2040 with DVI Output Port and their own custom firmware. It is a different board and build from the Metro benchmark. A full-size HDMI cable connects the DVI output to a compatible display.
Another way to share the work
Jeff Epler's PIO example sends NeoPixel data in the background while CircuitPython continues. PIO handles a timing-sensitive I/O job; a compiled function handles CPU work. Choose the part that is keeping your project busy.
Page last edited September 12, 2026
Text editor powered by tinymce.
Try Turbo
Download, flash, and try Turbo
Turbo support is now included in the latest official CircuitPython builds for RP2040 and RP2350 boards. Download the UF2 for your board, flash it, and the firmware is ready to run compiled Turbo modules. You do not need to build custom firmware for this example.
Start with the ready-to-copy Blinka art demo on an Adafruit Feather RP2040 with DVI Output Port. Its modules are already compiled, so you can try the display without installing compiler tools on your computer. The later build-and-compare section uses the small Mandelbrot example on a Metro RP2040.
1. Download and install the UF2
As of September 11, 2026, use the Absolute Newest continuous build. The stable 10.3.0 download predates Turbo support. The official files below identify themselves as 10.3.0-55-geb49ac4141, built on September 11, 2026.
| Your board | Official UF2 with Turbo support | Board download page |
|---|---|---|
| Metro RP2040, for the later Mandelbrot example | Download Metro RP2040 UF2 | Metro RP2040 on circuitpython.org |
| Feather RP2040 with DVI Output Port, for the art demo | Download Feather RP2040 DVI UF2 | Feather RP2040 DVI on circuitpython.org |
For a newer build, open your board's page on circuitpython.org and scroll to Absolute Newest → BROWSE S3. Choose your language and a main-branch build containing the Turbo support merged on September 11. Each board needs its own UF2.
- Back up the files already on your board's CIRCUITPY drive. Connect the board with a USB data cable.
- Hold BOOT/BOOTSEL, then press and release RESET. Keep holding BOOT until the RPI-RP2 drive appears, then release it.
- Drag the downloaded UF2 onto RPI-RP2. Let the copy finish. The board restarts and the CIRCUITPY drive appears.
That's the firmware installed! Your board can now load matching native and Viper .mpy modules. The prepared project below supplies those modules and its Python files.
For pictures of the buttons and drives, see the Metro RP2040 install guide or the Feather RP2040 DVI install guide.
2. Copy the Blinka art demo
Use the Feather RP2040 DVI and its matching UF2 from step 1. Connect its DVI output to a compatible monitor using an HDMI cable. This program uses that board's display wiring.
Download the ready-to-copy Blinka demo, CIRCUITPY.zip (24 KB)
- Unzip the download and open the folder named CIRCUITPY.
- Copy everything inside that folder to the root of your board's CIRCUITPY drive. Replace the old
code.pyonly after backing it up. The newcode.py,.mpymodules andassetsfolder should sit directly on the drive, with no extra CIRCUITPY folder inside it. - Wait for the copy to finish, safely eject or sync the drive, then press RESET.
The program cycles through Blinka's Turbo OFF art, Turbo ON art and a live native neon effect. The ZIP includes the prepared modules, so no host CLI or compiler is needed for this demo. Install the UF2 separately; it is not inside this download.
The OFF and ON numbers time the same job: adding up the bytes of the starting artwork. The program compares the Python and native results before showing their timing ratio. The live effect is a separate demonstration; its displayed multiplier reuses that byte-sum ratio and is not a measured animation speedup.
The supplied clip below shows the art demonstration. The archive's contents and installation layout were checked for this guide; we have not run a new hardware test of this ZIP on the official firmware.
Compile a function yourself
The ready-to-copy art demo above is complete. Continue here when you want to build or change your own accelerated function. We'll use the Metro RP2040 Mandelbrot example for this compiler walkthrough.
3. Install the MPY cross-compiler tools on your computer
On Apple Silicon macOS or supported Linux hosts, with Python 3.11 or newer and Git installed, create a new project folder and an isolated environment. Install the reviewed CLI revision:
python3 -m venv .venv
.venv/bin/python -m pip install 'git+https://github.com/mikeysklar/turbo-cli.git@a0143a4efa246a8621d8ea076aa718f8c7215265'
.venv/bin/turbo --help
This installs the Turbo CLI, which invokes a matching mpy-cross compiler to prepare modules for your board. Compile a function once and run it repeatedly on the board; compile it again after changing it. This pinned installation produced adafruit-turbo 0.1.0 in our host check. Use the CLI source and firmware notes when choosing a newer revision.
The pinned CLI can resolve compiler downloads for Apple Silicon macOS and Linux x86_64, aarch64, and armv7l. Intel Macs need a separately supplied matching compiler via --mpy-cross. Compiler download was not exercised in this draft's host check.
4. Build and compare
Use the intended test board with its matching official UF2 installed. These are the documented example commands; build copies compiled modules to the board, and bench runs and installs candidates:
.venv/bin/turbo doctor
.venv/bin/turbo init --example
.venv/bin/turbo build
.venv/bin/turbo bench pixels --trials 5
doctor identifies the target and checks the toolchain. init --example creates the shim, source module, and example application. build produces architecture-specific modules. bench times bytecode, native, and Viper, compares the benchmark's returned value, and selects a faster matching candidate. A successful build by itself is not an output-equivalence check.
The benchmark command runs through the board's REPL. To run the example as the board's application, copy the local project's generated code.py to the root of the intended board's CIRCUITPY drive after backing up its existing code.py. Wait for copying to finish and safely eject or sync the CIRCUITPY drive, then press the board's reset button. build already copies the shim and source module, but leaves the board's code.py alone.
A minimal application is just:
import turbo
import pixels
print(pixels._turbo_bench())
The actual Mandelbrot source contains the fixed-point row function and the benchmark. Its expected returned value is 407644.
5. Make the comparison useful
Record the exact board, firmware, clock, source revision, input, trial count, median times, and result checks. Compare complete outputs where practical. Then time the full application, including the parts that wait for hardware.
On the Metro RP2040's CIRCUITPY drive, remove lib/turbo/armv6m/pixels.mpy and press reset to run the installed example from source. Leave src/pixels.py, lib/turbo.py, and code.py present. Removing only the host copy does not change the board. That gives you a baseline for the next experiment.
Once the example works, choose one small loop from your own project. Save a working copy, time it, and check the result after compiling. Then run the whole project again. That's where you'll find out whether the extra speed buys you another animation step, a bigger calculation, or more time to respond to input.
In out: ptr8, ptr8 tells Viper to access the buffer through a pointer to bytes. out[px] = i writes the byte at that position. Viper pointer access does not check the buffer's bounds, so this function needs a writable buffer at least width bytes long. The example allocates 160 bytes and uses indexes 0 through 159. If you change the width, keep the allocation and loop limits together. The address does not come with a fence.
Sources: Viper pointers, this function and its buffer.
from turbo import turbo
@turbo.viper
def mandel_row(out: ptr8, width: int, dx: int, cy: int, max_iter: int):
# fixed point, 12 fractional bits, integers only so viper can take it
for px in range(width):
cx = px * dx - (2 << 12)
x = 0
y = 0
i = 0
while i < max_iter:
x2 = (x * x) >> 12
y2 = (y * y) >> 12
if x2 + y2 > (4 << 12):
break
y = ((x * y) >> 11) + cy
x = x2 - y2 + cx
i += 1
out[px] = i
def _turbo_bench():
W, H, IT = 160, 120, 64
row = bytearray(W)
dx = (3 << 12) // W
total = 0
for r in range(H):
mandel_row(row, W, dx, ((r * 2) << 12) // H - (1 << 12), IT)
total += sum(row)
return total
Source: mikeysklar/turbo, revision 3a1c28a. Copyright (c) 2026 Adafruit Industries. MIT license.
A smaller file is another useful goal
Kevin Matocha's Memory-saving Tips for CircuitPython explains how ordinary precompiled .mpy files can help with storage. That is a useful companion to this experiment: a file can take less space without making its calculation run faster. Check which benefit your project needs.
Parts for these examples
The ready-to-copy art demo needs a Feather RP2040 DVI, a compatible monitor, and an HDMI cable. The compiler walkthrough uses the Metro RP2040 below. For either board, use one USB data cable that fits your computer: USB A to USB C or USB C to USB C. Choose one, and keep a backup of the files already on the board.
Page last edited September 12, 2026
Text editor powered by tinymce.
Blinka's Playground
Make a guess. Then make a change.
Let's put these ideas to work! Blinka will walk through two small examples: where compilation happens, and how a big image can pass through a small working buffer. These are teaching animations. Their motion does not represent measured execution time.
Before trying the timing playground, make a prediction: a program spends 50 ms computing and 50 ms waiting for hardware. If the computation becomes 20 times faster, how much faster is the whole program? Keep your guess, then try it.
Compile first, then run
Follow the highlighted path. Your computer builds the selected function; the compatible board runs the installed native code. The ordinary Python path uses the interpreter on that same board. Rebuilding is needed when you change the accelerated source.
Blinka is showing the route, not racing a stopwatch.
Keep the buffer. Change the contents.
Watch the source image and the little row buffer. A piece is read, decoded, and used; the next piece reuses that working space. The picture progresses while the row buffer stays the same size.
Before replaying, point to what changes and what stays put. The decoder and display still need their own working memory. These colored blocks explain the arrangement; their dimensions are not measurements of the real image demo.
Try the timing playground
Download the companion below and unzip it. Open index.html to try the timing and fixed-point activities. Everything needed for those activities is in the file.
Start with your prediction, then change how much time the loop takes. Try a project that mostly waits and one that mostly computes. The timing model keeps the other work unchanged and adds no copying or call overhead. The recorded Metro RP2040 results have their own labeled table.
In the fixed-point activity, compare 1.25 with 0.1. Which value fits the 4096-step scale exactly? Try a negative value too.
Bring a board when you are ready
The optional serial viewer receives text from the port you choose. First run a program on your intended board that prints a result, such as the example on Try Turbo. Close other serial monitors before connecting. Opening a port can reset some devices.
For Web Serial, follow the included README to serve the companion on localhost, then open it in a supported desktop browser such as Chrome. Choose serial viewer, then connect & choose port. The viewer displays incoming text; it has no command box or firmware installer. Use disconnect when finished.
The browser activities and serial code paths were checked without hardware. An actual board connection has not been tested for this companion.
After changing a function, compare its output first, then its time. Blinka can point to the fast path; your measurements tell you whether it helped.
An optional board for the next step
The timing and fixed-point activities run in your browser without hardware. If you want to try the recorded benchmark afterward, the Metro RP2040 is the board used by the Try Turbo page. The serial panel only displays output from a board you choose; it does not install the example for you.
Page last edited September 12, 2026
Text editor powered by tinymce.
Where We Go Next
It started with Slug

The Slug Algorithm poster from my March 22 article. Image: Slug / Eric Lengyel / Terathon Software.
In March 2026, Slug got me thinking about what else we could bring to small boards. On March 17, Eric Lengyel released Slug's reference vertex and pixel shaders, and I wrote about it on March 22. There was working rendering code to study, with an open-source license. The reference shaders are available separately from the commercial Slug Library.
Our Impossible Type experiment took a smaller step: prepare font masks on the computer, then use a native compositor to animate them on a Feather RP2040 DVI. You saw its recorded output on Real Device Demos. It uses prepared masks rather than running Slug's GPU shaders or rendering font curves directly on the board.
That's open source doing useful work: an implementation you can read gives you a place to begin. Find a piece the hardware can handle, try it, and share what you learn.
MicroPython already has useful tools
MicroPython has Native and Viper emitters for supported targets. It can also import native .mpy modules built from C. Another route is to compile C modules into the firmware itself. Each route has its own build and runtime requirements.
Turbo brings selected pieces into a CircuitPython workflow. The example in this guide uses mpy-cross, matching firmware, readable source, and a benchmark. Start with a working program, compile one function, and check the result.
PyMCU opens another compiler path

Project artwork from pymcu.org. Image: PyMCU project. This represents the compiler project; it is not a photograph of a hardware test.
PyMCU compiles a statically typed subset of Python. Its ARM backend uses LLVM to produce code for the target processor.
An earlier Feather RP2040 DVI prototype combined PyMCU output with a handwritten C adapter and CircuitPython's native-module linker. Recorded demos included brightness and Mandelbrot, with removable native modules and Python fallbacks. It showed that this separate compiler route could run selected work on the board. The Metro 19.71× result in this guide comes from the later mpy-cross workflow.
There is useful work ahead here: make the adapter easier to produce, check types and target compatibility, and compare complete outputs. A compiler can do more of the repetitive preparation while the person writing the project keeps control of the program.
Bao: give the loop a few helpers
The Baochip BIO has four small RISC-V cores. Our recorded Dabao evaluation-board tests used all four, with CircuitPython on the main CPU sending parameters and collecting results through queues. The BIO programs were handwritten RISC-V, assembled on the computer. This was an explicit dispatch path, not automatic Python-to-BIO compilation.
Two recorded comparisons show what that arrangement can do. Each ran Python first, then BIO on the same board. These are September 4, 2026 results from CircuitPython 10.3.0-alpha.4-bao-turbo.1, reviewed from saved records for this guide. They were not rerun for the guide.
Read the recorded numbers
| Recorded workload | Main-CPU CircuitPython compute | Four BIO cores, including coordination | Compute speedup |
|---|---|---|---|
| Integer Monte Carlo, 1,048,576 darts | 94.888732858 s | 0.102630589 s | 924.6× |
| Fixed-point Mandelbrot, 128 × 96, up to 64 iterations | 2.796966554 s | 0.150634769 s | 18.6× |
The Monte Carlo run added the compute times of 32 batches per mode. Both modes returned 823,567 hits; all returned counts, previews, and final states matched. This check covered the returned records, not a stored list of every dart. For Mandelbrot, all 12,288 output bytes matched. Each row describes one final paired run, not a median of repeated trials.
Four cores, so why more than four times?
We changed two things: where the work ran and how it ran. The baseline executed the integer loops through CircuitPython. The BIO path ran small machine-code loops across four cores. This comparison does not isolate the benefit of parallelism, and it does not compare BIO with optimized C on the main CPU.
Monte Carlo gives each core a long batch of repetitive work with relatively few results to send back. Mandelbrot sends a whole image's pixel counts back through the queues, and the main CPU must receive and unpack them. Those different costs help explain why each workload gets its own ratio.
The stopwatch boundary matters. The compute measurements include BIO startup, parameters, and receiving results. They exclude one-time program loading and USB reporting. In the same recorded Mandelbrot run, the wider host stage that includes USB reporting took 5.140444959 s for Python and 2.487102917 s for BIO, about 2.07×. The faster loop helped, and moving the results became a larger part of the wait. That stage is not a DVI display frame rate.
Inspect the recorded data
The download below contains the exact timing records, Monte Carlo results, all Mandelbrot output bytes, source hashes, and an offline verification script. The script checks the saved data without connecting to hardware. Recording nanoseconds preserves the timer values; it does not imply nanosecond measurement accuracy.
What we'd like to try next
Pixel transforms, small simulations, audio-buffer math, sensor-data filtering, and repeated searches through buffers are useful candidates. First find a loop that takes time. Then ask whether a built-in operation, a better data layout, native CPU code, PIO, or a coprocessor is the right tool for that particular job.
For Turbo, we want easier builds, clearer target checks, reusable adapters, and more examples with complete output comparisons. On Bao, the next experiments could explore better batching and less time moving results. Other processors and compiler backends will need their own working paths and measurements.
Keep the part of Python that makes a project easy to change. Give the busy part a well-defined job, and measure the whole project afterward. The reward might be smoother type, another animation layer, or enough time to read the next input.
Hardware behind these experiments
The Feather RP2040 DVI below is the board used for the earlier PyMCU and Impossible Type experiments, with their separate custom builds. The Bao results came from a Dabao evaluation board with Baochip-1x; see the Baochip hardware project for that platform. They do not qualify a different Bao board or establish a ready-to-install Turbo package for it.
Page last edited September 12, 2026
Text editor powered by tinymce.