Add spanish translation - 'Algorithmic changes' section (#39)
* Add Algorithmic Changes section Roughly one third of the text has been translated, the rest is still the original text. * Add algorithms section translations * Complete the translation of Algorithmic Changes section * Improve translations * Improve translations * Update section title translation * Add missing main section title * Improve translation, orthography * Change CS101 phrase translation * Update section title * Update TOC * Fix link
This commit is contained in:
parent
cdaf593e8b
commit
1878a28b6a
@ -16,6 +16,9 @@ Las secciones posteriores cubren técnicas específicas de Go.
|
||||
### Tabla de contenidos
|
||||
|
||||
1. [Escribiendo y optimizando código en Go](performance-es.md#escribir-y-optimizar-codigo-en-go)
|
||||
1. [Cuándo y dónde optimizar](performance-es.md#cuándo-y-dónde-optimizar)
|
||||
1. [Modificar los datos](performance-es.md#modificar-los-datos)
|
||||
1. [Modificar los algoritmos](performance-es.md#modificar-los-algoritmos)
|
||||
|
||||
### Cómo contribuir
|
||||
|
||||
|
@ -21,7 +21,7 @@ Podemos resumir estas tres secciones como:
|
||||
2. "Sé deliberado"
|
||||
3. "Sé peligroso"
|
||||
|
||||
# Cuándo y dónde optimizar
|
||||
## Cuándo y dónde optimizar
|
||||
|
||||
Escribo esta sección primero porque es el paso más importante. Deberías estar haciendo esto?
|
||||
|
||||
@ -58,6 +58,8 @@ En la gran mayoría de casos, el tamaño y velocidad del programa no es el probl
|
||||
|
||||
Cuando hayas decidido cambiar tu programa, sigue leyendo.
|
||||
|
||||
## Cómo optimizar
|
||||
|
||||
### Modificar los datos
|
||||
|
||||
Modificar los datos significa agregar o alterar la representación de los datos
|
||||
@ -127,7 +129,7 @@ manejen gran cantidad de datos.
|
||||
|
||||
* Reorganiza tus datos
|
||||
|
||||
Elimina el padding. Remueve campos extra. Utiliza tipos de datos mas pequeños.
|
||||
Elimina el padding. Remueve campos extra. Utiliza tipos de datos más pequeños.
|
||||
|
||||
* Cambia a una estructura de datos más lenta
|
||||
|
||||
@ -177,9 +179,113 @@ Otro aspecto a considerar es el tiempo de transmisión de datos. Generalmente el
|
||||
acceso a disco y de red es muy lento, y por lo tanto ser capaz de cargar un
|
||||
bloque comprimido va a resultar en un proceso mucho más rápido incluso teniendo
|
||||
en cuenta el tiempo que lleva descomprimirlo. Como siempre, realiza benchmarks.
|
||||
Un formato binario generalmente va a ser mas liviano y rápido de parsear que uno
|
||||
Un formato binario generalmente va a ser más liviano y rápido de parsear que uno
|
||||
de texto, pero el coste es que no será legible para un humano.
|
||||
|
||||
Para la transferencia de datos, cambia a un protocol menos verboso, o
|
||||
mejora el API para aceptar consultas parciales. Por ejemplo, usa una consulta
|
||||
incremental en lugar de forzar a traer siempre el set de datos completo.
|
||||
|
||||
### Modificar los algoritmos
|
||||
|
||||
Si no estás modificando los datos, la alternativa más importante es modificar el código.
|
||||
|
||||
Es muy posible que las mejoras más importantes vengan de un cambio de algoritmo. Esto es equivalente a sustituir bubble sort (`O(n^2)`) con quicksort (`O(n log n)`) o reemplazar un acceso linear a un array (`O(n)`) con una búsqueda binaria (`O(log n)`) o una búsqueda en un mapa (`O(1)`).
|
||||
|
||||
Así es como el software se vuelve lento. Estructuras originalmente diseñadas para un propósito se reusan para algo que no habían sido diseñadas. Esto ocurre gradualmente.
|
||||
|
||||
Es importante tener un entendimiento intuitivo de los diferentes niveles de big-O. Elige la estructura de datos para tu problema. Esto no siempre ahorra ciclos de CPU, pero previene problemas de rendimiento que pueden no ser detectados hasta mucho más adelante.
|
||||
|
||||
Las clases básicas de complejidad son:
|
||||
|
||||
* O(1): acceso a un campo, un array o un mapa
|
||||
|
||||
Consejo: no te preocupes por ellos
|
||||
|
||||
* O(log n): búsqueda binaria
|
||||
|
||||
Consejo: sólo es un problema si se hace en un bucle
|
||||
|
||||
* O(n): bucle simple
|
||||
|
||||
Consejo: lo haces todo el tiempo
|
||||
|
||||
* O(n log n): divide y vencerás, ordenación
|
||||
|
||||
Consejo: sigue siendo bastante rapido
|
||||
|
||||
* O(n\*m): bucles anidados / cuadrático
|
||||
|
||||
Consejo: ten cuidado y limita el tamaño de tu conjunto de datos
|
||||
|
||||
* Cualquier cosa entre cuadrático y subexponencial
|
||||
|
||||
Consejo: no lo ejecutes en un millón de filas
|
||||
|
||||
* O(b ^ n), O(n!): exponencial y mayor
|
||||
|
||||
Consejo: vas a necesitar suerte si tienes más de una o dos docenas de datos
|
||||
|
||||
Link: <http://bigocheatsheet.com>
|
||||
|
||||
Supongamos que tienes que buscar en un conjunto desordenado de datos. "Debería usar búsqueda binaria" piensas, sabiendo que una búsqueda binaria es O(log n) que es más rapido que el O(n) de una búsqueda linear. Sin embargo, una búsqueda binaria requiere que los datos estén ordenados, lo que significa que tendrás que ordenarlos antes, que tarda O(n log n). Si haces muchas búsquedas, el coste inicial de la ordenación merecerá la pena. Pero, si sobre todo estas haciendo **lookups**, quizás usar un array fue una decisión equivocada y sería mejor usar un mapa con coste O(1).
|
||||
|
||||
Si tu estructura de datos es estática, entonces generalmente podrás hacerlo mucho mejor que en el caso de que fuera dinámica. Resultará más facil construir una estructura de datos óptima para tus patrones de búsqueda. Soluciones como minimal perfect hashing pueden tener más sentido aquí, o filtros de Bloom precalculados. Esto también tiene sentido si tu estructura de datos es "estática" durante un periodo largo de manera que puedas amortizar el coste inicial de su construcción en muchas búsquedas.
|
||||
|
||||
Escoje la estructura de datos más simple que sea razonable y continúa. Esto es elemental para escribir "software no lento". Este debe ser tu modo de desarrollar por defecto. Si sabes que necesitas acceso aleatorio, no escojas una lista enlazada. Si sabes
|
||||
que necesitas recorrer los datos en orden, no uses un mapa. Los requerimientos cambian
|
||||
y no siempre puedes averiguar el futuro. Haz una suposición razonable de la carga de trabajo.
|
||||
|
||||
<http://daslab.seas.harvard.edu/rum-conjecture/>
|
||||
|
||||
Estructuras de datos para problemas similares diferirán cuando hagan una parte de su trabajo. Un árbol binario se ordena a medida que se insertan elementos. Un array no-ordenado es más rapido al insertar pero no está ordenado: al acabar, para "finalizar", tienes que hacer la ordenación.
|
||||
|
||||
Cuando escribas un paquete para ser usado por otros, evita la tentación de optimizar por adelantado para cada caso de uso individual. Esto resultará en código ilegible. Las estructura de datos tienen por diseño un solo proposito. No puedes ni leer mentes ni predecir el futuro. Si un usuario dice "Tu paquete es demasiado lento para este caso de uso", una respuesta razonable puede ser "Entonces usa este otro paquete". Un paquete debe "hacer una cosa bien".
|
||||
|
||||
A veces, estructuras de datos hibridas proveerán las mejoras de rendimiento que necesitas. Por ejemplo, agrupando tus datos puedes limitar tu búsqueda a una sola agrupación. Esto todavía tiene un coste teórico de O(n), pero la constante será más pequeña. Volveremos a visitar estos tipos de ajustes cuando lleguemos a la parte de afinar programas.
|
||||
|
||||
Dos cosas que la gente olvida cuando se discuten notaciones big-O:
|
||||
|
||||
Primero, hay un factor constante. Dos algoritmos que tienen la misma complejidad algorítmica pueden tener diferentes factores constantes. Imagina que iteras una lista 100 veces frente a iterar una sola vez. Aunque ambas son O(n), una de ellas tiene un factor constante que es 100 veces mayor.
|
||||
|
||||
Estos factores constantes explican que aunque merge sort, quicksort y heapsort son todos O(n log n), todo el mundo use quicksort porque es el más rapido. Tiene el factor constante más pequeño.
|
||||
|
||||
La segunda cosa es que big-O solo dice "a medida que n crece a infinito". Habla de la tendencia de crecimiento, "A medida que los números crezcan, este es el factor de crecimiento que dominará el tiempo de ejecución". No dice nada sobre el rendimiento real o sobre como se comporta cuando n es pequeño.
|
||||
|
||||
Con frecuencia hay un punto de corte por debajo del cual un algoritmo más tonto es más rápido. Un buen ejemplo del paquete `sort` de la librería estandar de Go. La mayoría del tiempo usa quicksort, pero hace una pasada con shell sort y luego con insertion sort cuando el tamaño de la partición está por debajo de 12 elementos.
|
||||
|
||||
Para algunos algoritmos, el factor constante puede ser tan grande que este punto de corte puede ser mayor que cualquier input razonable. Esto es, el algoritmo O(n^2) es más rapido que el algoritmo O(n) para cualquier input con el que te vayas a encontrar.
|
||||
|
||||
Esto también significa que necesitas tener muestras representativas del tamaño de tu input tanto para escoger el algoritmo más apropiado como para escribir buenos benchmarks. ¿10 elementos? ¿1000 elementos? ¿1000000 elementos?
|
||||
|
||||
Esto también funciona en sentido contrario: por ejemplo, escoger una estructura de datos más compleja para obtener un crecimiento O(n) en lugar de O(n^2), aunque los benchmarks para inputs más pequeños sean más lentos. Esto también aplica para la mayoría de estructuras de datos que son lock-free. Son generalmente más lentas cuando se usan en un sólo hilo pero más escalables cuando hay muchos hilos usándolas.
|
||||
|
||||
La jerarquía de memoria en los ordenadores modernos confunde un poco el tema, en el sentido de que las caches prefieren el predecible acceso linear al recorrer un slice que el acceso aleatorio de seguir un puntero. Aún así, es mejor empezar con un buen algoritmo. Hablaremos más de esto en la sección sobre hardware.
|
||||
|
||||
> La pelea no siempre la ganará el más fuerte, ni la carrera el más rapido, pero esa es es la manera de apostar. -- <cite>Rudyard Kipling</cite>
|
||||
|
||||
A veces el mejor algoritmo para un problema específico no es un único algoritmo, sino un conjunto de algoritmos especializados en tipos de input ligeramente diferentes. Este "polialgoritmo" primero detecta el tipo de input que tiene que tratar y luego sigue el code path apropiado. De esta manera funciona el paquete `sort` mencionado anteriormente: determina el tamaño del problema y elige un algoritmo distinto. Además de combinar quicksort, shell sort e insertion sort, también controla el nivel de recursividad de quicksort y usa heapsort si es necesario. Los paquetes `string` y `bytes` hacen algo similar, detectando y especializando para diferentes casos. Como con la compresión de datos, cuanto más sepas sobre las características de tu input, mejor será tu solución especifica. Incluso si una optimización no siempre se puede aplicar, complicar tu código determinando que es seguro de usar y ejecutando una lógica diferente puede valer la pena.
|
||||
|
||||
Esto también aplica a los subproblemas que tu algoritmo tiene que solucionar. Por ejemplo, poder usar radix sort puede tener un impacto significativo en el rendimiento, o usar quicksort si sólo necesitas una ordenación parcial.
|
||||
|
||||
A veces, en vez de una especialización para tu tarea, el mejor enfoque es abstraer la tarea a un categoría de problemas más general que ya haya sido estudiada. Así podrás aplicar la solución más general a tu caso concreto. Mapear tu problema a un dominio con implementaciones bien estudiadas puede resultar en una ganancia significativa.
|
||||
|
||||
De manera similar, usar un algoritmo más simple significa que es más probable que las concesiones, analisis y detalles de la implementación hayan sido más estudiados y sean mejor entendidos que en otros algoritmos más esótericos, exóticos y complejos.
|
||||
|
||||
Los algoritmos más simples pueden ser más rápidos. Estos dos ejemplos no son casos aislados:
|
||||
https://go-review.googlesource.com/c/crypto/+/169037
|
||||
https://go-review.googlesource.com/c/go/+/170322/
|
||||
|
||||
TODO: notes on algorithm selection
|
||||
|
||||
TODO:
|
||||
improve worst-case behaviour at slight cost to average runtime
|
||||
linear-time regexp matching
|
||||
randomized algorithms: MC vs. LV
|
||||
improve worse-case running time
|
||||
skip-list, treap, randomized marking,
|
||||
primality testing, randomized pivot for quicksort
|
||||
power of two random choices
|
||||
statistical approximations (frequently depend on sample size and not population size)
|
||||
|
||||
TODO: batching to reduce overhead: https://lemire.me/blog/2018/04/17/iterating-in-batches-over-data-structures-can-be-much-faster/
|
||||
|
Loading…
Reference in New Issue
Block a user