DPDK para Redes de Alto Rendimiento: Guía del Kit de Desarrollo de Plano de Datos

Introducción

DPDK (Kit de Desarrollo de Plano de Datos) representa el marco líder en la industria para procesamiento de paquetes de alto rendimiento, permitiendo que las aplicaciones eviten la pila de red del kernel de Linux e interactúen directamente con el hardware de red. Desarrollado y mantenido por la Linux Foundation, DPDK impulsa la infraestructura de redes en las principales empresas tecnológicas incluyendo Intel, Cisco, Nokia, Ericsson, y proveedores de nube que procesan miles de millones de paquetes por segundo.

Las redes tradicionales de Linux sufren de limitaciones fundamentales de rendimiento: cambios de contexto del kernel, llamadas al sistema por paquete, sobrecarga de interrupciones y contaminación de caché de CPU restringen el rendimiento a ~1-2 millones de paquetes por segundo por núcleo. DPDK elimina estos cuellos de botella a través de procesamiento de paquetes en espacio de usuario, controladores en modo de sondeo, memoria de páginas grandes y dedicación de núcleos de CPU—logrando 10-100 millones de paquetes por segundo por núcleo dependiendo del tamaño del paquete y la complejidad del procesamiento.

Las organizaciones que construyen funciones de red, balanceadores de carga, firewalls, sistemas de detección de intrusiones, plataformas de streaming de video y soluciones de redes definidas por software aprovechan DPDK para un rendimiento imposible con redes del kernel. Los proveedores de telecomunicaciones despliegan funciones de red virtuales basadas en DPDK reemplazando dispositivos de hardware dedicados. Los CDN usan DPDK para procesamiento en el borde a cientos de gigabits por segundo. Las plataformas NFV (Virtualización de Funciones de Red) dependen de DPDK para densidad de rendimiento que permite múltiples funciones virtuales por servidor.

Aunque DPDK ofrece un rendimiento excepcional, requiere experiencia significativa: comprender hardware de red, gestión de memoria, arquitectura de CPU y patrones de diseño de aplicaciones difieren fundamentalmente de la programación tradicional de sockets. Las organizaciones que invierten en DPDK obtienen ventajas competitivas a través de eficiencia de infraestructura—procesar 10× más tráfico por servidor se traduce directamente en costos de hardware reducidos y mejores economías de servicio.

Esta guía completa explora implementaciones DPDK de nivel empresarial, cubriendo fundamentos de arquitectura, configuración de entorno de desarrollo, patrones de aplicaciones, optimización de rendimiento, estrategias de integración y mejores prácticas operacionales esenciales para despliegues DPDK de producción.

Teoría y Conceptos Fundamentales

Arquitectura DPDK

DPDK consiste en varios componentes integrados:

Controladores en Modo de Sondeo (PMDs): Controladores en espacio de usuario que evitan el kernel, sondeando interfaces de red continuamente en lugar de E/S dirigida por interrupciones. Elimina la sobrecarga de interrupciones y cambios de contexto.

Gestión de Memoria: Usa páginas grandes (2MB/1GB) reduciendo fallos TLB y mejorando el rendimiento de acceso a memoria. Pre-asigna pools de memoria (mempools) para manejo de paquetes sin copia.

Bibliotecas de Anillos: Colas multi-productor/multi-consumidor sin bloqueos que permiten paso eficiente de paquetes entre núcleos sin sobrecarga de sincronización.

Afinidad de Núcleos: Dedica núcleos de CPU al procesamiento de paquetes, previniendo interferencia del planificador y asegurando rendimiento determinista.

Marco de Paquetes: Abstracciones de nivel superior para construir pipelines de procesamiento de paquetes—tablas, ACLs, QoS, aceleradores de criptografía.

Ventajas de Rendimiento

DPDK logra rendimiento superior a través de:

Procesamiento Sin Copia: Los paquetes permanecen en memoria accesible por NIC durante todo el procesamiento. Sin copias kernel-espacio de usuario.

Operaciones por Lotes: Procesa múltiples paquetes juntos, amortizando la sobrecarga por paquete.

Optimización de Caché de CPU: Estructuras de datos alineadas a líneas de caché, algoritmos de precarga, asignación de memoria consciente de NUMA.

Descargas de Hardware: Aprovecha capacidades de NIC—cálculo de checksum, segmentación, RSS (Escalado de Recepción), director de flujo.

Cambios de Contexto Eliminados: El modelo de sondeo elimina la interacción con el kernel, patrones de ejecución predecibles.

DPDK vs Redes Tradicionales

Entendiendo las diferencias fundamentales:

Redes Tradicionales de Linux:

  • Llegada de paquetes dirigida por interrupciones
  • Procesamiento del kernel por paquete
  • Llamadas al sistema para enviar/recibir
  • Sobrecarga de pila TCP/IP del kernel
  • ~1-2 millones PPS por núcleo

Redes DPDK:

  • Verificación continua en modo de sondeo
  • Procesamiento de paquetes en espacio de usuario
  • Operaciones por lotes (32-256 paquetes)
  • La aplicación implementa protocolos
  • ~10-100 millones PPS por núcleo

Compromisos:

  • DPDK: Máximo rendimiento, desarrollo complejo
  • Linux: Facilidad de uso, herramientas estándar, menor rendimiento

Casos de Uso

DPDK sobresale en escenarios específicos:

Reenvío de Paquetes: Routers, switches, balanceadores de carga que requieren reenvío a velocidad de línea.

Inspección Profunda de Paquetes: Sistemas IDS/IPS analizando cargas útiles de paquetes a altas velocidades.

Funciones de Red: Gateways VPN, firewalls, dispositivos NAT en entornos NFV.

Streaming de Medios: Plataformas de entrega de video procesando flujos RTP/RTCP.

Aplicaciones Financieras: Procesamiento de datos de mercado de baja latencia, enrutamiento de órdenes.

Equipos de Prueba: Generadores de tráfico, emuladores de red, analizadores de protocolos.

Requisitos Previos

Requisitos de Hardware

Tarjetas de Interfaz de Red Soportadas:

  • Intel: X710, XXV710, E810 (recomendado)
  • Mellanox: ConnectX-4/5/6
  • Broadcom: NetXtreme-E/BCM57xxx
  • AMD/Xilinx: Adaptadores Alveo
  • Virtual: virtio-net (para VMs)

Requisitos de CPU:

  • Arquitectura x86_64 (soporte principal)
  • ARM64 (soporte creciente)
  • POWER (soporte limitado)
  • Sistema multi-núcleo (8+ núcleos recomendados)
  • Instrucciones SSE4.2/AVX/AVX2 (características de rendimiento)

Memoria:

  • 16GB RAM mínimo (32GB+ recomendado)
  • Sistemas habilitados para NUMA para rendimiento óptimo
  • Soporte de páginas grandes (2MB o 1GB)

Configuración de BIOS:

  • VT-d/IOMMU habilitado (para VFIO)
  • Hyperthreading deshabilitado (para apps sensibles a latencia)
  • C-states deshabilitados (frecuencia constante)
  • Consideración de Turbo Boost (según requisitos)

Requisitos de Software

Sistema Operativo:

  • Ubuntu 20.04/22.04 LTS
  • RHEL/Rocky Linux 8/9
  • Debian 11/12
  • Fedora (última versión)

Requisitos del Kernel:

  • Kernel 4.x+ (5.x+ recomendado)
  • Soporte VFIO habilitado
  • IOMMU habilitado

Herramientas de Desarrollo:

# Ubuntu/Debian
apt update
apt install -y build-essential meson ninja-build pkg-config \
  libnuma-dev python3-pip python3-pyelftools \
  linux-headers-$(uname -r)

# RHEL/Rocky
dnf groupinstall -y "Development Tools"
dnf install -y meson ninja-build numactl-devel \
  python3-pip python3-pyelftools kernel-devel

Instalación de DPDK

Instalar desde Paquete (más fácil):

# Ubuntu
apt install -y dpdk dpdk-dev dpdk-doc

# RHEL/Rocky
dnf install -y dpdk dpdk-devel dpdk-tools

Compilar desde Fuente (recomendado para últimas características):

# Download DPDK
wget https://fast.dpdk.org/rel/dpdk-23.11.tar.xz
tar xf dpdk-23.11.tar.xz
cd dpdk-23.11

# Configure build
meson setup build

# Compile
cd build
ninja

# Install
ninja install
ldconfig

# Verify installation
dpdk-testpmd --version

Configuración Avanzada

Configuración del Sistema

Habilitar Páginas Grandes:

# Allocate 2MB huge pages (8GB total)
echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# Verify
grep HugePages /proc/meminfo

# Mount hugetlbfs
mkdir -p /mnt/huge
mount -t hugetlbfs nodev /mnt/huge

# Make persistent
cat >> /etc/fstab << EOF
nodev /mnt/huge hugetlbfs defaults 0 0
EOF

# Add to sysctl
echo "vm.nr_hugepages = 4096" >> /etc/sysctl.d/99-dpdk.conf
sysctl -p /etc/sysctl.d/99-dpdk.conf

Cargar Módulos del Kernel Requeridos:

# Load VFIO driver (recommended, safer than UIO)
modprobe vfio-pci

# Enable IOMMU (if not in kernel command line)
# Add to /etc/default/grub:
# GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt"
# Then: grub-mkconfig -o /boot/grub/grub.cfg && reboot

# Verify IOMMU
dmesg | grep -i iommu

# Load at boot
echo "vfio-pci" >> /etc/modules-load.d/dpdk.conf

Vincular Interfaz de Red a DPDK:

# Install dpdk-devbind utility
# (Usually at /usr/local/bin/dpdk-devbind.py or /usr/bin/dpdk-devbind)

# Check current NIC status
dpdk-devbind.py --status

# Identify NIC to bind (example: eth1 = 0000:01:00.0)
lspci | grep Ethernet

# Bind to VFIO-PCI
dpdk-devbind.py --bind=vfio-pci 0000:01:00.0

# Verify binding
dpdk-devbind.py --status

# Create persistent binding script
cat > /usr/local/bin/dpdk-bind-nics.sh << 'EOF'
#!/bin/bash
dpdk-devbind.py --bind=vfio-pci 0000:01:00.0
dpdk-devbind.py --bind=vfio-pci 0000:01:00.1
EOF

chmod +x /usr/local/bin/dpdk-bind-nics.sh

Configurar Aislamiento de CPU (para núcleos dedicados):

# Edit /etc/default/grub
GRUB_CMDLINE_LINUX="isolcpus=4-7 nohz_full=4-7 rcu_nocbs=4-7"

# Update GRUB
grub-mkconfig -o /boot/grub/grub.cfg
reboot

# Verify isolation
cat /sys/devices/system/cpu/isolated

Aplicación DPDK Básica

Reenviador de Paquetes Simple:

// simple_fwd.c - Basic DPDK packet forwarder

#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>

#define RX_RING_SIZE 1024
#define TX_RING_SIZE 1024
#define NUM_MBUFS 8191
#define MBUF_CACHE_SIZE 250
#define BURST_SIZE 32

static const struct rte_eth_conf port_conf_default = {
    .rxmode = {
        .max_lro_pkt_size = RTE_ETHER_MAX_LEN,
    },
};

// Initialize port
static int port_init(uint16_t port, struct rte_mempool *mbuf_pool) {
    struct rte_eth_conf port_conf = port_conf_default;
    const uint16_t rx_rings = 1, tx_rings = 1;
    uint16_t nb_rxd = RX_RING_SIZE;
    uint16_t nb_txd = TX_RING_SIZE;
    int retval;
    struct rte_eth_dev_info dev_info;

    if (!rte_eth_dev_is_valid_port(port))
        return -1;

    retval = rte_eth_dev_info_get(port, &dev_info);
    if (retval != 0)
        return retval;

    // Configure device
    retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
    if (retval != 0)
        return retval;

    // Allocate and setup RX queue
    retval = rte_eth_rx_queue_setup(port, 0, nb_rxd,
            rte_eth_dev_socket_id(port), NULL, mbuf_pool);
    if (retval < 0)
        return retval;

    // Allocate and setup TX queue
    retval = rte_eth_tx_queue_setup(port, 0, nb_txd,
            rte_eth_dev_socket_id(port), NULL);
    if (retval < 0)
        return retval;

    // Start device
    retval = rte_eth_dev_start(port);
    if (retval < 0)
        return retval;

    // Enable promiscuous mode
    retval = rte_eth_promiscuous_enable(port);
    if (retval != 0)
        return retval;

    return 0;
}

// Main forwarding loop
static void lcore_main(void) {
    uint16_t port;

    RTE_ETH_FOREACH_DEV(port) {
        if (rte_eth_dev_socket_id(port) > 0 &&
            rte_eth_dev_socket_id(port) != (int)rte_socket_id())
            printf("WARNING: Port %u on remote NUMA node\n", port);
    }

    printf("Core %u forwarding packets\n", rte_lcore_id());

    // Main loop
    for (;;) {
        RTE_ETH_FOREACH_DEV(port) {
            // Receive packets
            struct rte_mbuf *bufs[BURST_SIZE];
            const uint16_t nb_rx = rte_eth_rx_burst(port, 0, bufs, BURST_SIZE);

            if (unlikely(nb_rx == 0))
                continue;

            // Forward to opposite port (0->1, 1->0)
            const uint16_t dst_port = port ^ 1;

            // Send packets
            const uint16_t nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx);

            // Free unsent packets
            if (unlikely(nb_tx < nb_rx)) {
                uint16_t buf;
                for (buf = nb_tx; buf < nb_rx; buf++)
                    rte_pktmbuf_free(bufs[buf]);
            }
        }
    }
}

int main(int argc, char *argv[]) {
    struct rte_mempool *mbuf_pool;
    unsigned nb_ports;
    uint16_t portid;

    // Initialize EAL
    int ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

    argc -= ret;
    argv += ret;

    // Check ports
    nb_ports = rte_eth_dev_count_avail();
    if (nb_ports < 2 || (nb_ports & 1))
        rte_exit(EXIT_FAILURE, "Error: need even number of ports\n");

    // Create mempool
    mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
        MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

    if (mbuf_pool == NULL)
        rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

    // Initialize ports
    RTE_ETH_FOREACH_DEV(portid)
        if (port_init(portid, mbuf_pool) != 0)
            rte_exit(EXIT_FAILURE, "Cannot init port %u\n", portid);

    // Call lcore_main on main core
    lcore_main();

    // Cleanup
    RTE_ETH_FOREACH_DEV(portid) {
        rte_eth_dev_stop(portid);
        rte_eth_dev_close(portid);
    }

    rte_eal_cleanup();

    return 0;
}

Compilar y Ejecutar:

# Compile
gcc -o simple_fwd simple_fwd.c \
  $(pkg-config --cflags --libs libdpdk)

# Run with DPDK arguments
./simple_fwd -l 0-1 -n 4 --

# DPDK EAL arguments:
# -l 0-1: Use cores 0 and 1
# -n 4: Memory channels
# --: Separator between EAL and application args

Generación de Tráfico con pktgen-dpdk

Instalar pktgen:

# Clone pktgen
git clone http://dpdk.org/git/apps/pktgen-dpdk
cd pktgen-dpdk

# Build
meson setup build
cd build
ninja

# Run pktgen
./usr/local/bin/pktgen -l 0-4 -n 4 -- -P -m "[1-2].0, [3-4].1"

# -P: Promiscuous mode
# -m: Core to port mapping

Comandos de Pktgen:

# Set packet size
set 0 size 64

# Set rate (%)
set 0 rate 100

# Set destination MAC
set 0 dst mac 00:11:22:33:44:55

# Set destination IP
set 0 dst ip 192.168.1.100

# Start traffic
start 0

# Stop traffic
stop 0

# Show statistics
page stats

Uso de testpmd

testpmd Básico:

# Start testpmd
dpdk-testpmd -l 0-3 -n 4 -- -i --nb-cores=2 --rxq=2 --txq=2

# testpmd commands:
# Start forwarding
testpmd> start

# Show port statistics
testpmd> show port stats all

# Show port info
testpmd> show port info all

# Set forwarding mode
testpmd> set fwd io  # or mac, macswap, flowgen, etc.

# Stop forwarding
testpmd> stop

# Quit
testpmd> quit

Pruebas de Rendimiento:

# RFC2544 throughput test
dpdk-testpmd -l 0-3 -n 4 -- -i --nb-cores=2 \
  --forward-mode=txonly --tx-first --stats-period=1

# Measure with increasing packet rates
# Observe packet loss at different rates
# Determine maximum forwarding rate

Optimización de Rendimiento

Optimización de CPU

Estrategia de Asignación de Núcleos:

# Dedicate cores to specific functions
# Example 8-core system:
# Core 0: OS and background tasks
# Core 1: Control plane
# Cores 2-3: RX processing
# Cores 4-5: TX processing
# Cores 6-7: Packet processing logic

# Run with specific core allocation
dpdk-app -l 2-7 -n 4 -- --rx-cores=2-3 --tx-cores=4-5 --worker-cores=6-7

Asignación de Memoria Consciente de NUMA:

// Allocate memory on local NUMA node
struct rte_mempool *mbuf_pool;
mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL",
    NUM_MBUFS, MBUF_CACHE_SIZE, 0,
    RTE_MBUF_DEFAULT_BUF_SIZE,
    rte_socket_id());  // Use local socket

// Check port NUMA node
unsigned socket_id = rte_eth_dev_socket_id(port_id);
if (socket_id != rte_socket_id())
    printf("Warning: Port on remote NUMA node\n");

Ajuste del Tamaño de Ráfaga de Paquetes

// Optimal burst size depends on workload
#define BURST_SIZE 32  // Typical starting point

// Test different burst sizes
for (burst_size = 16; burst_size <= 256; burst_size *= 2) {
    // Benchmark at each burst size
    // Measure throughput and latency
}

// Larger bursts: higher throughput, higher latency
// Smaller bursts: lower latency, potentially lower throughput

Configuración de Descarga de Hardware

// Enable hardware offloads
struct rte_eth_conf port_conf = {
    .rxmode = {
        .offloads = RTE_ETH_RX_OFFLOAD_CHECKSUM |
                    RTE_ETH_RX_OFFLOAD_RSS_HASH,
    },
    .txmode = {
        .offloads = RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
                    RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
                    RTE_ETH_TX_OFFLOAD_UDP_CKSUM,
    },
};

// Configure RSS (Receive Side Scaling)
struct rte_eth_rss_conf rss_conf = {
    .rss_key = NULL,  // Use default key
    .rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP,
};

Precarga y Optimización de Caché

// Prefetch packet data
for (i = 0; i < nb_rx; i++) {
    rte_prefetch0(rte_pktmbuf_mtod(bufs[i], void *));
}

// Process packets
for (i = 0; i < nb_rx; i++) {
    // Prefetch next packet while processing current
    if (i + 1 < nb_rx)
        rte_prefetch0(rte_pktmbuf_mtod(bufs[i + 1], void *));

    // Process current packet
    process_packet(bufs[i]);
}

// Align structures to cache lines
struct __rte_cache_aligned stats {
    uint64_t rx_packets;
    uint64_t tx_packets;
};

Monitoreo y Observabilidad

Estadísticas DPDK

Estadísticas de Ethdev:

// Get port statistics
struct rte_eth_stats stats;
rte_eth_stats_get(port_id, &stats);

printf("Port %u statistics:\n", port_id);
printf("  RX packets: %lu\n", stats.ipackets);
printf("  TX packets: %lu\n", stats.opackets);
printf("  RX bytes: %lu\n", stats.ibytes);
printf("  TX bytes: %lu\n", stats.obytes);
printf("  RX errors: %lu\n", stats.ierrors);
printf("  TX errors: %lu\n", stats.oerrors);
printf("  RX missed: %lu\n", stats.imissed);

// Reset statistics
rte_eth_stats_reset(port_id);

Estadísticas Extendidas:

# testpmd extended stats
testpmd> show port xstats all

# Programmatic access
int nb_xstats = rte_eth_xstats_get_names(port_id, NULL, 0);
struct rte_eth_xstat_name *xstats_names = malloc(sizeof(*xstats_names) * nb_xstats);
rte_eth_xstats_get_names(port_id, xstats_names, nb_xstats);

Script de Monitoreo de Rendimiento

#!/bin/bash
# monitor_dpdk.sh

PORT=0
INTERVAL=1

while true; do
    clear
    echo "=== DPDK Port $PORT Statistics ==="
    date
    echo ""

    dpdk-telemetry /ethdev/stats,$PORT 2>/dev/null || \
        echo "Telemetry not available, use testpmd"

    sleep $INTERVAL
done

Solución de Problemas

Páginas Grandes No Disponibles

Síntoma: La inicialización de DPDK falla con "cannot get hugepage information".

Diagnóstico:

# Check huge pages
grep HugePages /proc/meminfo

# Check mount
mount | grep hugetlbfs

Resolución:

# Allocate huge pages
echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# Mount hugetlbfs
mkdir -p /mnt/huge
mount -t hugetlbfs nodev /mnt/huge

Problemas de Vinculación de NIC

Síntoma: No se puede vincular NIC al controlador DPDK.

Diagnóstico:

# Check current binding
dpdk-devbind.py --status

# Check if NIC in use
ip link show

# Check IOMMU
dmesg | grep -i iommu

Resolución:

# Bring interface down first
ip link set eth1 down

# Unbind from kernel driver
dpdk-devbind.py --unbind 0000:01:00.0

# Bind to DPDK driver
dpdk-devbind.py --bind=vfio-pci 0000:01:00.0

# Verify
dpdk-devbind.py --status

Bajo Rendimiento

Síntoma: No se logran las tasas de paquetes esperadas.

Diagnóstico:

# Check CPU frequency
cat /proc/cpuinfo | grep MHz

# Monitor CPU usage
mpstat -P ALL 1

# Check for packet drops
testpmd> show port stats all

Resolución:

# Set performance governor
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
    echo performance > $cpu
done

# Disable power management
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo

# Increase burst size
# Tune application for specific workload

# Enable hardware offloads
# Check NIC firmware version

Conclusión

DPDK representa el marco principal para procesamiento de paquetes de alto rendimiento, ofreciendo mejoras de orden de magnitud sobre las redes del kernel a través de controladores en espacio de usuario, operación en modo de sondeo y estructuras de datos cuidadosamente optimizadas. Las organizaciones que construyen aplicaciones intensivas en red obtienen ventajas competitivas sustanciales a través del rendimiento excepcional de DPDK—procesar 10-100 millones de paquetes por segundo permite la consolidación de infraestructura y mejores economías.

El despliegue exitoso de DPDK requiere una comprensión profunda del hardware de red, arquitectura de CPU, gestión de memoria y patrones de diseño de aplicaciones fundamentalmente diferentes de la programación tradicional de sockets. La curva de aprendizaje es sustancial, pero las ganancias de rendimiento justifican la inversión para cargas de trabajo sensibles a la latencia e intensivas en rendimiento.

A medida que las velocidades de red aumentan hacia 100GbE y 400GbE, DPDK se vuelve cada vez más crítico para el procesamiento de paquetes basado en software capaz de mantenerse al ritmo de las capacidades del hardware. Las plataformas NFV, routers de software, dispositivos de seguridad y redes de entrega de contenido dependen de DPDK para niveles de rendimiento que hacen viables económicamente las implementaciones de software como alternativas al hardware dedicado.

Los ingenieros que dominan DPDK se posicionan en la intersección de redes, programación de sistemas y optimización de rendimiento—habilidades cada vez más valiosas a medida que las redes evolucionan hacia arquitecturas definidas por software que demandan rendimiento extremo de plataformas de hardware commodity.