2 puntos por GN⁺ 2023-07-13 | 1 comentarios | Compartir por WhatsApp
  • El prototipo de préstamo regional de Vale logró compilar por primera vez, lo que permite validar en programas reales un enfoque de seguridad de memoria que combina referencias generacionales y regiones
  • Los desarrolladores pueden escribir código de una forma cercana a C/C++ y aplicar pure y préstamo regional solo donde sea necesario para reducir el overhead de las comprobaciones generacionales
  • El primer programa Vale con cero comprobaciones fue un ejemplo de Cellular Automata para generar niveles de roguelike, y el ensamblador resultante quedó casi al mismo nivel que el modo unsafe_with_bounds
  • En el benchmark, safe_fastest no mostró ninguna desaceleración observable frente a unsafe_with_bounds, mientras que unsafe_no_bounds fue 1.18 ± 0.01 veces más rápido que ambos modos
  • Todavía no es una comparación directa con C/Rust; debido al ruido de optimización de LLVM, la madurez del prototipo y la falta de soporte para inline data, aún quedan pendientes un pre-optimizer específico para Vale y la depuración de las funciones regionales

Combinación de referencias generacionales y préstamo regional

  • El enfoque de seguridad de memoria de Vale apunta a no usar reference counting, garbage collection con tracing ni borrow checking
  • La estructura básica consiste en que los desarrolladores escriban programas de una forma cercana a C o C++, mientras las generational references de Vale mantienen la seguridad de memoria
  • Luego, al aplicar pure y region borrowing, se puede eliminar la mayor parte del overhead de las comprobaciones generacionales
  • Sumando el linear style, se considera posible reducir las comprobaciones generacionales a cero en código Vale
  • El préstamo regional es completamente opt-in, por lo que se puede escribir primero de forma cómoda y agregarlo después solo en las partes que necesiten optimización
  • La idea es una estructura en la que, dentro del mismo programa, algunas partes puedan ser flexibles como Java, otras rápidas como Rust, o elegir puntos intermedios

Trabajo necesario para crear el prototipo

  • Durante los últimos años se construyó la base del compilador y se trabajó para soportar conjuntamente un sistema de préstamo basado en regiones y generational references
  • El sistema de préstamo en sí es complejo y además requería full generics, más potentes que los templates existentes
  • Para que las regiones y las referencias generacionales funcionaran juntas de forma natural, también hizo falta una nueva etapa del compilador
    • Internamente, las regiones se reducen a un entero de “pure height”
    • Los parámetros genéricos de región se representan con números negativos, la región predeterminada con 0 y cada pure block con números positivos crecientes
  • Hace unos meses se completó el prototipo de regiones y, aunque todavía tiene partes ásperas, logró compilar algo con éxito por primera vez
  • Como resultado se creó el primer programa Vale con cero comprobaciones
    • Con la flag del compilador --print_mem_overhead true se puede contar la cantidad de comprobaciones generacionales del programa

Primer programa con cero comprobaciones y comparación de ensamblador

  • El primer programa fue un ejemplo de Cellular Automata que genera niveles para un juego roguelike
  • Incluso un pequeño error en el código del compilador podía agregar instrucciones al ensamblador resultante y crear overhead artificial en el programa final
  • Para rastrear el problema, el ensamblador resultante se comparó continuamente con los modos unsafe de Vale
    • unsafe_no_bounds: desactiva toda la protección de seguridad de memoria, de forma similar a C, y usa raw pointers en lugar de generational references
    • unsafe_with_bounds: agrega bounds checking en los accesos a arrays, como Rust
  • Tras varios meses rastreando diferencias, el ensamblador resultante quedó casi idéntico al modo unsafe_with_bounds
  • La única diferencia esperada era la inserción de un pseudo-random generation number al inicio de cada allocation, que en la práctica no se leía en las comprobaciones generacionales
    • Internamente, para mantenerlo rápido, se usa un registro que aumenta de forma monótona
    • Cuando se agreguen isolates o unique references, esta diferencia podría eliminarse

Resultados del benchmark y condiciones de medición

  • El resumen del benchmark es el siguiente
Summary
  './build_unsafe_no_bounds/main' ran
    1.18 ± 0.01 times faster than './build_unsafe_with_bounds/main'
    1.18 ± 0.01 times faster than './build_safe_fastest/main'
  • safe_fastest, el modo normal de Vale, no muestra desaceleración frente al modo que solo tiene bounds checking
  • En esta medición, este enfoque no tiene overhead observable
  • Para probarlo directamente, se puede compilar la rama regions, revisar los scripts de benchmarking y hacer preguntas en el servidor de Discord
  • Las condiciones de medición tienen limitaciones claras
    • No es un benchmark que compare directamente con lenguajes como C o Rust
    • Esos compiladores tienen años de optimizaciones independientes que podrían enturbiar las variables del experimento
    • Para aislar las diferencias del enfoque de seguridad de memoria, se comparó con unsafe_no_bounds y unsafe_with_bounds
    • El entorno de medición fue una Razer Blade 15" 2018 con SSD de 512 GB, ejecutando Ubuntu 22.04
    • La herramienta de medición fue hyperfine, ejecutada dentro de cset shield

Ruido de optimización observado en programas grandes

  • En programas más grandes se observó bastante optimizer noise
    • Es distinto del ruido de benchmark, ya que la configuración de medición mostró tiempos de ejecución muy consistentes, como ± 0.01
    • Un cambio pequeño en una zona podía mover la medición hacia un lado
  • Hubo un caso en el que, al cambiar el tamaño del generation number, apareció de forma consistente un overhead negativo de 1.13 ± 0.01
    • Era un resultado extraño porque el programa no tenía muchos generation numbers
    • Es posible que cambios en la register allocation hayan dominado la diferencia de rendimiento debida a diferencias semánticas
  • En un programa más grande, un pequeño juego roguelike, el optimizador no logró fusionar dos branches idénticas dentro de una sentencia if, y también pasó por alto otras optimizaciones evidentes
  • No está claro qué efecto tiene la presencia de un entero que no se lee, y también podría tratarse de un bug de LLVM
  • Este resultado sugiere que podría hacer falta un pre-optimizer específico para Vale, similar al MIR de Rust
    • LLVM fue diseñado teniendo más en mente a C
    • Se considera posible que, si LLVM interpreta las generational references como un patrón de acceso intencional a memoria liberada, las trate como undefined behavior

Aplicabilidad y próximos pasos

  • Cuando se combinan, las referencias generacionales y las regiones pueden crear un enfoque de seguridad de memoria muy rápido
  • Los dominios de software en los que este enfoque podría encajar bien tienen las siguientes condiciones
    • Quieren una latencia más predecible que la de tracing garbage collection
    • Quieren mejor rendimiento y cache friendliness que con reference counting
    • Quieren hacer prototipos e iterar con más facilidad que con borrow checking
  • Antes de que Vale se compare de frente con C o C++, queda trabajo pendiente
    • Debido a problemas de LLVM optimizer para inferir generation e immutability, se necesita un pre-optimizer específico para Vale
    • Vale debe soportar inline data en lugar de la solución temporal actual de colocar todos los structs en el heap
    • Como el benchmark anterior no usaba structs, la falta de soporte para inline data no afectó ese resultado
    • Las funciones regionales aún están en etapa de prototipo, por lo que hay que pulir las partes ásperas, reducir la deuda técnica y luego fusionarlas en la main branch
  • Después de la fusión, el plan es hacer que la biblioteca estándar use regiones para que el main program code de los usuarios obtenga beneficios aunque no use regiones directamente
  • La medición actual muestra que los programas con cero comprobaciones son posibles y que pueden alcanzar la velocidad esperada

1 comentarios

 
GN⁺ 2023-07-13
Opiniones de Hacker News
  • Descargué Vale para probarlo, y me dio mala impresión que, al ejecutar por primera vez el compilador valec sin argumentos, imprimiera de inmediato "(panic)"
    panic es una expresión muy fuerte y creo que debería evitarse en situaciones normales de manejo de errores. Que un programa haya entrado en pánico se siente como una situación fuera de control, y deja mal sabor
    Luego intenté ver la ayuda de argumentos de la línea de comandos, pero actualmente prácticamente no existe; después guardé el ejemplo Hello World del sitio web en hello.vl y ejecuté valec hello.vl, y apareció Unknown subcommand
    Así que ejecuté valec build hello.vl, y salió Unrecognized input: hello.vl, seguido otra vez de (panic); valec help tampoco ayudó, así que al final me rendí. No sé cómo se supone que se use esto

    • Perdón. Parece que los archivos de ayuda ya no se muestran correctamente. Si haces cat directamente al valec-help-build.txt incluido en la descarga, ahí debería explicarse lo que buscas
      El compilador está en un estado bastante áspero ahora mismo. De agosto a mayo estuve 100% enfocado en prototipar regiones (region), y lo que estás viendo ahora es la deuda técnica acumulada en ese proceso. Eso incluye no tener pruebas de integración para el sistema de ayuda
      Durante el último mes o dos he estado pagando esa deuda, pero todavía no volvió al nivel que tenía en el lanzamiento 0.2. Si necesitas más ayuda, avísame o entra al servidor de Discord; hay mucha gente que puede ayudarte
    • Me parece que Vale todavía está básicamente en etapa de investigación y desarrollo. Yo esperaría que solo funcione un commit específico de una rama específica, no que cualquiera pueda bajar el compilador y construir algo con él
      Dicho eso, el README no lo deja del todo claro y dice “Try Vale”, así que es ambiguo. Aun así, por ahora parece más bien investigación y desarrollo / prueba de concepto
    • Esto se parece más a un problema de interfaz de usuario que a un bug. Si es algo experimental, creo que también se le puede dar cierto margen incluso con bugs reales
      Incluso mirándolo desde la interfaz de usuario o los bugs, la experiencia de depurar C++ de 40 años con gdb de 35 años compite de sobra con cualquier lenguaje experimental. Por ejemplo, imprimir funcname()::staticvarname es una interfaz rara y falla como la mitad de las veces. Ni hablar de los sistemas de build de C++
      Con tecnología experimental, se puede criticar el concepto, pero creo que una interfaz de usuario áspera es algo que se puede tolerar hasta cierto punto
    • En el README de GitHub está explicado cómo usar el compilador
      https://github.com/ValeLang/Vale#building-a-vale-program
    • Si es software que todavía está en fase alfa, es más o menos lo esperable
  • Que tenga latencia más predecible que la recolección de basura por trazado, mejor rendimiento y afinidad con la caché que el conteo de referencias, y que facilite más el prototipado y la iteración que un verificador de préstamos me genera más que curiosidad: me interesa
    También empecé a suscribirme al feed RSS: https://verdagon.dev/rss.xml

    • Por fin apareció una idea nueva en lenguajes compilados AOT que no termina en “dejemos que simplemente haya bugs de memoria de vez en cuando”
  • Vale necesita más patrocinadores
    https://github.com/sponsors/ValeLang
    Mientras este artículo esté en la portada, me gustaría que ayudáramos al proyecto a alcanzar su meta de 3.000 dólares al mes
    Quiero ayudar a que Evan pueda dedicarse a esto a tiempo completo. Yo también soy patrocinador. Un lenguaje rápido y seguro, pero en el que además sea agradable prototipar, merece apoyo

    • Me pregunto cómo difiere el reparto de ingresos entre el patrocinio de GitHub y el de Patreon
  • “Un preoptimizador específico para Vale, algo parecido a Cranelift en Rust” probablemente se refiere a MIR, es decir, una representación intermedia de nivel medio. Hay un buen post de blog relacionado: https://blog.rust-lang.org/2016/04/19/MIR.html
    Cranelift es un backend de compilador centrado principalmente en JIT, pero en teoría también podría reemplazar a LLVM. También hay trabajo en curso en backends alternativos, aunque con limitaciones: https://github.com/bjorn3/rustc_codegen_cranelift

    • Creo que sí. Veo a Cranelift como un optimizador de Rust para WebAssembly
  • Un enfoque en el que no tengas que preocuparte por la gestión de memoria en la mayor parte del código, pero que te dé la opción de optimizar solo las rutas calientes con abstracciones de costo cero, suena como lo mejor de ambos mundos
    Sobre todo si, por conveniencia, lo único que se intercambia es rendimiento y no seguridad

    • Solo uso C++, pero no me preocupo en absoluto por la gestión de memoria
      La propiedad compartida es un mal concepto, así que tampoco uso punteros inteligentes
      En general, creo que los problemas de gestión de memoria son menores
  • Sigo preguntándome qué significa que sea seguro en el contexto de las referencias generacionales (generational references)
    Si lo entendí bien, ¿quiere decir que evita use-after-free y double-free? Si es así, el programa igual puede fallar al acceder a memoria cuando la generación esperada y la generación real no coinciden
    En ese sentido, parece menos seguro que el conteo de referencias, la recolección de basura con trazado o el verificador de préstamos

    • El double-free se evita con la propiedad única de Vale, es decir, propiedad única en el sentido de C++, y las referencias generacionales permiten detectar de forma segura el use-after-free
      Si se intenta acceder a memoria liberada mediante una referencia, debería producirse de forma predecible y segura un error de segmentación o una falla de aserción. Con futuras mejoras que vuelvan a mapear el espacio de direcciones virtual, espero que incluso se puedan eliminar los errores de segmentación
    • Es seguro en el mismo sentido que obtener un error de segmentación en lugar de permitir que un puntero colgante lea o escriba memoria arbitraria
      Aunque, si se trata de un índice generacional, el runtime también debería poder comprobar si el acceso es válido antes de intentar el acceso real. No sé si eso es posible en Vale
    • Es menos seguro que GC, el verificador de préstamos y el conteo de referencias. Aun así, es más seguro que malloc/free, y tiene otras ventajas
    • Yo también tengo esa duda. No veo cómo esto evita use-after-free y double-free
      La función check necesita el número de generación de la asignación, así que accede a la asignación. Es decir, para verificar si la referencia puede acceder a esa asignación, primero tiene que acceder a esa asignación
      Claro que, si la asignación ya fue liberada, acceder a esa asignación y a su número de generación ya es comportamiento indefinido, así que no funciona
      Parece tan obvio que no sé si me estoy perdiendo algo grande, o si “seguridad de memoria” aquí significa algo totalmente distinto
    • Si preguntas si es menos seguro que la definición limitada que él mismo da de “seguro”, probablemente sí
  • Vale no es el mismo lenguaje que V. V recibió una reseña muy crítica en https://mawfig.github.io/2022/06/18/v-lang-in-2022.html, y por la similitud del nombre lo estaba recordando erróneamente como Vale
    Lo dejo por si alguien más cometió el mismo error

    • Esa “reseña muy crítica” no es más que una lista de pequeños bugs que se corrigieron hace un año
      El contenido del artículo ya no es relevante, pero sigue ahí, y además es la única entrada de ese blog
    • Esa “reseña crítica” parece más bien spam viejo que opositores o trolls siguen usando. Es una “reseña” de una versión alfa del lenguaje, en la práctica un texto de ataque, y no parece tener valor más allá de eso
      Ahora estamos en 2023 y V también está en beta (0.4). Además, la persona que creó ese texto usó una cuenta desechable de GitHub para publicar la reseña/ataque, generar polémica y luego desaparecer
      La única entrada de ese blog también es un ataque a V y no hay otras reseñas. Las partes que tenían contenido sustancial ya fueron corregidas[1]
      Si buscas mawfig.github, también se ve que se difundió repetidamente en HN y que normalmente se usó para difamar
      [1]: https://github.com/vlang/v/issues/14803
      [1]: https://github.com/vlang/v/issues/14787
      [1]: https://github.com/vlang/v/issues/14786
  • Felicidades a Evan por alcanzar este hito. No tengo experiencia en diseño de lenguajes de programación ni en compiladores, pero disfruto leer los artículos de Vale

    • Me pasa algo parecido, aunque desearía que tuviera otro nombre
      Ahora están el Vale de Evan y Val, del Adobe Software Technology Lab, así que creo que va a ser bastante difícil buscar materiales relacionados
      https://www.val-lang.dev
    • Yo tampoco tengo los conocimientos de base para entender los artículos en la mayoría de los casos, pero igual me parecen interesantes
    • Yo también creo que los artículos son excelentes y me entusiasma el futuro de Vale
  • “Vale es rápido: Vale se compila AOT con LLVM, usa tipos estáticos, emplea una nueva técnica de referencias generacionales para ofrecer seguridad de memoria con velocidad y flexibilidad, y pronto incorporará verificación de préstamos por regiones para volverse aún más rápido”
    https://vale.dev/

  • Se siente como escuchar de reojo una discusión de dos personas que lleva 5 años
    ¿Alguien puede explicar qué está pasando aquí? El artículo es demasiado difícil de entender