Cómo eBay hizo un escáner de código de barras en WebAssembly

Desde su anuncio, la tecnología WebAssembly ha atraído inmediatamente la atención de los desarrolladores front-end. La comunidad web aceptó con entusiasmo la idea de ejecutar código en un navegador escrito en otros idiomas además de JavaScript. Lo principal es que WebAssembly garantiza una velocidad mucho mayor que JavaScript.

Nuestros ingenieros siguieron de cerca el desarrollo del estándar. Tan pronto como se implementó el soporte de WebAssembly 1.0 en todos los principales navegadores, los desarrolladores inmediatamente quisieron probarlo.

Pero hubo un problema. Aunque muchas aplicaciones se benefician de WebAssembly, el alcance de la tecnología en el comercio electrónico sigue siendo primitivo. No pudimos encontrar de inmediato la versión correcta de su uso. Hubo algunas sugerencias, pero JavaScript fue mejor en todas las variaciones. Cuando evaluamos nuevas tecnologías en eBay, la primera pregunta es: "¿Cuáles son los beneficios potenciales para nuestros clientes?" Si no hay claridad aquí, no procederemos al siguiente paso. Es muy fácil dejarse llevar por la nueva tecnología de moda, incluso si no es importante para los clientes y solo complica el flujo de trabajo existente. La experiencia del usuario siempre es más importante que la experiencia del desarrollador. Pero con WebAssembly de manera diferente. Esta tecnología tiene un gran potencial, simplemente no pudimos encontrar el caso de uso correcto. Sin embargo, al final todavía lo encontraron.

Escáner de código de barras


En las aplicaciones nativas de eBay en iOS y Android, hay una función de escaneo de código de barras UPC para ingresar automáticamente en el formulario. Funciona solo en aplicaciones y requiere un procesamiento intensivo de imágenes en el dispositivo para reconocer los dígitos del código de barras en el flujo de imágenes desde la cámara. El código resultante se envía al servicio del servidor, que, a su vez, completa el formulario. Esto significa que la lógica de procesamiento de imágenes en el dispositivo debe ser muy eficiente. Para aplicaciones nativas, compilamos nuestra propia biblioteca C ++ en código nativo para iOS y Android. Reconoce códigos de barras excepcionalmente bien. Nos estamos moviendo gradualmente a las API nativas en iOS y Android, pero nuestra biblioteca C ++ sigue siendo confiable.

El escáner de código de barras es una función intuitiva para los vendedores, simplifica significativamente el llenado del formulario. Desafortunadamente, esta función no funcionó en la versión móvil del sitio, y los vendedores tuvieron que ingresar manualmente el UPC, lo cual es inconveniente.

Escáner de código de barras web


Solíamos buscar una opción para escanear códigos de barras en la web. Hace dos años, incluso lanzaron un prototipo basado en la biblioteca JavaScript de código abierto BarcodeReader . El problema era que solo funcionaba bien en el 20% de los casos. El 80% restante del tiempo el escáner funcionó extremadamente lento o no funcionó en absoluto. En la mayoría de los casos, fue un tiempo de espera. Es bastante esperado: JavaScript se puede comparar en velocidad con el código nativo solo si está en una “vía rápida”, es decir, está altamente optimizado por los compiladores JIT . El truco es que los motores de JavaScript utilizan numerosas heurísticas para determinar si una ruta está "activa" sin garantizar un resultado. Esta discrepancia obviamente condujo a la frustración del usuario, y tuvimos que desactivar esta función. Pero ahora todo es diferente. Con el rápido desarrollo de la plataforma web, surgió la pregunta: "¿Es posible implementar un escáner de código de barras confiable en la web?"

Una opción es esperar a que salga la API de detección de forma con sus funciones de detección de imagen incorporadas, incluidos los códigos de barras . Pero estas interfaces todavía están en una etapa muy temprana de desarrollo y están lejos de ser compatibles con los navegadores cruzados. E incluso en este caso, el trabajo en todas las plataformas no está garantizado . Por lo tanto, debe considerar otras opciones.

Aquí es donde entra en juego WebAssembly. Si se implementa un escáner de código de barras en WebAssembly, entonces se garantiza que funcionará. La sólida estructura de escritura y código de bytes de WebAssembly le permite mantener siempre el "camino activo" de la ejecución. Además, ya tenemos una biblioteca C ++ para aplicaciones nativas. Las bibliotecas C ++ son candidatos ideales para la compilación en WebAssembly. Pensamos que el problema estaba resuelto. Resultó, no realmente.

Arquitectura


La arquitectura de prototipo funcional para el escáner de código de barras en WebAssembly era bastante simple.

  • Compile la biblioteca C ++ con Emscripten . Producirá el middleware y el archivo .wasm.
  • Seleccione un subproceso de trabajo del subproceso principal. El código JavaScript para el trabajador importa el código de enlace JavaScript generado, que a su vez crea el archivo .wasm.
  • La transmisión principal envía una instantánea de la transmisión desde la cámara a la transmisión del trabajador, y llamará a la API WASM correspondiente a través del código de conexión. La respuesta API se pasa al hilo principal. La respuesta puede ser una cadena UPC (que se pasa al backend) o una cadena vacía si no se detecta un código de barras.
  • Para una respuesta en blanco, el paso anterior se repite hasta que se detecta un código de barras. Este ciclo se ejecuta durante el intervalo de tiempo especificado en segundos. Una vez que se alcanza el umbral, mostraremos un mensaje de advertencia "Código de producto no válido. Pruebe con un código de barras diferente o una búsqueda de texto " . O el usuario no enfocó la cámara en un código de barras real o el escáner no es lo suficientemente efectivo. Hacemos un seguimiento de las estadísticas sobre los tiempos de espera como un indicador de la calidad del escáner.


Flujo de trabajo de WebAssembly

Compilación


El primer paso en cualquier proyecto de WebAssembly es definir una canalización de compilación clara. Emscripten se ha convertido en el estándar de facto para compilar WebAssembly, pero es importante tener un entorno consistente que produzca un resultado determinista. Nuestra interfaz se basa en Node.js, por lo que debemos encontrar una solución compatible con el flujo de trabajo npm. Afortunadamente, por esa época, Surma Das publicó un artículo llamado "Emscripten and npm" . El enfoque basado en Docker para compilar WebAssembly tiene sentido ya que elimina una tonelada de sobrecarga. Como se recomienda en el artículo, tomamos la imagen acoplable de Emscripten de trzeci . Para habilitar la compilación en WebAssembly, la biblioteca nativa de C ++ tuvo que modificarse un poco. Básicamente, actuamos al azar, ensayo y error. Al final, logré compilarlo y también configuré un flujo de trabajo limpio de WebAssembly dentro de la tubería de ensamblaje existente.

Funciona rápido, pero ...


El rendimiento del escáner se mide por la cantidad de fotogramas procesados ​​por Wasm API por segundo. Wasm API toma un fotograma de la transmisión de video de la cámara, realiza cálculos y devuelve una respuesta. Esto se realiza de manera continua hasta que se detecta un código de barras. El rendimiento se mide en FPS.

Nuestra implementación de prueba de WebAssembly mostró una increíble velocidad de 50 FPS. Sin embargo, funcionó solo en el 60% de los casos, y en el resto se bloqueó por tiempo de espera. Incluso con un FPS tan alto, no pudieron detectar rápidamente el código de barras para el 40% restante de los escaneos, dando un mensaje de advertencia al final. En comparación, la implementación anterior de JavaScript generalmente se ejecutó a 1 FPS. Sí, WebAssembly es mucho más rápido (50 veces), pero por alguna razón no funciona en casi la mitad de los casos. También debe tenerse en cuenta que en algunas situaciones JavaScript funcionó muy bien e inmediatamente encontró el código de barras. Una de las opciones obvias era aumentar el tiempo de espera, pero esto solo aumentará la frustración de los usuarios, por lo que no resolvemos el problema real. Por lo tanto, abandonamos esta idea.

Al principio, no podíamos entender por qué la biblioteca nativa de C ++, que funcionaba perfectamente en aplicaciones nativas, no mostraba el mismo resultado en la web. Después de largas pruebas y depuración, encontramos que la velocidad de reconocimiento depende del ángulo de enfoque del objeto y la sombra de fondo. Pero, ¿cómo funciona todo en aplicaciones nativas? El hecho es que en las aplicaciones nativas usamos las API incorporadas para el enfoque automático y brindamos al usuario la oportunidad de enfocar manualmente al señalar con un dedo el código de barras. Por lo tanto, las aplicaciones nativas siempre proporcionan a la biblioteca imágenes claras de alta calidad.

Al darnos cuenta de la esencia de lo que está sucediendo, decidimos probar otra biblioteca nativa: un escáner de código de barras ZBar de código abierto bastante popular y estable. Más importante aún, funciona bien con imágenes borrosas y granuladas. ¿Por qué no intentarlo? Como ya teníamos el flujo de trabajo de WebAssembly, la compilación e implementación de ZBar en WebAssembly se realizó sin problemas. El rendimiento resultó ser decente, alrededor de 15 FPS, aunque no tan bueno como el de nuestra propia biblioteca de C ++. Pero la tasa de éxito fue cercana al 80% para el mismo tiempo de espera. Una clara mejora con respecto a nuestra biblioteca de C ++, pero aún no al 100%.

El resultado aún no nos satisfizo, pero notamos algo inesperado. Cuando Zbar colapsó, nuestra propia biblioteca de C ++ hizo el trabajo muy rápidamente. Fue una grata sorpresa. Parece que las bibliotecas procesaron imágenes de diferente calidad de diferentes maneras. Esto nos llevó a la idea.

Multithreading y carreras de velocidad


Probablemente ya lo entendiste. ¿Por qué no crear dos subprocesos de trabajo: uno para Zbar y otro para nuestra biblioteca C ++, y no ejecutarlos en paralelo? Quien ganó (el primero que envía un código de barras válido) envía el resultado a la transmisión principal, y ambos trabajadores se detienen. Implementamos tal escenario y comenzamos a probarnos a nosotros mismos, tratando de simular tantos escenarios como sea posible. Esta configuración mostró el 95% de los escaneos exitosos. Mucho mejor que los resultados anteriores, pero aún no 100%.

Una de las sugerencias extrañas fue agregar la biblioteca JavaScipt original a la competencia. Serán tres corrientes. Sinceramente, no pensamos que esto cambiaría nada. Pero tal prueba no requirió ningún esfuerzo, porque estandarizamos la interfaz de trabajo. Para nuestra sorpresa, con tres transmisiones, la tasa de éxito realmente se acercó al 100%. Esto nuevamente fue completamente inesperado. Como se mencionó anteriormente, JavaScript funcionó muy bien en algunas situaciones. Aparentemente, cerró la brecha. Entonces, la sabiduría popular de la ley es "JavaScript siempre gana" . Sin bromas, la siguiente ilustración proporciona una visión general de la arquitectura final que hemos implementado.


Escáner de código de barras de arquitectura web

La siguiente figura muestra un diagrama funcional de alto nivel:


Diagrama funcional de un escáner de código de barras.

Nota de carga de recursos


Los recursos necesarios para que funcione el escáner se cargan previamente después de mostrar la página principal. De esta manera, la página de destino se carga rápidamente y está lista para la interacción. Los recursos de WebAssembly (archivos wasm y scripts de middleware) y la biblioteca del escáner JavaScript se precargan y almacenan en caché utilizando XMLHttpRequest después de cargar la página principal. Es importante aquí que no se ejecuten inmediatamente para dejar el hilo principal libre para la interacción del usuario con la página. La ejecución se produce solo cuando el usuario hace clic en el icono del código de barras. Si el usuario hizo clic en el icono antes de cargar los recursos, se cargarán a pedido y se ejecutarán de inmediato. El controlador de eventos del escáner de código de barras y el controlador del trabajador se cargan con la página, pero son muy pequeños.

Resultados


Después de pruebas rigurosas y uso interno por parte de los empleados, lanzamos pruebas A / B en los usuarios. El icono del escáner (captura de pantalla a continuación) se mostró al grupo de prueba, pero no al grupo de control.


Producto final

Para medir el éxito, presentamos la métrica de tasa de finalización del borrador. Este es el tiempo entre comenzar a editar un borrador y enviar un formulario. La métrica debe mostrar cómo un escáner de código de barras ayuda a las personas a completar formularios. La prueba duró varias semanas y los resultados fueron muy agradables. Son totalmente consistentes con nuestra hipótesis original. El tiempo de finalización del borrador disminuyó en un 30% para una secuencia con un escáner de código de barras.


Resultados de la prueba A / B

También agregamos perfiles para evaluar la efectividad de todos los tipos de escáneres. Como se esperaba, la mayor contribución fue realizada por Zbar (53% de los escaneos exitosos), luego nuestra biblioteca C ++ (34%) y, finalmente, la biblioteca JavaScript con 13%.



Conclusión


La experiencia de implementar WebAssembly se ha vuelto muy informativa para nosotros. Los ingenieros están muy contentos con la aparición de nuevas tecnologías e inmediatamente quieren probarlas. Si la tecnología también es útil para los clientes, entonces esta es una doble alegría. Repitamos el pensamiento expresado al comienzo del artículo. La tecnología se está desarrollando a un ritmo muy rápido. Todos los días aparece algo nuevo. Pero solo unas pocas tecnologías son importantes para los clientes, y WebAssembly es una de ellas. Nuestra mayor conclusión de este ejercicio es decir "no" en 99 situaciones y "sí" en el único caso cuando es realmente importante para los clientes.

En el futuro, planeamos expandir el uso de un escáner de código de barras e introducirlo en el lado de los compradores, para que puedan escanear códigos de productos fuera de línea para buscar y comprar en eBay. También consideraremos expandir la función usando la API de detección de forma y otras funciones en el navegador. Pero nos complace haber encontrado el caso de uso adecuado para WebAssembly en eBay y haber aplicado con éxito la tecnología en el comercio electrónico.

Un agradecimiento especial a Surma Das y Lin Clark por numerosos artículos sobre WebAssembly. Realmente nos ayudaron a romper el punto muerto varias veces.

Source: https://habr.com/ru/post/453712/


All Articles