nandi/vflutter_ffipublic Fork 0
728acaab2594667f4998fc18812e7c1e2e4cc549
Commits
Clone
git clone https://git.rickub.com/nandi/vflutter_ffi.git
git clone ssh://git@rickub.com/nandi/vflutter_ffi.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Add drag-to-pan 0ed90f3 · on 728acaab2594667f4998fc18812e7c1e2e4cc549 · nandithebull · 6h ago
README.md · 121 lines · 4.3 KBmarkdown
Blame HistoryOpen raw

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 and vf_mandelbrot fills it in place, so no V
    memory crosses the boundary and there is nothing to vf_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
# 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 and `vf_mandelbrot` fills it in place, so no V
  memory crosses the boundary and there is nothing to `vf_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

```bash
just mandelbrot
```

or, driving Flutter yourself:

```bash
cd example
flutter run -d linux
```

Only the Linux runner is generated. For the other platforms, generate their
runner directories once first:

```bash
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:

```bash
./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:

```bash
./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:

```bash
dart compile exe example/lib/fractal_bench.dart -o /tmp/bench
LD_LIBRARY_PATH=build /tmp/bench
```