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