$30 off During Our Annual Pro Sale. View Details »

Cuándo usar extensiones nativas en Rust: Rendimiento accesible y seguro

Cuándo usar extensiones nativas en Rust: Rendimiento accesible y seguro

Eric Arellano

April 28, 2021
Tweet

Other Decks in Technology

Transcript

  1. Cuándo usar
    extensiones
    nativas en Rust:
    Rendimiento
    accesible y
    seguro
    Eric Arellano
    @EArellanoAZ
    PyCon US, Mayo 2021

    View Slide

  2. Plan de presentación
    1. ¿Cuándo usar las extensiones nativas?
    2. ¿Por qué usar Rust?
    3. ¿Qué sigue de esto?

    View Slide

  3. Quién soy
    ● Ingeniero para Toolchain
    Labs, Twitter, y
    Foursquare
    ● Colaborador del proyecto
    Pants por 3 años
    ● Consejero para jóvenes
    LGBTQ en crisis
    ● Amante de cerdos

    View Slide

  4. Pants
    Una herramienta
    que coordina los
    builds.
    ● ejecutar
    pruebas
    ● empaqueter
    distribuciones
    ● ejecutar linters
    ● formatear código
    ● generar Protobufs

    View Slide

  5. Pants - como usamos Rust
    + Python
    Python 3 para modelar toda la lógica de un build.
    Rust para la gestión de tareas, "the engine":
    ● Lanzar procesos con concurrencia
    ● Escribir/leer al caché
    ● Observar el sistema de archivos para cambios

    View Slide

  6. 1. ¿Cuándo usar las
    extensiones nativas?

    View Slide

  7. Python suele ser lento
    "Why is Python
    slow?"
    Anthony Shaw
    Pycon 2020

    View Slide

  8. Pero no siempre es un problema
    "Why is Python
    slow?"
    Anthony Shaw
    Pycon 2020

    View Slide

  9. ¿Que importancia tiene el
    rendimiento para su
    proyecto?

    View Slide

  10. ¿Por qué CPython puede
    ser lento?
    Según "Why is Python Slow" (Anthony Shaw):
    ● Compilación
    ○ Hay una etapa de interpretación
    ○ Bytecode no está siempre optimizado
    ● Recolector de basura
    ● Limites a la concurrencia
    ○ GIL (global interpreter lock)

    View Slide

  11. Perfilar
    Característica de
    rendimiento
    Herramientas
    antes y después;
    CPU vs. I/O
    hyperfine, GNU time
    "hot spots" PySpy, "flame graphs" como
    Snakeviz
    gasto de memoria Guppy3

    View Slide

  12. Flame graphs

    View Slide

  13. Perfiles del GIL
    Es complicado porque hay que identificar
    la duración de espera.
    Tutorial en YouTube: "Identifying Contention on the
    Python GIL in Rust from macOS" por Pants Build,
    https://youtu.be/zALr3zFIQJo

    View Slide

  14. Algunas opciones cuando hay
    un problema de rendimiento
    1. Optimizar.
    2. Probar la concurrencia de Python.
    3. Probar PyPy o Numba.
    4. Reescribir todo en otro lenguaje.
    5. Usar las extensiones nativas.

    View Slide

  15. Opción 1: optimizar
    Por ejemplo:
    ● Cambiar las estructuras de datos y algoritmos
    ● Usar __slots__ para menos gasto de memoria
    Es bueno, pero hay limites fundamentales,
    como el GIL.

    View Slide

  16. Opción 2: la concurrencia
    de Python
    ● multiprocessing
    ○ evita el GIL, pero hay gastos
    ● threading
    ○ todavía tiene el GIL
    ○ es útil cuando I/O-bound, no tanto cuando CPU-bound
    ● asyncio
    http://masnun.rocks/2016/10/06/async-python-the-different-forms-of-concurrency/

    View Slide

  17. Opción 3
    ● Son intérpretes alternativos
    ● Todavía escribe Python
    ● PyPy es ~4.2x más rapido que CPython

    View Slide

  18. PyPy y Numba
    "If you want your code to run faster,
    you should probably just use PyPy."
    Guido van Rossum, creador de Python

    View Slide

  19. Opción 4: reescribir todo en
    otro lenguaje
    "The single worst strategic mistake that any
    software company can make: rewrite the
    code from scratch."
    Joel Spolsky, 2000
    "Things You Should Never Do, Part 1"

    View Slide

  20. Reescribir todo en otro lenguaje
    Es mejor hacerlo
    incrementalmente.

    View Slide

  21. Opción 5: las extensiones
    nativas
    ● Una mezcla de Python y otro
    lenguaje
    ● FFI (Foreign Function Interface)
    ● Importado como un módulo típico
    a través de un archivo .so

    View Slide

  22. Extensión - demo
    def suma_dos(x: int) -> int:
    return x + 2
    if __name__ == "__main__":
    assert suma_dos(2) == 4
    proyecto
    └── demo.py

    View Slide

  23. Extensión - demo
    from mi_extension_nativa import suma_dos
    if __name__ == "__main__":
    assert suma_dos(2) == 4
    proyecto
    ├── demo.py
    └── mi_extension_nativa.so

    View Slide

  24. Extensiones nativas
    Son muy populares
    ● Cryptography
    ● NumPy
    ● Pandas
    ● TensorFlow
    ● PyYAML

    View Slide

  25. Cuándo las extensiones
    nativas pueden valer la pena
    ● Optimizaciones y PyPy/Numpy no están
    suficientes
    ● Hay "hot spots" mucho más rápidos con un
    lenguaje nativo
    ○ Escribir experimentos sencillos para probar

    View Slide

  26. Por que Pants escogió una
    extensión nativa
    ● Después de las optimizaciones, todavía gastaba
    80% del tiempo en la gestión de tareas
    ● Queriamos usar asyncio, pero con más control y
    sin el GIL
    ● Tenemos estructuras de datos enormes ->
    control de la memoria

    View Slide

  27. Las extensiones nativas
    llevan complejidad
    ● dos lenguajes para entender y mantener
    ● empaquetamiento
    Menos riesgo con un límite claro de su
    interfaz.

    View Slide

  28. 2. ¿Por qué usar Rust?

    View Slide

  29. Algunas opciones para
    extensiones nativas
    ● C
    ○ ctypes/cffi
    ○ CPython API
    ○ Cython*
    ● C++
    ○ pybind11
    ● Rust
    ○ Rust-CPython
    ○ PyO3
    ● Golang
    *Cython es un dialecto de
    Python

    View Slide

  30. View Slide

  31. Rust - demo
    fn suma_dos(x: i64) -> i64 {
    x + 2
    }
    fn main() {
    println!("{}", suma_dos(2));
    }

    View Slide

  32. Extensión - demo
    use pyo3::prelude::*;
    use pyo3::wrap_pyfunction;
    #[pyfunction]
    fn suma_two(x: i64) -> PyResult {
    Ok(x + 2)
    }
    #[pymodule]
    fn mi_extension_nativa(py: Python, m: &PyModule) -> PyResult<()>
    {
    m.add_function(wrap_pyfunction!(suma_dos, m)?)?;
    Ok(())
    }

    View Slide

  33. ¡Rust puede usar Python también!
    use pyo3::prelude::*;
    fn main() {
    Python::with_gil(|py| {
    let sys_module = py.import("sys").unwrap();
    let version: String = sys_module
    .get("version")
    .unwrap()
    .extract()
    .unwrap();
    println!("Usando Python {}", version);
    }
    }

    View Slide

  34. C y C++ no tienen la
    seguridad de memoria.
    La habilidad no elimina el
    riesgo.

    View Slide

  35. ● ~70% de vulnerabilidades de Microsoft 1
    ● ~70% de vulnerabilidades severos de Android 2
    ● 74% de vulnerabilidades de Firefox Style
    Component 3
    1. https://msrc-blog.microsoft.com/2019/07/18/we-need-a-safer-systems-programming-language/
    2. https://security.googleblog.com/2021/04/rust-in-android-platform.html
    3. https://hacks.mozilla.org/2019/02/rewriting-a-browser-component-in-rust/
    Vulnerabilidades de
    memoria son comunes

    View Slide

  36. error[E0382]: borrow of moved value: `s1`
    --> src/main.rs:5:28
    |
    2 | let s1 = String::from("hello");
    | -- move occurs because `s1` has type `String`, which does not implement
    the `Copy` trait
    3 | let s2 = s1;
    | -- value moved here
    4 |
    5 | println!("{}, world!", s1);
    | ^^ value borrowed here after move
    Rust - seguridad de memoria sin
    un recolector de basura
    El compilador checa la seguridad de memoria a través
    de su "borrow checker".

    View Slide

  37. Rust - concurrencia sin miedo
    El compilador checa la seguridad de
    concurrencia—incluyendo carreras de datos—a través
    de su "borrow checker".
    error[E0373]: closure may outlive the current function, but it
    borrows `v`, which is owned by the current function
    --> src/main.rs:6:32
    |
    6 | let handle = thread::spawn(|| {
    | ^^ may outlive borrowed value `v`
    7 | println!("Aquí hay un vector: {:?}", v);
    | - `v` is borrowed here

    View Slide

  38. Rust - rendimiento ~igual
    a C
    https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/gcc-rust.html

    View Slide

  39. Rust - otras ventajas
    ● Sistema de tipo
    moderno, ~MyPy
    ● Herramientas
    modernas
    ● Buena
    documentación y
    mensajes de errores
    ● Una comunidad
    inclusiva

    View Slide

  40. Rust - algunas desventajas
    ● Más complejo que Python
    ● Compilación lento
    ● Todavía joven - no siempre hay
    bibliotecas maduras

    View Slide

  41. 3. ¿Qué sigue de esto?

    View Slide

  42. Recursos sobre Rust
    "The Rust Book"
    ● Gratis
    ● Es official
    ● Hay una traducción,
    github.com/ManRR/rust-book-es
    doc.rust-lang.org/stable/book

    View Slide

  43. Recursos sobre Rust
    Clase de
    Microsoft
    Learn (gratis)
    docs.microsoft.com/es-es/learn/paths/rust-first-steps/

    View Slide

  44. PyO3 - biblioteca para
    extensiones nativas en Rust
    pyo3.rs
    Ejemplo:
    orjson

    View Slide

  45. Resumen
    1. El rendimiento no siempre importa mucho.
    2. Perfilen. Intenten optimizar, prueben
    PyPy/Numba, y tal vez antes la concurrencia de
    Python.
    3. Las extensiones nativas en Rust ofrecen
    rendimiento seguro y accesible. Rust + Python
    están listos para la producción.

    View Slide

  46. Sigan la conversación
    con el equipo Pants
    Twitter:
    @pantsbuild
    @EArellanoAZ
    Slack:
    pantsbuild.org
    para el enlace
    Pycon 2021
    Startup Row:

    View Slide