vflutter_ffi example
A Mandelbrot explorer whose pixels are computed in V. Drag to pan, scroll to
zoom smoothly, tap to zoom in, right-click to zoom out — zoom is anchored at
the cursor, and a drag covers less ground the deeper you are. Sliders
control the iteration cap and how many isolates the frame is split across;
"Reset view" restores the framing without disturbing either.
It exists to exercise the parts of the bridge the string demo cannot:
- Bulk data across the boundary. Each frame is a full RGBA buffer, not a
string. Dart allocates it andvf_mandelbrotfills it in place, so no V
memory crosses the boundary and there is nothing tovf_free. - Band parallelism. The image splits into horizontal strips rendered on
separate isolates. Bands are independent by construction, and-gc none
means no stop-the-world phase to serialise them. - An honest benchmark. The same algorithm is written twice — in V
(src/vflutter.v) and in Dart (lib/mandelbrot_dart.dart) — and the
Benchmark button times both. The tests assert the two agree pixel-for-pixel,
which is what makes the comparison meaningful.
What the numbers actually say
Measured here (800x600, maxIter 500, release build, 8-core Linux):
| time | |
|---|---|
| V, one isolate | ~152 ms |
| V, 4 isolates | ~65 ms |
| Dart AOT, one isolate | ~165 ms |
V is not meaningfully faster than Dart at this. For a scalar
floating-point loop, V-through-C and Dart AOT land within ~10% of each other.
The 2.5x comes from the parallel split, which Dart could do on its own without
any FFI. Treat this app as a demonstration of the bridge — bulk buffers,
caller-owned memory, safe multi-isolate calls — not as a speed claim for V.
Reach for V here because you want to write the logic in V, or already have it
in V, not because C-via-V is expected to outrun Dart AOT on arithmetic.
How many isolates?
The slider goes to 32, and past the core count is the right place to be. Bands
cost very different amounts — rows crossing the set's interior run the full
iteration cap, rows in open space escape almost immediately — and a frame is
not done until its slowest band is. Over-decomposing lets a free core start the
next small band instead of idling.
Measured here (800x600 / 500 iterations, 8 logical cores):
| tiles | best ms | speedup |
|---|---|---|
| serial | 153 | 1.00x |
| 4 | 64 | 2.39x |
| 8 | 43 | 3.56x |
| 16 | 34 | 4.50x |
| 32 | 29 | 5.28x |
| 64 | 27 | 5.67x |
Even at 32 bands the slowest is still 2.7x the mean, which is the remaining
inefficiency — not isolate overhead. Spawning 24 isolates costs ~0.8 ms and
moving the whole 1.83 MB frame back across them ~1.4 ms, against a ~29 ms
frame. Run just sweep to get these numbers for your own machine.
Run it
just mandelbrot
or, driving Flutter yourself:
cd example
flutter run -d linux
Only the Linux runner is generated. For the other platforms, generate their
runner directories once first:
flutter create --platforms=android,ios,macos,windows .
flutter create leaves lib/, test/ and pubspec.yaml alone. The native
library is built by the plugin itself: CMake invokes v on Linux, Windows and
Android; iOS and macOS compile the pre-generated C in ios/Classes/.
Tests
flutter test runs on the host VM rather than in the app bundle, so it needs
the native library on the loader path:
./tool/build.sh
cd example && LD_LIBRARY_PATH=../build flutter test
The suite covers the zoom maths as a property (the point under the cursor stays
put), the buffer contract, the band split matching a single-shot render, and
the V and Dart kernels agreeing exactly.
Headless scripts
No Flutter SDK needed — only dart and the host library. Run from the
repository root, which resolves without Flutter:
./tool/build.sh
LD_LIBRARY_PATH=build dart example/lib/fractal_bench.dart # V vs Dart timings
LD_LIBRARY_PATH=build dart example/lib/main_test.dart # string round-trips
LD_LIBRARY_PATH=build dart example/lib/memory_check.dart # RSS regression guard
For a like-for-like comparison with a release app, compile the benchmark AOT
first — dart run uses the JIT:
dart compile exe example/lib/fractal_bench.dart -o /tmp/bench
LD_LIBRARY_PATH=build /tmp/bench
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
|