- Startseite
- Documentation
- Nativ
- Portierung zu pi-natives (N-API) — Feldnotizen
Portierung zu pi-natives (N-API) — Feldnotizen
Dies ist ein praxisorientierter Leitfaden zum Verschieben von Hot Paths in crates/pi-natives und deren Anbindung über die JS-Bindings. Er existiert, um zu vermeiden, dass dieselben Fehler zweimal auftreten.
Wann portieren
Abschnitt betitelt „Wann portieren“Portieren Sie, wenn einer der folgenden Punkte zutrifft:
- Der Hot Path läuft in Render-Schleifen, engen UI-Updates oder großen Batches.
- JS-Allokationen dominieren (String-Churn, Regex-Backtracking, große Arrays).
- Sie haben bereits eine JS-Baseline und können beide Versionen nebeneinander benchmarken.
- Die Arbeit ist CPU-gebunden oder blockierendes I/O, das im libuv-Thread-Pool laufen kann.
- Die Arbeit ist asynchrones I/O, das auf Tokios Runtime laufen kann (z. B. Shell-Ausführung).
Vermeiden Sie Portierungen, die von JS-exklusivem State oder dynamischen Imports abhängen. N-API-Exports sollten pur sein, Data-in/Data-out. Lang laufende Arbeit sollte über task::blocking (CPU-gebunden/blockierendes I/O) oder task::future (asynchrones I/O) mit Abbruchmöglichkeit geleitet werden.
Anatomie eines nativen Exports
Abschnitt betitelt „Anatomie eines nativen Exports“Rust-Seite:
- Die Implementierung befindet sich in
crates/pi-natives/src/<module>.rs. Wenn Sie ein neues Modul hinzufügen, registrieren Sie es incrates/pi-natives/src/lib.rs. - Exportieren Sie mit
#[napi]; snake_case-Exports werden automatisch in camelCase konvertiert. Verwenden Sie explizitesjs_namenur für echte Aliase/nicht-standardmäßige Namen. Verwenden Sie#[napi(object)]für Structs. - Verwenden Sie
task::blocking(tag, cancel_token, work)(siehecrates/pi-natives/src/task.rs) für CPU-gebundene oder blockierende Arbeit. Verwenden Sietask::future(env, tag, work)für asynchrone Arbeit, die Tokio benötigt (z. B. Shell-Sessions). Übergeben Sie einCancelToken, wenn SietimeoutMsoderAbortSignalexponieren.
JS-Seite:
packages/natives/src/bindings.tsenthält das Basis-NativeBindings-Interface.packages/natives/src/<module>/types.tsdefiniert TS-Typen und erweitertNativeBindingsvia Declaration Merging.packages/natives/src/native.tsimportiert jede<module>/types.ts-Datei, um die Deklarationen zu aktivieren.packages/natives/src/<module>/index.tsumhüllt dasnative-Binding auspackages/natives/src/native.ts.packages/natives/src/native.tslädt das Addon undvalidateNativeerzwingt die erforderlichen Exports.packages/natives/src/index.tsre-exportiert den Wrapper für Aufrufer inpackages/*.
Portierungs-Checkliste
Abschnitt betitelt „Portierungs-Checkliste“- Rust-Implementierung hinzufügen
- Platzieren Sie die Kernlogik in einer einfachen Rust-Funktion.
- Wenn es ein neues Modul ist, fügen Sie es zu
crates/pi-natives/src/lib.rshinzu. - Exponieren Sie es mit
#[napi], damit das Standard-Mapping snake_case -> camelCase konsistent bleibt. - Halten Sie Signaturen owned und einfach:
String,Vec<String>,Uint8ArrayoderEither<JsString, Uint8Array>für große String-/Byte-Eingaben. - Für CPU-gebundene oder blockierende Arbeit verwenden Sie
task::blocking; für asynchrone Arbeit verwenden Sietask::future. Übergeben Sie einCancelTokenund rufen Sieheartbeat()in langen Schleifen auf.
- JS-Bindings verdrahten
- Fügen Sie die Typen und die
NativeBindings-Erweiterung inpackages/natives/src/<module>/types.tshinzu. - Importieren Sie
./<module>/typesinpackages/natives/src/native.ts, um Declaration Merging auszulösen. - Fügen Sie einen Wrapper in
packages/natives/src/<module>/index.tshinzu, dernativeaufruft. - Re-exportieren Sie aus
packages/natives/src/index.ts.
- Native-Validierung aktualisieren
- Fügen Sie
checkFn("newExport")invalidateNative(packages/natives/src/native.ts) hinzu.
- Benchmarks hinzufügen
- Platzieren Sie Benchmarks neben dem zugehörigen Paket (
packages/tui/bench,packages/natives/benchoderpackages/coding-agent/bench). - Fügen Sie eine JS-Baseline und die native Version im selben Durchlauf ein.
- Verwenden Sie
Bun.nanoseconds()und eine feste Iterationsanzahl. - Halten Sie die Benchmark-Eingaben klein und realistisch (tatsächliche Daten aus dem Hot Path).
- Native Binary bauen
bun --cwd=packages/natives run build- Verwenden Sie
bun --cwd=packages/natives run buildund setzen SiePI_DEV=1, wenn Sie beim Testen Loader-Diagnosen sehen möchten.
- Benchmark ausführen
bun run packages/<pkg>/bench/<bench>.ts(oderbun --cwd=packages/natives run bench)
- Über Verwendung entscheiden
- Wenn native langsamer ist, behalten Sie JS bei und lassen Sie den nativen Export ungenutzt.
- Wenn native schneller ist, wechseln Sie die Aufrufstellen zum nativen Wrapper.
Schmerzpunkte und wie man sie vermeidet
Abschnitt betitelt „Schmerzpunkte und wie man sie vermeidet“1) Veraltete pi_natives.node verhindert neue Exports
Abschnitt betitelt „1) Veraltete pi_natives.node verhindert neue Exports“Der Loader bevorzugt die plattform-getaggte Binary in packages/natives/native (pi_natives.<platform>-<arch>.node). PI_DEV=1 aktiviert jetzt nur Loader-Diagnosen; es wechselt nicht mehr zu einem separaten Dev-Addon-Dateinamen. Es gibt auch ein Fallback pi_natives.node. Kompilierte Binaries werden nach ~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node extrahiert. Wenn eine dieser Dateien veraltet ist, werden Exports nicht aktualisiert.
Lösung: Entfernen Sie die veraltete Datei vor dem Neubauen.
rm packages/natives/native/pi_natives.linux-x64.noderm packages/natives/native/pi_natives.nodebun --cwd=packages/natives run buildWenn Sie eine kompilierte Binary verwenden, löschen Sie das zwischengespeicherte Addon-Verzeichnis:
rm -rf ~/.xcsh/natives/<version>Überprüfen Sie dann, ob der Export in der Binary vorhanden ist:
bun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes("newExport"));'2) “Missing exports”-Fehler von validateNative
Abschnitt betitelt „2) “Missing exports”-Fehler von validateNative“Das ist gut — es verhindert stille Abweichungen. Wenn Sie dies sehen:
Native addon missing exports ... Missing: visibleWidthbedeutet das, dass Ihre Binary veraltet ist, der Rust-Exportname (oder expliziter Alias, wenn verwendet) nicht mit dem JS-Namen übereinstimmt, oder der Export nie kompiliert wurde. Beheben Sie den Build und die Namensabweichung, schwächen Sie nicht die Validierung.
3) Rust-Signatur-Mismatch
Abschnitt betitelt „3) Rust-Signatur-Mismatch“Halten Sie es einfach und owned. String, Vec<String> und Uint8Array funktionieren. Vermeiden Sie Referenzen wie &str in öffentlichen Exports. Wenn Sie strukturierte Daten benötigen, umhüllen Sie sie in #[napi(object)]-Structs.
4) Benchmarking-Fehler
Abschnitt betitelt „4) Benchmarking-Fehler“- Vergleichen Sie keine unterschiedlichen Eingaben oder Allokationen.
- Verwenden Sie für JS und native identische Eingabe-Arrays.
- Führen Sie beide in derselben Benchmark-Datei aus, um Verzerrungen zu vermeiden.
Benchmark-Vorlage
Abschnitt betitelt „Benchmark-Vorlage“const ITERATIONS = 2000;
function bench(name: string, fn: () => void): number { const start = Bun.nanoseconds(); for (let i = 0; i < ITERATIONS; i++) fn(); const elapsed = (Bun.nanoseconds() - start) / 1e6; console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`); return elapsed;}
bench("feature/js", () => { jsImpl(sample);});
bench("feature/native", () => { nativeImpl(sample);});Verifizierungs-Checkliste
Abschnitt betitelt „Verifizierungs-Checkliste“validateNativebesteht (keine fehlenden Exports).NativeBindingsist inpackages/natives/src/<module>/types.tserweitert und der Wrapper ist inpackages/natives/src/index.tsre-exportiert.Object.keys(require(...))enthält Ihren neuen Export.- Benchmark-Ergebnisse sind im PR/in den Notizen dokumentiert.
- Aufrufstelle nur dann aktualisiert, wenn native schneller oder gleichwertig ist.
Faustregel
Abschnitt betitelt „Faustregel“- Wenn native langsamer ist, wechseln Sie nicht. Behalten Sie den Export für zukünftige Arbeit, aber das TUI sollte auf dem schnelleren Pfad bleiben.
- Wenn native schneller ist, wechseln Sie die Aufrufstelle und behalten Sie den Benchmark bei, um Regressionen zu erkennen.