NIFs cuando es necesario
Código nativo para operaciones críticas de rendimiento.⏱️ 2.5 horas
¿Qué son los NIFs?
NIFs (Native Implemented Functions) son funciones escritas en C/C++/Rust que se ejecutan directamente en el contexto del scheduler de la BEAM.
⚠️ Peligros de NIFs
Un NIF que bloquea o crashea afecta directamente al scheduler. Puede congelar o crashear toda la VM. Usa NIFs solo cuando realmente los necesites y después de agotar opciones puras de Elixir.
Rustler: NIFs en Rust
# mix.exs
{:rustler, "~> 0.29"}
# Generar scaffold
mix rustler.new
Ejemplo: hash rápido
// native/fast_hash/src/lib.rs
use rustler::{Binary, NifResult};
use xxhash_rust::xxh3::xxh3_64;
#[rustler::nif]
fn hash(data: Binary) -> u64 {
xxh3_64(data.as_slice())
}
rustler::init!("Elixir.FastHash", [hash]);
# lib/fast_hash.ex
defmodule FastHash do
use Rustler, otp_app: :my_app, crate: "fast_hash"
def hash(_data), do: :erlang.nif_error(:nif_not_loaded)
end
Dirty NIFs para operaciones largas
// Dirty CPU scheduler (no bloquea scheduler normal)
#[rustler::nif(schedule = "DirtyCpu")]
fn expensive_calculation(data: Binary) -> Vec<u8> {
// Operación que toma > 1ms
}
// Dirty I/O scheduler
#[rustler::nif(schedule = "DirtyIo")]
fn blocking_io() -> NifResult<String> {
// I/O que bloquea
}
Yielding NIFs
// Para operaciones muy largas, yield periódicamente
#[rustler::nif]
fn process_large_data(env: Env, data: Binary) -> NifResult<Term> {
let mut work_done = 0;
for chunk in data.as_slice().chunks(1000) {
// Procesar chunk
work_done += process_chunk(chunk);
// Yield al scheduler periódicamente
if work_done % 10000 == 0 {
if rustler::schedule::consume_timeslice(env, 1) {
// Reschedule si se acabó el timeslice
return Err(rustler::Error::RaiseAtom("reschedule"));
}
}
}
Ok(work_done.encode(env))
}
Cuándo usar NIFs
| Caso de uso | NIF? | Alternativa |
|---|---|---|
| Crypto/hashing | Sí, si no hay NIF de OTP | :crypto (ya es NIF) |
| Parsing binario complejo | Quizás | Binary pattern matching |
| Cálculos numéricos intensivos | Sí | Nx/EXLA |
| JSON encode/decode | No | Jason/jiffy |
| Compresión | Ya existe | :zlib |
Ports como alternativa segura
# Port: proceso externo, no crashea la VM
port = Port.open({:spawn, "./my_program"}, [:binary])
send(port, {self(), {:command, data}})
receive do
{^port, {:data, result}} -> result
end
Ejercicio 20.1
NIF de checksum
Intermedio
Implementa un NIF en Rust para calcular checksums CRC32:
- Usa la crate crc32fast
- Benchmark contra :erlang.crc32
- Implementa versión dirty para datos grandes
Ejercicio 20.2
Evaluar necesidad de NIF
Avanzado
Toma una función de tu protocolo binario:
- Identifica si es candidata a NIF
- Implementa versión NIF y compara
- Documenta trade-offs (complejidad vs rendimiento)
Conexión con el proyecto final
Probablemente NO necesitaremos NIFs para el sistema de distribución. Elixir puro suele ser suficiente. Sin embargo, si identificamos cuellos de botella en:
- Serialización: protocolo binario custom muy complejo
- Compresión: lz4 nativo podría ayudar
- Checksums: verificación de integridad de mensajes