- lsr é um novo programa substituto do
ls(1), desenvolvido com a biblioteca de IO ourio, baseada em io_uring
- Em comparação com o
ls tradicional e ferramentas alternativas (eza, lsd, uutils ls), a execução do comando é muito mais rápida e o número de chamadas de sistema é mais de 10 vezes menor
- Para maximizar o desempenho, toda a IO principal, como abertura de diretórios,
stat e lstat, é processada de forma assíncrona e em lote com io_uring. Quanto mais arquivos houver, mais rápido ele fica
- Usa o StackFallbackAllocator do Zig para minimizar chamadas a
mmap durante a alocação de memória
- É compilado estaticamente, sem linkagem dinâmica, então até o tamanho do executável é menor que o do
ls tradicional
Introdução e relevância
- O projeto lsr é uma alternativa ao comando
ls comum, uma ferramenta rápida de listagem de diretórios que usa io_uring
- Em comparação com
ls, eza, lsd e uutils ls, ele apresenta desempenho excepcional em velocidade de execução e uso de chamadas de sistema
- Executa o máximo possível de IO diretamente com a biblioteca de IO desenvolvida pelo próprio autor, ourio
- Os benchmarks mostram que o lsr comprova desempenho rápido e qualidade mesmo em ambientes com grande volume de arquivos
Resultados de benchmark
- Foi usado
hyperfine para medir o tempo de execução de cada comando em diretórios com n arquivos regulares
- Com
lsr -al, no intervalo de 10 a 10.000 arquivos, ele registrou tempos de execução muito menores que os do ls tradicional e de suas alternativas
- Exemplo: com 10.000 arquivos, o lsr registrou 22,1 ms, superando o
ls tradicional (38,0 ms), eza (40,2 ms), lsd (153,4 ms) e uutils ls (89,6 ms), alcançando a melhor velocidade
- A contagem de chamadas de sistema foi feita com
strace -c
lsr -al: de no mínimo 20 chamadas (n=10) até no máximo 848 (n=10.000), mantendo uma contagem muito baixa
- O
ls chegou a 30.396 chamadas (n=10.000), e o lsd a 100.512; as demais alternativas também ficaram na faixa de milhares a centenas de milhares
- Nas mesmas condições, o lsr alcançou a melhor eficiência com pelo menos 10 vezes menos syscalls
Estrutura e implementação do lsr
- O programa funciona em três etapas: parse de argumentos, coleta de dados e saída de dados
- Toda a IO acontece na segunda etapa, de coleta de dados, e todo acesso a arquivos e consulta de informações possível é tratado com io_uring
- Abertura do diretório de destino,
stat, lstat e consulta de informações de tempo/usuário/grupo são todos executados com base em io_uring
- O processamento em lote de
stat reduz drasticamente o número de chamadas de sistema
- Com o Zig StackFallbackAllocator, 1 MB de memória é pré-alocado para minimizar chamadas adicionais de sistema, como
mmap
Build estático e otimização
- Como é um build totalmente estático, sem linkagem dinâmica com libc, o overhead de execução é significativamente menor
- Em comparação com o GNU
ls, o tamanho do build ReleaseSmall do lsr é menor: 138,7 KB vs 79,3 KB
- Porém, o lsr não tem suporte a locale (idioma/região). O
ls comum tem overhead por oferecer suporte a vários idiomas
Análise de chamadas de sistema e problemas de desempenho
- O
lsd chama clock_gettime mais de 5 vezes por arquivo; o motivo é desconhecido (supõe-se medição interna de tempo, etc.)
- A tarefa de ordenação (sorting) representa uma parte significativa do trabalho total (cerca de 30%)
- O
uutils ls é eficiente em chamadas de sistema, mas fica mais lento no processamento de ordenação
- Só a adoção de io_uring já confirma o potencial de ganhos revolucionários de desempenho em ambientes de IO de alta carga, como servidores
Conclusão
- O tempo de desenvolvimento também não foi longo, e o efeito da otimização de syscalls superou as expectativas
- O lsr é um substituto experimental do
ls que alcança ao mesmo tempo alta velocidade, poucas chamadas de sistema e tamanho enxuto
- É muito adequado para ambientes com grande volume de arquivos ou sistemas em que IO de alto desempenho é importante
- Embora tenha algumas limitações de funcionalidade, como a falta de suporte a locale, ele mostra resultados inovadores tanto na prática quanto nos benchmarks
1 comentários
Comentários no Hacker News
O autor do projeto se identificou e comentou que é possível ver um texto de apresentação sobre o
lsrbaseado em io_uring aquils(1)original do UNIX era extremamente rápido por causa do design simples, mas foi ficando mais lento com o acúmulo de pequenos custos de vários recursos extras, VFS, vários conjuntos de caracteres, suporte a cores etc. Achou que isso rende uma discussão interessante sobre o custo de abstração que o io_uring lidalsrebfs -ls. Atualmente o bfs usa io_uring apenas em multithreading, mas talvez valha pensar em usar também em single thread (bfs -j1)tim(link de apresentação) talvez seja melhor do que com hyperfine. Como foi escrito em Nim, pode ser um desafio, mas é engraçado como os nomes se parecem por coincidêncialibevringque criou ainda está no começo, então mantém a mente aberta para substituí-la por ourio, se necessário. Acha que, se projetos baseados em Zig oferecerem suporte a bindings para C/C++, isso pode ser útil na migração de C/C++ para ZigFiquei curioso sobre como seria o desempenho do lsr em um servidor NFS, especialmente em um ambiente de rede ruim. É evidente que usar syscalls POSIX bloqueantes em um serviço de rede instável é uma desvantagem do design do NFS. Também seria interessante observar o quanto o io_uring consegue aliviar esse problema
lsetc.) não precisassem lidar diretamente com erros de rede. O protocolo NFS original não guardava estado, então mesmo após reinicializar o servidor o cliente se recuperava automaticamente. Fico curioso se o io_uring repassa corretamente os erros nesses casos. Também interessa saber como isso é tratado em timeouts de NFS$HOMEvia NFS em vários PCs e considera a usabilidade média do NFS bastante satisfatória, desde que a rede seja boa e se evitem casos mais complicados como escrita paralela. Dito isso, já sofreu bastante quando o cabo de rede estava instável por causa das desconexõesctrl+cnão funciona em apps que estão lendo uma pasta NFS. Em teoria, a opção de montagemintrpermitia interromper operações em andamento em um servidor remoto ao entregar um sinal, mas no Linux isso já foi removido há muito tempo (hoje só resta a opçãosoft) (referência 1, referência 2, suporte no FreeBSD)É interessante que, mesmo reduzindo em 35 vezes o número de chamadas de syscall, a melhora de velocidade tenha sido de só cerca de 2 vezes
O projeto chama mais atenção como exemplo de uso de io_uring para ganhos de desempenho de longo prazo, ou como introdução/tutorial de uso, do que pelo ganho prático em si. Em comparação com ferramentas existentes como eza, não parecia haver uma motivação intuitiva clara para precisar disso. Se listar dez mil arquivos leva 40 ms contra 20 ms, provavelmente não daria para sentir diferença nenhuma em uma execução isolada
ls/duleva vários minutos. Os comandos padrão do coreutils muitas vezes não conseguem aproveitar direito o desempenho de SSDs modernosO lsr é legal, mas o suporte a cores e ícones do eza é melhor. A pessoa usa a configuração
eza --icons=always -1, então arquivos de música (.opusetc.) já aparecem com ícone e cor automaticamente, enquanto no lsr são mostrados só como arquivos comuns. Ainda assim, ficou claro para ela que o lsr é fácil de aplicar patch e extremamente rápido. Também criou expectativa de vercate outros utilitários feitos nesse estilo, achou curioso o uso de tangled.sh e atproto, e por ter sido escrito em Zig parece mais acessível para iniciantes do que Rustbaté um substituto moderno paracat(bat aqui)lstem cores bonitasFicou curioso sobre por que nem todas as ferramentas de CLI usam io_uring. Ao conectar um nvme em usb 3.2 gen2, com ferramentas comuns obtém 740 MB/s, mas com ferramentas baseadas em aio ou io_uring chega a 1005 MB/s. Acha que isso também tem a ver com estratégia de profundidade de fila e redução de locks
#ifdef, então a adoção de novas tecnologias específicas de plataforma/versão é lenta. Hoje em dia, acha que a vantagem da compatibilidade entre várias plataformas tipo POSIX já não é tão grande quanto antesasyncfosse algo natural no nível da linguagem, talvez a migração fosse mais fácil, mas hoje seria preciso um grande refactor. O io_uring também ainda não está totalmente estabilizado, então talvez valha observar o surgimento de tecnologias novas ou até ferramentas/IA de migração automáticaNo
strace, foi observado que o lsd chamaclock_gettimealgo como 5 vezes por arquivo. Não se sabe exatamente o motivo; talvez seja para calcular expressões como “há tantos minutos/horas/dias” para cada timestamp, ou talvez seja legado de alguma bibliotecaclock_gettimenão é uma syscall de verdade, e sim algo tratado via vDSO (vejaman 7 vDSO). Fica a dúvida se o Zig não estaria aproveitando essa estruturaFugindo um pouco do assunto, surgiu curiosidade sobre, em servidores enterprise com Mellanox 4 ou 5 e NIC de 10G, quanto o io_uring reduz na prática o overhead de latência de socket em microssegundos em comparação com LD_PRELOAD. Parece que os efeitos não se acumulam, e gostaria de ouvir números concretos de experiência real ou benchmarks
O io_uring não oferece suporte a
getdents, então o principal ganho aparece emstatem lote (por exemplo,ls -l). Fica a sensação de que seria bom se desse para tornar o processamento degetdentsassíncrono e sobrepostogetdents+stat), parte da vantagem específica do io_uring provavelmente seria anuladaParece engraçado haver ícones para extensões
.mjse.cjs, mas não para extensões como.c,.he.sh