Desempenho de chamar funções especificadas por POSIX versus chamadas diretas ao kernel do Linux

2

Em uma resposta no Stack Overflow , forneci um exemplo de código para executar uma pequena tarefa mencionada na pergunta. A pergunta original tinha a ver com a técnica de desempenho mais rápido (portanto, os critérios de desempenho estão em jogo aqui).

Outro comentarista / respondente sugeriu que fazer uma chamada de API system definida pelo POSIX (nesse caso, readdir ) não era tão rápida quanto fazer uma chamada de sistema direta no kernel ( syscall(SYS_getdents,...) ) e a diferença de desempenho reivindicada está na faixa de 25%. (Eu não implantei e re-benchmark; acredito que o desempenho poderia de fato ser melhor.)

Minha pergunta é sobre as características de desempenho da solução baseada em syscall proposta e por que elas podem ser mais rápidas. Posso pensar em algumas razões pelas quais o desempenho pode ser melhor:

  1. POSIX readdir é inerentemente mais complicado que syscall(SYS_getdents,...) / getdents()
  2. readdir (que, presumivelmente, chama syscall(SYS_getdents,...) simplesmente adiciona a sobrecarga de indireção
  3. readdir apenas retorna um registro (por chamada de kernel) versus syscall(SYS_getdents,...)/ getdents () 'que retorna (presumivelmente) mais de um registro por chamada de kernel

Eu não posso imaginar que o # 1 acima seja verdadeiro. readdir e getdents são tão semelhantes que a implementação de readdir em glibc simplesmente não pode ter muito mais chamadas de sistema "verdadeiras" do que uma invocação direta de syscall(SYS_getdents,...) / getdents() invocaria.

Também não consigo imaginar que o item 2 seja verdadeiro, pois chamar readdir provavelmente envolve getdents e também syscall(SYS_getdents,...) provavelmente chama getdents (a resposta proposta usa especificamente syscall(SYS_getdents,...) em vez de chamar getdents . É possível que tudo dentro do glibc no Linux se reduza a syscall(syscallid, args) , caso em que # 2 provavelmente é verdadeiro.

A última possibilidade parece ser a melhor explicação: menos chamadas para o kernel simplesmente resultam em um desempenho mais rápido.

Existe alguma explicação específica para por que uma "chamada direta ao kernel" seria mensuravelmente mais rápida do que chamar uma função definida pelo POSIX?

    
por Christopher Schultz 09.01.2018 / 23:56

1 resposta

1

Funções como readdir() e amigos são implementadas na libc, que é uma biblioteca compartilhada. Como com todas as bibliotecas compartilhadas, isso adiciona algum redirecionamento para poder resolver o endereço de memória da função dentro da biblioteca compartilhada.

Na primeira vez que qualquer chamada de biblioteca particular é executada, o vinculador dinâmico precisa procurar o endereço da chamada de biblioteca dentro de uma tabela de hash. Isso envolve pelo menos uma (mas possivelmente mais) comparações de strings, um método comparativamente caro. O endereço encontrado é salvo na PLT (tabela de ligação do procedimento), para que na próxima vez que a função for chamada, a sobrecarga de encontrar a função seja reduzida a três instruções (na arquitetura x86, menos que em outra arquiteturas). É por isso que compilar algo como um objeto compartilhado (em vez de um objeto estático) tem alguma sobrecarga. Para obter mais informações sobre a sobrecarga da biblioteca compartilhada e sobre como as bibliotecas compartilhadas funcionam no Linux, consulte a explicação técnica detalhada de Ulrich Drepper sobre o assunto .

A função syscall() em si é também implementada na libc, então também tem esse redirecionamento. No entanto, desde que você usaria apenas essa função (e nenhum outro), o vinculador dinâmico tem menos trabalho a fazer. Além disso, a implementação de uma função específica, como readdir , teria que converter os valores de retorno e fazer a verificação de erros, etc. ao sair da função syscall() , que é uma sobrecarga extra. Um programa que executa syscall() diretamente funcionaria com os valores de retorno direto da chamada do sistema e não precisaria dessa conversão (ainda seria necessário fazer a verificação de erros, o que complicaria a função significativamente).

A desvantagem de executar syscall() diretamente é que você passa para uma API menos portável. O syscall() manpage explica algumas restrições específicas da arquitetura com as quais a libc lida com você; se você usa syscall() diretamente, sua função pode funcionar na arquitetura que você está lidando, mas falharia, digamos, em uma máquina de braço.

Em geral, eu recomendaria não usar diretamente a syscall() API, pelo mesmo motivo que eu recomendaria contra a gravação direta do código em linguagem assembly. Sim, isso pode acabar sendo mais rápido no final, mas a carga de manutenção se torna (muito) mais alta. Algumas coisas que você poderia fazer em vez disso:

  • Não se preocupe com desempenho. Os sistemas continuam ficando mais baratos e, em muitos casos, "adicionar outro sistema para que as coisas corram mais rápido" é mais barato do que "pagar as taxas por hora de um programador para melhorar o desempenho".
  • Compile o software em relação a bibliotecas estáticas em vez de usar bibliotecas compartilhadas, para as poucas pequenas coisas em que o desempenho é crítico (por exemplo, gcc -static )
  • Use um criador de perfil para ver onde as coisas estão indo devagar e concentre-se em essas coisas, em vez de se preocupar em como fazer uma chamada do sistema.
por 10.01.2018 / 12:35