Programación: ¿Siempre es más lento lo de bajo nivel?

Fuentes: Hidden Overheads

Este artículo explora el concepto de "costos ocultos" en la programación, desafiando la idea de que los lenguajes de alto nivel son inherentemente menos eficientes que los lenguajes de bajo nivel como C o C++. El autor observa que los programadores de sistemas a menudo evitan lenguajes con recolección de basura (GC) debido a las pausas que impone, pero argumenta que incluso C y C++ tienen sus propias trampas de rendimiento no evidentes.

Recolección de Basura (GC) y Pausas: Los lenguajes con GC, como Go, automatizan la gestión de memoria, pero esto implica pausas periódicas para que el recolector de basura limpie la memoria no utilizada. Estas pausas pueden ser inaceptables en sistemas donde el control preciso del tiempo de ejecución es crucial.

Copy-on-Write (CoW) en Swift: Swift, con su enfoque en los valores, utiliza CoW para evitar copias innecesarias de datos. Aunque esto mejora la eficiencia en muchos casos, una asignación aparentemente simple (como data[x] = 10) puede desencadenar una copia masiva de memoria (memcpy) en segundo plano, un costo oculto.

Indexación de Cadenas Unicode: En Swift, la indexación de cadenas funciona sobre clústeres de grafemas extendidos (Unicode), en lugar de bytes individuales. Esto facilita la manipulación de texto para el usuario, pero convierte la indexación en una operación O(n) (lineal) en lugar de la esperada O(1) (constante), lo cual es perjudicial para el rendimiento en sistemas.

Stack Spilling: Cuando una función tiene demasiadas variables locales que no caben en los registros de la CPU, estas variables se "derraman" a la pila (stack spilling). El acceso a la pila es significativamente más lento que el acceso a los registros, impactando el rendimiento, especialmente en bucles críticos. El artículo sugiere que sería útil que el compilador emitiera errores si no puede asignar todas las variables locales a registros, pero esta idea no es común.

Memcpy Silencioso: La asignación de estructuras de datos grandes puede resultar en una copia de memoria (memcpy) masiva, sin que el programador sea notificado. Esto puede degradar significativamente el rendimiento sin una indicación clara del problema.

El autor concluye que la clave para evaluar estos costos ocultos no es su impacto absoluto en el rendimiento, sino su impacto en la complejidad temporal. Un memcpy grande puede ser tolerable si no cambia la complejidad temporal de un bucle, pero una operación O(n) donde se espera O(1) puede llevar a tiempos de ejecución inaceptablemente largos con grandes conjuntos de datos. El artículo plantea la pregunta de dónde trazar la línea entre los costos aceptables y los inaceptables, y sugiere que la complejidad temporal es el factor determinante.