A principios de este verano, el equipo GO lanzó la versión V0.12 de GOPLS, el servidor de idiomas para GO, con una reescritura de su núcleo que le permite escalar a bases de código más grandes. Esta es la culminación de un esfuerzo de un año, y estamos entusiasmados de compartir nuestro progreso, así como hablar un poco sobre la nueva arquitectura y lo que significa para el futuro de Gopls.
Desde el lanzamiento de V0.12, hemos ajustado el nuevo diseño, centrándonos en hacer consultas interactivas (como la completación automática o la búsqueda de referencias) tan rápido como lo fueron con V0.11, a pesar de mantener mucho menos estado en la memoria. Si aún no lo has hecho, esperamos que lo pruebes:
$ go set up golang.org/x/instruments/gopls@newest
Nos encantaría saber sobre su experiencia con esta breve encuesta.
Reducciones en el uso de la memoria y el tiempo de inicio
Antes de sumergirnos en los detalles, ¡veamos los resultados! El cuadro a continuación muestra el cambio en el tiempo de inicio y el uso de la memoria para 28 de los repositorios de GO más populares en GitHub. Estas medidas se tomaron después de abrir un archivo GO seleccionado al azar y esperar que los gopls carguen completamente su estado, y dado que asumimos que la indexación inicial se amortiza en muchas sesiones de edición, tomamos estas medidas las medidas de las medidas. segundo tiempo abrimos el archivo.
En estos reposos, los ahorros promedian alrededor del 75%, pero las reducciones de memoria no son lineales: a medida que los proyectos se hacen más grandes, también lo hace la disminución relativa en el uso de la memoria. Explicaremos esto con más detalle a continuación.
GoPls y el ecosistema en evolución GO
GOPLS proporciona a los editores de lenguaje agnóstico características similares a IDE, como completación automática, formato, referencias cruzadas y refactorización. Desde sus inicios en 2018, GOPLS ha consolidado muchas herramientas de línea de comandos dispares como Guru, Gorename y Goimorts y se ha convertido en el Backend predeterminado para la extensión VS Code Go así como muchos otros editores y complementos LSP. Tal vez has estado usando Gopls a través de tu editor sin siquiera saberlo, ¡ese es el objetivo!
Hace cinco años, Gopls ofreció un rendimiento mejorado simplemente al mantener una sesión con estado. Mientras que las herramientas de línea de comandos más antiguas tenían que comenzar desde cero cada vez que ejecutaban, los GPLS podían ahorrar resultados intermedios para reducir significativamente la latencia. Pero todo ese estado llegó con un costo, y con el tiempo escuchamos cada vez más a los usuarios que el alto uso de la memoria de Gopls period apenas tolerable.
Mientras tanto, el ecosistema GO estaba creciendo, con más código escrito en repositorios más grandes. GO espacios de trabajo Permitieron a los desarrolladores trabajar en múltiples módulos simultáneamente, y el desarrollo contenedorizado coloca los servidores de idiomas en entornos cada vez más limitados por recursos. Las bases de código se estaban haciendo más grandes, y los entornos de desarrolladores se estaban volviendo más pequeños. Necesitábamos cambiar la forma en que se ampliaron los gopl para mantenerse al día.
Revisando los orígenes del compilador de Gopls
En muchos sentidos, GOPLS se asemeja a un compilador: tiene que leer, analizar, verificar y analizar archivos de origen, para los cuales utiliza muchos de los bloques de construcción del compilador proporcionados por la biblioteca estándar GO y el módulo golang.org/x/instruments. Estos bloques de construcción utilizan la técnica de “programación simbólica”: en un compilador en ejecución hay un solo objeto o “símbolo” que representa cada función como fmt.Println
. Cualquier referencia a una función se representa como un puntero a su símbolo.
Para probar si dos referencias están hablando del mismo símbolo, no necesita pensar en los nombres. Simplemente comparas punteros. Un puntero es mucho más pequeño que una cadena, y la comparación del puntero es muy barata, por lo que los símbolos son una forma eficiente de representar una estructura tan compleja como un programa.
Para responder rápidamente a las solicitudes, Gopls V0.11 mantuvo todos estos símbolos en la memoria, como si Gopls fuera compilando todo su programa a la vez. El resultado fue una huella de memoria que fue proporcional y mucho mayor que el código fuente que se está editando (por ejemplo, los árboles de sintaxis escrita son típicamente 30 veces más grandes que el texto fuente!).
Compilación separada
Los diseñadores de los primeros compiladores en la década de 1950 descubrieron rápidamente los límites de la compilación monolítica. Su solución period dividir el programa en unidades y compilar cada unidad por separado. La compilación separada permite construir un programa que no se ajuste en la memoria, al hacerlo en piezas pequeñas. En Go, las unidades son paquetes.
La compilación de diferentes paquetes no se puede separar por completo: al compilar un paquete P, el compilador aún necesita información sobre lo que proporciona los paquetes que P importa. Para organizar esto, el sistema GO Construct compila todos los paquetes importados de P antes de P en sí, y el compilador GO escribe un resumen compacto de la API exportada de cada paquete. Los resúmenes de los paquetes importados de P se proporcionan como entradas para la compilación de P en sí.
GOPLS V0.12 aporta una compilación separada a GOPL, reutilizando el mismo formato de resumen del paquete utilizado por el compilador. La thought es easy, pero hay sutileza en los detalles. Reescribimos cada algoritmo que previamente inspeccionaba la estructura de datos que representa todo el programa, por lo que ahora funciona en un paquete a la vez y guarda los resultados por paquete a los archivos, al igual que un código de objeto que emite un compilador.
Por ejemplo, encontrar todas las referencias a una función solía ser tan fácil como buscar en la estructura de datos del programa para todas las ocurrencias de un valor de puntero explicit.
Ahora, cuando GOPLS procesa cada paquete, debe construir y guardar un índice que asocie cada ubicación del identificador en el código fuente con el nombre del símbolo al que se refiere. En el momento de la consulta, Gopls carga y busca estos índices. Otras consultas globales, como “encontrar implementaciones”, utilizan técnicas similares.
Como el go construct
Comando, GOPLS ahora utiliza un almacén de caché basado en archivos para registrar resúmenes de información calculada a partir de cada paquete, incluido el tipo de cada declaración, el índice de referencias cruzadas y el conjunto de métodos de cada tipo. Dado que el caché persiste en los procesos, notará que la segunda vez que inicia GOPLS en su espacio de trabajo, se está listo para servir mucho más rápido, y si ejecuta dos instancias de Gopls, trabajan juntos sinérgicamente.
El resultado de este cambio es que el uso de memoria de GOPLS es proporcional al número de paquetes abiertos y sus importaciones directas. Es por eso que observamos la escala sublínea en la tabla anterior: a medida que los repositorios se hacen más grandes, la fracción del proyecto observada por cualquier paquete abierto se hace más pequeño.
Invalidación de grano fino
Cuando realiza un cambio en un paquete, solo es necesario recompilar los paquetes que importan ese, directa o indirectamente. Esta thought es la base de todos los sistemas de construcción incrementales desde la década de 1970, y Gopls la ha estado utilizando desde su inicio. En efecto, cada pulsación de tecla en su editor habilitado para LSP comienza una construcción incremental. Sin embargo, en un gran proyecto, se suman dependencias indirectas, lo que hace que estas reconstrucciones incrementales sean demasiado lentas. Resulta que gran parte de este trabajo no es estrictamente necesario, porque la mayoría de los cambios, como agregar una declaración dentro de una función existente, no afectan los resúmenes de importación.
Si realiza un pequeño cambio en un archivo, tenemos que recompilar su paquete, pero si el cambio no afecta el resumen de importación, no tenemos que compilar ningún otro paquete. El efecto del cambio es “podado”. Un cambio que afecte el resumen de importación requiere recompilar los paquetes que importan directamente ese paquete, pero la mayoría de estos cambios no afectarán los resúmenes de importación de aquellos Paquetes, en cuyo caso, el efecto aún se poda y evita recompilar importadores indirectos.
Gracias a esta poda, es raro que un cambio en un paquete de bajo nivel requiera recompilar todo Los paquetes que indirectamente dependen de ese paquete. Las reconstrucciones incrementales podadas hacen que la cantidad de trabajo proporcional al alcance de cada cambio. Esta no es una thought nueva: fue introducida por Vesta y también utilizada en go construct
.
La versión V0.12 presenta una técnica de poda related a los GPL, yendo un paso más allá para implementar una heurística de poda más rápida basada en el análisis sintáctico. Al mantener un gráfico simplificado de referencias de símbolos en la memoria, GOPLS puede determinar rápidamente si un cambio en el paquete c
Posiblemente puede afectar el paquete a
a través de una cadena de referencias.
En el ejemplo anterior, no hay una cadena de referencias de a
a c
por lo que A no está expuesto a cambios en C a pesar de que indirectamente depende de ello.
Nuevas posibilidades
Si bien estamos contentos con las mejoras de rendimiento que hemos logrado, también estamos entusiasmados con varias características de Gopls que son factibles ahora que la memoria ya no está limitada por la memoria.
El primero es el análisis estático robusto. Anteriormente, nuestro controlador de análisis estático tenía que operar en la representación de paquetes en memoria de Gopls, por lo que no podía analizar las dependencias: hacerlo extraería demasiado código adicional. Con ese requisito eliminado, pudimos incluir un nuevo controlador de análisis en GOPLS V0.12 que analiza todas las dependencias, lo que resulta en una mayor precisión.
Por ejemplo, GOPLS ahora informa diagnóstico para Printf
formatear errores incluso en sus envoltorios definidos por el usuario alrededor fmt.Printf
. Notablemente, go vet
ha proporcionado este nivel de precisión durante años, pero Gopls no pudo hacer esto en tiempo actual después de cada edición. Ahora puede.
El segundo es Configuración de espacio de trabajo más simple y Manejo mejorado para etiquetas de construcción. Estas dos características equivalen a GOPL “haciendo lo correcto” cuando abre cualquier archivo GO en su máquina, pero ambas eran inviables sin el trabajo de optimización porque (por ejemplo) cada configuración de compilación multiplica la huella de memoria.
¡Pruébalo!
Además de la escalabilidad y las mejoras de rendimiento, también hemos solucionado numerosos errores reportados y muchos no informados que descubrimos al mejorar la cobertura de la prueba durante la transición.
Para instalar los últimos gopls:
$ go set up golang.org/x/instruments/gopls@newest
Pruébelo y full la encuesta, y si debe encontrarse con un error, informarlo Y lo arreglaremos.
Robert Findley y Alan Donovan
Foto de Emma Henderson en Unsplash
Este artículo está disponible en