Como o eBay criou um scanner de código de barras no WebAssembly

Desde seu anúncio, a tecnologia WebAssembly atraiu imediatamente a atenção dos desenvolvedores de front-end. A comunidade da Web aceitou com entusiasmo a ideia de executar código em um navegador escrito em idiomas diferentes do JavaScript. O principal é que o WebAssembly garante uma velocidade muito maior que o JavaScript.

Nossos engenheiros acompanharam de perto o desenvolvimento do padrão. Assim que o suporte ao WebAssembly 1.0 foi implementado em todos os principais navegadores, os desenvolvedores imediatamente quiseram testá-lo.

Mas houve um problema. Embora muitos aplicativos se beneficiem do WebAssembly, o escopo da tecnologia no comércio eletrônico ainda é primitivo. Não foi possível encontrar imediatamente a versão correta de seu uso. Houve algumas sugestões, mas o JavaScript foi melhor em todas as variações. Quando avaliamos novas tecnologias no eBay, a primeira pergunta é: "Quais são os possíveis benefícios para nossos clientes?" Se não houver clareza aqui, não avançaremos para a próxima etapa. É muito fácil se empolgar com a nova tecnologia da moda, mesmo que isso não importe para os clientes e apenas complique o fluxo de trabalho existente. A experiência do usuário é sempre mais importante que a experiência do desenvolvedor. Mas com o WebAssembly de maneira diferente. Essa tecnologia tem um enorme potencial, simplesmente não conseguimos encontrar o caso de uso correto. No entanto, no final, eles ainda o encontraram.

Scanner de código de barras


Nos aplicativos nativos do eBay para iOS e Android, existe um recurso de leitura de código de barras UPC para entrar automaticamente no formulário. Funciona apenas em aplicativos e requer processamento intensivo de imagens no dispositivo para reconhecer os dígitos do código de barras no fluxo de imagens da câmera. O código resultante é então enviado ao serviço do servidor, que, por sua vez, preenche o formulário. Isso significa que a lógica de processamento de imagem no dispositivo deve ser muito eficiente. Para aplicativos nativos, compilamos nossa própria biblioteca C ++ em código nativo para iOS e Android. Ele reconhece códigos de barras excepcionalmente bem. Estamos gradualmente migrando para APIs nativas no iOS e Android, mas nossa biblioteca C ++ ainda é confiável.

O scanner de código de barras é uma função intuitiva para os vendedores, pois simplifica significativamente o preenchimento do formulário. Infelizmente, essa função não funcionou na versão móvel do site e os vendedores precisaram entrar manualmente no UPC, o que é inconveniente.

Web Barcode Scanner


Costumávamos procurar uma opção para escanear códigos de barras na web. Dois anos atrás, eles até lançaram um protótipo baseado na biblioteca JavaScript de código aberto do BarcodeReader . O problema era que funcionava bem apenas em 20% dos casos. Nos 80% restantes do tempo, o scanner trabalhou extremamente devagar ou não funcionou. Na maioria dos casos, foi um tempo limite. É bastante esperado: o JavaScript pode ser comparado em velocidade com o código nativo apenas se for "quente", ou seja, for altamente otimizado pelos compiladores JIT . O truque é que os mecanismos JavaScript usam inúmeras heurísticas para determinar se um caminho está "quente" sem garantir um resultado. Essa discrepância obviamente levou à frustração do usuário e tivemos que desativar esse recurso. Mas agora tudo está diferente. Com o rápido desenvolvimento da plataforma web, surgiu a pergunta: "É possível implementar um scanner de código de barras confiável na web?"

Uma opção é aguardar a API de detecção de forma sair com seus recursos internos de detecção de imagem, incluindo códigos de barras . Mas essas interfaces ainda estão em um estágio muito inicial de desenvolvimento e estão longe da compatibilidade entre navegadores. E mesmo nesse caso, o trabalho em todas as plataformas não é garantido . Portanto, você deve considerar outras opções.

É aqui que o WebAssembly entra em ação. Se um scanner de código de barras for implementado no WebAssembly, é garantido que ele funcione. A forte estrutura de digitação e bytecode do WebAssembly permite que você mantenha sempre o "caminho quente" da execução. Além disso, já temos uma biblioteca C ++ para aplicativos nativos. As bibliotecas C ++ são candidatas ideais para compilação no WebAssembly. Achamos que o problema estava resolvido. Acabou que não.

Arquitetura


A arquitetura do protótipo de trabalho para o scanner de código de barras no WebAssembly era bastante simples.

  • Compile a biblioteca C ++ com o Emscripten . Ele produzirá o middleware e o arquivo .wasm.
  • Selecione um segmento de trabalho no segmento principal. O código JavaScript do trabalhador importa o código de vinculação JavaScript gerado, que por sua vez cria o arquivo .wasm.
  • O fluxo principal envia um instantâneo do fluxo da câmera para o fluxo do trabalhador e chama a API WASM correspondente por meio do código de conexão. A resposta da API é passada para o encadeamento principal. A resposta pode ser uma string UPC (que é passada para o back-end) ou uma string vazia se nenhum código de barras for detectado.
  • Para uma resposta em branco, a etapa acima é repetida até que um código de barras seja detectado. Este ciclo é executado pelo intervalo de tempo especificado em segundos. Quando o limite for atingido, exibiremos uma mensagem de aviso “Código de produto inválido. Tente um código de barras ou pesquisa de texto diferente . O usuário não focou a câmera em um código de barras real ou o scanner não é eficaz o suficiente. Nós rastreamos estatísticas sobre tempos limite como um indicador da qualidade do scanner.


Fluxo de trabalho do WebAssembly

Compilação


A primeira etapa de qualquer projeto do WebAssembly é definir um pipeline de compilação claro. O Emscripten se tornou o padrão de fato para compilar o WebAssembly, mas é importante ter um ambiente consistente que produza um resultado determinístico. Nosso front-end é baseado no Node.js, portanto, precisamos encontrar uma solução compatível com o fluxo de trabalho npm. Felizmente, nessa época, Surma Das publicou um artigo chamado "Emscripten and npm" . A abordagem baseada no Docker para compilar o WebAssembly faz sentido, pois elimina uma tonelada de sobrecarga. Conforme recomendado no artigo, tiramos a imagem do Docker do Emscripten do trzeci . Para habilitar a compilação no WebAssembly, a biblioteca C ++ nativa precisou ser alterada um pouco. Basicamente, agimos aleatoriamente, por tentativa e erro. No final, consegui compilá-lo e também configurei um fluxo de trabalho puro do WebAssembly dentro do pipeline de montagem existente.

Funciona rápido, mas ...


O desempenho do scanner é medido pelo número de quadros processados ​​pela API Wasm por segundo. A API Wasm pega um quadro do fluxo de vídeo da câmera, realiza cálculos e retorna uma resposta. Isso é feito continuamente até que um código de barras seja detectado. O desempenho é medido em FPS.

Nossa implementação de teste do WebAssembly mostrou uma velocidade incrível de 50 FPS. No entanto, funcionou apenas em 60% dos casos e, no restante, caiu por tempo limite. Mesmo com um FPS tão alto, eles não conseguiam detectar rapidamente o código de barras para os 40% restantes das digitalizações, emitindo uma mensagem de aviso no final. Em comparação, a implementação anterior do JavaScript geralmente era executada a 1 FPS. Sim, o WebAssembly é muito mais rápido (50 vezes), mas, por algum motivo, não funciona em quase metade dos casos. Também deve ser observado que, em algumas situações, o JavaScript funcionou muito bem e encontrou imediatamente o código de barras. Uma das opções óbvias era aumentar o tempo limite, mas isso só aumentaria a frustração dos usuários e, portanto, não resolvemos o problema real. Portanto, abandonamos essa ideia.

Inicialmente, não conseguimos entender por que a biblioteca C ++ nativa, que funcionava perfeitamente em aplicativos nativos, não mostrava o mesmo resultado na web. Após longos testes e depuração, descobrimos que a velocidade de reconhecimento depende do ângulo de foco do objeto e da sombra do plano de fundo. Mas como, então, tudo funciona em aplicativos nativos? O fato é que, em aplicativos nativos, usamos as APIs internas para foco automático e fornecemos ao usuário a oportunidade de focar manualmente, apontando um dedo para o código de barras. Portanto, aplicativos nativos sempre fornecem à biblioteca imagens claras de alta qualidade.

Percebendo a essência do que está acontecendo, decidimos tentar outra biblioteca nativa: um scanner de código de barras ZBar de código aberto bastante popular e estável. Mais importante, ele funciona bem com imagens borradas e granuladas. Por que não tentar? Como já tínhamos o fluxo de trabalho do WebAssembly, a compilação e implantação do ZBar no WebAssembly ocorreram sem problemas. O desempenho acabou decente, em torno de 15 FPS, embora não seja tão bom quanto o de nossa própria biblioteca C ++. Mas a taxa de sucesso ficou perto de 80% no mesmo tempo limite. Uma melhoria clara em relação à nossa biblioteca C ++, mas ainda não 100%.

O resultado ainda não nos satisfez, mas percebemos algo inesperado. Onde o Zbar caiu, nossa própria biblioteca C ++ fez o trabalho muito rapidamente. Foi uma surpresa agradável. Parece que as bibliotecas processaram imagens de qualidade diferente de maneiras diferentes. Isso nos levou à ideia.

Multithreading e velocidade de corrida


Você provavelmente já entendeu. Por que não criar dois threads de trabalho: um para o Zbar e outro para a nossa biblioteca C ++, e não os executa em paralelo. Quem ganhou (quem primeiro envia um código de barras válido) envia o resultado para o fluxo principal e os dois trabalhadores param. Implementamos esse cenário e começamos a nos testar, tentando simular o maior número possível de cenários. Essa configuração mostrou 95% das verificações bem-sucedidas. Muito melhor que os resultados anteriores, mas ainda não 100%.

Uma das sugestões estranhas foi adicionar a biblioteca JavaScipt original à competição. Serão três fluxos. Sinceramente, não pensamos que isso mudaria alguma coisa. Mas esse teste não exigiu nenhum esforço, porque padronizamos a interface de trabalho. Para nossa surpresa, com três fluxos, a taxa de sucesso chegou perto de 100%. Isso novamente foi completamente inesperado. Como mencionado anteriormente, o JavaScript funcionou muito bem em algumas situações. Aparentemente, ele fechou a lacuna. Portanto, a sabedoria popular da lei é "O JavaScript sempre vence" . Se sem piadas, a ilustração a seguir fornece uma visão geral da arquitetura final que implementamos.


Scanner de código de barras de arquitetura da Web

A figura a seguir mostra um diagrama funcional de alto nível:


Diagrama funcional de um scanner de código de barras

Nota sobre carregamento de recursos


Os recursos necessários para o scanner funcionar são pré-carregados após renderizar a página principal. Dessa forma, a página de destino é carregada rapidamente e está pronta para interação. Os recursos do WebAssembly (arquivos wasm e scripts de middleware) e a biblioteca do scanner JavaScript são pré-carregados e armazenados em cache usando XMLHttpRequest após o carregamento da página principal. É importante aqui que eles não sejam executados imediatamente para deixar o thread principal livre para interação do usuário com a página. A execução ocorre apenas quando o usuário clica no ícone do código de barras. Se o usuário clicar no ícone antes de carregar os recursos, eles serão carregados sob demanda e executados imediatamente. O manipulador de eventos do scanner de código de barras e o controlador de trabalho são carregados com a página, mas são muito pequenos.

Resultados


Após testes rigorosos e uso interno pelos funcionários, lançamos os testes A / B nos usuários. O ícone do scanner (captura de tela abaixo) foi mostrado ao grupo de teste, mas não ao grupo de controle.


Produto final

Para avaliar o sucesso, introduzimos a métrica Taxa de conclusão de rascunho. Este é o tempo entre começar a editar um rascunho e enviar um formulário. A métrica deve mostrar como um scanner de código de barras ajuda as pessoas a preencher formulários. O teste durou várias semanas e os resultados foram muito agradáveis. Eles são totalmente consistentes com nossa hipótese original. O tempo de conclusão do rascunho diminuiu 30% para um fluxo com um scanner de código de barras.


Resultados do teste A / B

Também adicionamos perfis para avaliar a eficácia de todos os tipos de scanners. Como esperado, a maior contribuição foi feita por Zbar (53% das verificações bem-sucedidas), depois por nossa biblioteca C ++ (34%) e, finalmente, pela biblioteca JavaScript com 13%.



Conclusão


A experiência de implementar o WebAssembly se tornou muito informativa para nós. Os engenheiros estão muito felizes com o surgimento de novas tecnologias e imediatamente querem experimentá-las. Se a tecnologia também é útil para os clientes, isso é uma alegria dupla. Vamos repetir o pensamento expresso no início do artigo. A tecnologia está se desenvolvendo em um ritmo muito rápido. Todos os dias algo novo aparece. Mas apenas algumas tecnologias são importantes para os clientes, e o WebAssembly é uma delas. Nossa maior conclusão deste exercício é dizer "não" em 99 situações e "sim" no único caso em que é realmente importante para os clientes.

No futuro, planejamos expandir o uso de um scanner de código de barras e introduzi-lo no lado dos compradores, para que eles possam digitalizar códigos de produtos offline para pesquisa e compra no eBay. Também consideraremos expandir a função usando a API de detecção de forma e outras funções no navegador. Mas estamos satisfeitos por ter encontrado o caso de uso correto para o WebAssembly no eBay e aplicado com sucesso a tecnologia no comércio eletrônico.

Agradecimentos especiais a Surma Das e Lin Clark por vários artigos no WebAssembly. Eles realmente nos ajudaram a quebrar o impasse várias vezes.

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


All Articles