tag - ¿Son los deltas de archivos del paquete de Git en lugar de instantáneas?
git tag best practices (3)
Una de las diferencias clave entre Git y la mayoría de los otros sistemas de control de versiones es que los otros tienden a almacenar confirmaciones como una serie de deltas, conjuntos de cambios entre una confirmación y la siguiente. Esto parece lógico, ya que es la cantidad más pequeña posible de información sobre una confirmación. Pero cuanto más largo sea el historial de compromisos, más cálculos se requieren para comparar rangos de revisiones.
Por el contrario, Git almacena una instantánea completa de todo el proyecto en cada revisión . La razón por la que esto no hace que el tamaño del repositorio crezca dramáticamente con cada confirmación es que cada archivo en el proyecto se almacena como un archivo en el subdirectorio Git, llamado así por el hash de su contenido. Entonces, si el contenido no ha cambiado, el hash no ha cambiado, y la confirmación solo apunta al mismo archivo. Y hay otras optimizaciones también.
Todo esto tenía sentido para mí hasta que tropecé con esta información sobre los archivos del paquete , en la que Git coloca los datos periódicamente para ahorrar espacio:
Para ahorrar ese espacio, Git utiliza el archivo de paquete. Este es un formato en el que Git solo guardará la parte que ha cambiado en el segundo archivo, con un puntero al archivo similar.
¿No se trata básicamente de volver a almacenar deltas? Si no, ¿cómo es diferente? ¿Cómo evita esto someter a Git a los mismos problemas que otros sistemas de control de versiones?
Por ejemplo, Subversion usa deltas, y revertir 50 versiones significa deshacer 50 diffs, mientras que con Git solo puede tomar la instantánea adecuada. A menos que git también almacene 50 diffs en los paquetes-archivos ... ¿hay algún mecanismo que diga "después de un pequeño número de deltas, almacenaremos una instantánea completamente nueva" para que no acumulemos un conjunto de cambios demasiado grande? ¿De qué otra forma podría evitar Git las desventajas de los deltas?
Como mencioné en " ¿Qué son los paquetes delgados de git? "
Git realiza la deslocalización solo en paquetes
Detallé la codificación delta utilizada para los archivos del paquete en " ¿Está estandarizado el algoritmo de diferencias bit git (almacenamiento delta)? ".
Ver también "¿ Cuándo y cómo usa Git deltas para el almacenamiento? ".
Tenga en cuenta que la configuración core.deltaBaseCacheLimit
que controla el tamaño predeterminado para el archivo del paquete pronto se verá afectada de 16MB a 96MB, para Git 2.0.x / 2.1 (Q3 2014).
Consulte la confirmación 4874f54 de David Kastrup (mayo de 2014):
Bump core.deltaBaseCacheLimit a 96m
El valor por defecto de 16m causa una fuerte contorsión para cadenas delta grandes combinadas con archivos grandes.
Aquí hay algunos puntos de referencia (variante de pu de
git blame
):
time git blame -C src/xdisp.c >/dev/null
para un repositorio de Emacs reempaquetado con
git gc --aggressive
(v1.9, que da como resultado un tamaño de ventana de 250) ubicado en un disco SSD.
El archivo en cuestión tiene aproximadamente 30000 líneas, 1Mb de tamaño y un historial con aproximadamente 2500 confirmaciones.
16m (previous default):
real 3m33.936s
user 2m15.396s
sys 1m17.352s
96m:
real 2m5.668s
user 1m50.784s
sys 0m14.288s
El uso del almacenamiento delta en el archivo del paquete es solo un detalle de la implementación. En ese nivel, Git no sabe por qué o cómo algo cambió de una revisión a la siguiente, sino que simplemente sabe que el blob B es bastante similar al blob A, excepto por estos cambios C. Por lo tanto, solo almacenará blob A y cambiará C (si elige hacerlo, también podría elegir almacenar blob A y blob B).
Al recuperar objetos del archivo del paquete, el almacenamiento delta no está expuesto a la persona que llama. La persona que llama aún ve blobs completos. Entonces, Git funciona de la misma manera que siempre, sin la optimización de almacenamiento delta.
Resumen:
Los archivos del paquete de Git están cuidadosamente construidos para usar de manera efectiva cachés de disco y proporcionar patrones de acceso "agradables" para comandos comunes y para leer objetos recientemente referenciados.
El formato de archivo del paquete de Git es bastante flexible (ver Documentation/technical/pack-format.txt , o The Packfile en The Git Community Book ). Los archivos de paquete almacenan objetos de dos formas principales: "sin degradar" (tomar los datos del objeto sin procesar y desinflar-comprimirlo) o "deltificar" (formar un delta contra algún otro objeto y luego desinflar-comprimir los datos delta resultantes). Los objetos almacenados en un paquete pueden estar en cualquier orden (no deben (necesariamente) ordenarse por tipo de objeto, nombre de objeto o cualquier otro atributo) y los objetos delificados pueden realizarse contra cualquier otro objeto adecuado del mismo tipo.
El comando pack-objects Git usa varias heuristics para proporcionar una excelente localidad de referencia para comandos comunes. Estas heurísticas controlan tanto la selección de objetos base para objetos delificados como el orden de los objetos. Cada mecanismo es en su mayoría independiente, pero comparten algunos objetivos.
Git forma largas cadenas de objetos comprimidos delta, pero la heurística trata de asegurarse de que solo los objetos "viejos" estén en los extremos de las cadenas largas. La memoria caché base delta (cuyo tamaño está controlado por la variable de configuración core.deltaBaseCacheLimit
) se usa automáticamente y puede reducir enormemente el número de "reconstrucciones" requeridas para los comandos que necesitan leer una gran cantidad de objetos (por ejemplo, git log -p
).
Heurística de compresión delta
Un repositorio típico de Git almacena una gran cantidad de objetos, por lo que no puede compararlos razonablemente para encontrar los pares (y las cadenas) que producirán las representaciones delta más pequeñas.
La heurística de selección de base delta se basa en la idea de que las buenas bases delta se encontrarán entre objetos con nombres de archivo y tamaños similares. Cada tipo de objeto se procesa por separado (es decir, un objeto de un tipo nunca se usará como base delta para un objeto de otro tipo).
A los fines de la selección de base delta, los objetos se ordenan (principalmente) por nombre de archivo y luego por tamaño. Una ventana en esta lista ordenada se usa para limitar el número de objetos que se consideran bases delta potenciales. Si no se encuentra una representación 1 delta "suficientemente buena" para un objeto entre los objetos en su ventana, entonces el objeto no se comprimirá delta.
El tamaño de la ventana está controlado por la opción --window=
de git pack-objects
, o la variable de configuración pack.window
. La profundidad máxima de una cadena delta está controlada por la opción --depth=
de git pack-objects
, o la variable de configuración pack.depth
. La opción --aggressive
de git gc
aumenta mucho el tamaño de la ventana y la profundidad máxima para intentar crear un archivo de paquete más pequeño.
La clasificación de nombre de archivo agrupa los objetos para las entradas con nombres idénticos (o al menos terminaciones similares (por ejemplo, .c
)). El tipo de tamaño va de mayor a menor, por lo que los deltas que eliminan datos son preferibles a los deltas que agregan datos (ya que los deltas de eliminación tienen representaciones más cortas) y los objetos mayores más antiguos (generalmente más nuevos) tienden a representarse con compresión simple.
1 Lo que califica como "lo suficientemente bueno" depende del tamaño del objeto en cuestión y su posible base delta, así como de cuán profunda sería la cadena delta resultante.
Heurística de ordenamiento de objetos
Los objetos se almacenan en los archivos del paquete en un orden de "referencia más reciente". Los objetos necesarios para reconstruir la historia más reciente se colocan antes en el paquete y estarán muy juntos. Esto generalmente funciona bien para cachés de disco del sistema operativo.
Todos los objetos de confirmación se ordenan por fecha de confirmación (la más reciente primero) y se almacenan juntos. Esta colocación y ordenación optimiza los accesos al disco necesarios para recorrer el gráfico del historial y extraer información básica de compromiso (por ejemplo, git log
).
Los objetos de árbol y blob se almacenan comenzando con el árbol desde la primera confirmación almacenada (más reciente). Cada árbol se procesa en profundidad, almacenando cualquier objeto que no haya sido almacenado. Esto coloca todos los árboles y blobs necesarios para reconstruir el compromiso más reciente en un solo lugar. Todos los árboles y blobs que aún no se han guardado pero que son necesarios para confirmaciones posteriores se almacenan a continuación, en el orden de confirmación ordenado.
El orden final de los objetos se ve ligeramente afectado por la selección base delta en el sentido de que si se selecciona un objeto para la representación delta y su objeto base aún no se ha almacenado, entonces su objeto base se almacena inmediatamente antes del objeto delificado. Esto evita posibles errores de caché de disco debido al acceso no lineal necesario para leer un objeto base que se habría "guardado" de forma natural más adelante en el archivo del paquete.