// Finds where band parallelism stops paying on this machine. // // just sweep // // The interesting result is that the best tile count is well above the core // count. 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 finished until its slowest band is. Cutting finer lets a // free core start the next small band instead of idling, so over-decomposing // wins even though it cannot add parallelism. import 'dart:io'; import 'package:vflutter_ffi/vflutter_ffi.dart' as v; const _view = v.FractalView(width: 800, height: 600, maxIter: 500); Future main() async { // Warm the library and the code paths before timing anything. v.render(const v.FractalView(width: 64, height: 64, maxIter: 50)); print('${_view.width}x${_view.height}, maxIter ${_view.maxIter}, ' '${Platform.numberOfProcessors} logical cores\n'); final serial = _best(() => v.render(_view)); print('tiles best ms speedup'); print('${"serial".padRight(8)} ${serial.toString().padRight(9)} 1.00x'); for (final tiles in [1, 2, 4, 8, 12, 16, 24, 32, 48, 64]) { final ms = await _bestAsync(() => v.renderParallel(_view, tiles: tiles)); print('${tiles.toString().padRight(8)} ${ms.toString().padRight(9)} ' '${(serial / ms).toStringAsFixed(2)}x'); } print('\nwhy more tiles than cores helps — per-band cost spread:'); for (final tiles in [8, 32]) { _imbalance(tiles); } } /// Times each band of a [tiles]-way split serially, so the numbers describe /// the work itself rather than how it happened to be scheduled. void _imbalance(int tiles) { final rows = (_view.height / tiles).ceil(); final times = []; for (var t = 0; t < tiles; t++) { final y0 = t * rows; final y1 = ((t + 1) * rows).clamp(0, _view.height); if (y0 >= y1) continue; final sw = Stopwatch()..start(); v.renderBand(_view, y0, y1); sw.stop(); times.add(sw.elapsedMicroseconds); } final total = times.reduce((a, b) => a + b); final slowest = times.reduce((a, b) => a > b ? a : b); final mean = total / times.length; print(' $tiles bands: slowest ${(slowest / 1000).toStringAsFixed(1)} ms ' 'vs mean ${(mean / 1000).toStringAsFixed(1)} ms ' '(${(slowest / mean).toStringAsFixed(1)}x — the frame waits on this)'); } int _best(void Function() f) { var best = 1 << 30; for (var i = 0; i < 3; i++) { final sw = Stopwatch()..start(); f(); sw.stop(); best = sw.elapsedMilliseconds < best ? sw.elapsedMilliseconds : best; } return best; } Future _bestAsync(Future Function() f) async { var best = 1 << 30; for (var i = 0; i < 3; i++) { final sw = Stopwatch()..start(); await f(); sw.stop(); best = sw.elapsedMilliseconds < best ? sw.elapsedMilliseconds : best; } return best; }