Por que require_once é tão ruim de usar?

143

Tudo o que li sobre melhores práticas de codificação PHP continua dizendo que não use require_oncepor causa da velocidade.

Por que é isso?

Qual é a melhor / melhor maneira de fazer a mesma coisa require_once? Se isso importa, estou usando o PHP 5.

Uberfuzzy
fonte
9
Esta questão é bastante antiga agora, e as respostas são duvidosamente relevantes. Seria ótimo ver um conjunto atualizado de respostas dos participantes :)
Purefan

Respostas:

108

require_oncee include_onceambos exigem que o sistema mantenha um registro do que já foi incluído / necessário. Toda *_oncechamada significa verificar esse log. Definitivamente, há algum trabalho extra sendo feito lá, mas o suficiente para prejudicar a velocidade de todo o aplicativo?

... Eu realmente duvido ... Não, a menos que você esteja usando um hardware muito antigo ou faça muito isso .

Se você estiver fazendo milhares *_once, poderá fazer o trabalho sozinho de uma maneira mais leve. Para aplicativos simples, apenas certifique-se de incluí-lo apenas uma vez deve ser suficiente, mas se você ainda estiver recebendo erros de redefinição, poderá algo como:

if (!defined('MyIncludeName')) {
    require('MyIncludeName');
    define('MyIncludeName', 1);
}

Pessoalmente, vou ficar com as *_oncedeclarações, mas no benchmark bobo de milhões de passes, você pode ver uma diferença entre os dois:

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

10-100 × mais lento require_oncee é curioso que require_onceseja aparentemente mais lento hhvm. Novamente, isso é relevante apenas para o seu código se você estiver executando *_oncemilhares de vezes.


<?php // test.php

$LIMIT = 1000000;

$start = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    if (!defined('include.php')) {
        require('include.php');
        define('include.php', 1);
    }

$mid = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    require_once('include.php');

$end = microtime(true);

printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);

<?php // include.php

// do nothing.
Oli
fonte
29
Duvido que seu método definido () seja mais rápido que a tabela de pesquisa interna, mas concordo com o seu argumento geral - certamente não é um problema ?!
Bobby Jack
1
Tenho quase certeza de que você está certo, Bobby, mas não estou defendendo as definições mais do que uma vez. É apenas uma opção. O tempo que levaria para interpretar o código pode até torná-lo um pouco mais lento, mas, dito isso, não sei quão completo é o método interno. Pode fazer um trabalho extra para garantir que não haja duplicatas.
Oli
8
A outra desvantagem é APC não include_once cache e require_once chama IIRC
dcousineau
3
Acabei de fazer um teste muito básico dos dois métodos - fiz 1.000.000 de iterações, incluindo um arquivo que simplesmente definia uma constante 'testinclude' como true. No primeiro teste, usei require_once, o segundo usei if (! Defined ('testinclude')) e os resultados foram interessantes: Exigir: 0.81639003753662 Não definido: 0.17906713485718 definido é 0,63732290267944 microssegundos mais rápido.
Travis Weston
O caso com define será mais rápido, pois você não está executando nenhum tipo de verificação extra, como usar o caminho real. Não está comparando duas coisas que são realmente iguais.
Jgmjgm 11/07
150

Esse tópico me faz estremecer, porque já existe uma "solução postada" e, para todos os efeitos, está errada. Vamos enumerar:

  1. As definições são realmente caras em PHP. Você pode procurar ou testar você mesmo, mas a única maneira eficiente de definir uma constante global em PHP é através de uma extensão. (As constantes de classe são realmente um desempenho decente, mas esse é um ponto discutível, por causa de 2)

  2. Se você estiver usando require_once()adequadamente, ou seja, para inclusão de classes, você nem precisa de uma definição; basta verificar se class_exists('Classname'). Se o arquivo que você está incluindo contiver código, ou seja, você o estiver usando de maneira processual, não há absolutamente nenhuma razão que require_once()seja necessária para você; sempre que você incluir o arquivo que você presume estar fazendo uma chamada de sub-rotina.

Por um tempo, muitas pessoas usaram o class_exists()método para suas inclusões. Não gosto porque é fugaz, mas eles tinham bons motivos para:require_once() era bastante ineficiente antes de algumas das versões mais recentes do PHP. Mas isso foi corrigido, e é meu argumento que o bytecode extra que você precisaria compilar para a condicional e a chamada de método extra, superaria de longe qualquer verificação interna de hashtable.

Agora, uma admissão: esse material é difícil de testar, porque é responsável por muito pouco tempo de execução.

Aqui está a pergunta em que você deve estar pensando: inclui, como regra geral, caro no PHP, porque toda vez que o intérprete o atinge, ele deve retornar ao modo de análise, gerar os códigos de operação e voltar. Se você tiver mais de 100 inclusões, isso definitivamente terá um impacto no desempenho. A razão pela qual o uso ou não do require_once é uma pergunta tão importante é porque dificulta a vida dos caches de código de operação. Uma explicação para isso pode ser encontrada aqui, mas o que isso se resume é o seguinte:

  • Se durante o tempo de análise, você souber exatamente o que inclui os arquivos necessários para toda a vida útil da solicitação, require()aqueles no início e o cache do opcode tratará de tudo o mais para você.

  • Se você não está executando um cache de código de operação, está em uma situação difícil. A inclusão de todas as suas inclusões em um arquivo (não faça isso durante o desenvolvimento, apenas na produção) certamente pode ajudar a analisar o tempo, mas é uma tarefa difícil de fazer e você também precisa saber exatamente o que incluirá durante o processo. solicitação.

  • O carregamento automático é muito conveniente, mas lento, porque a lógica do carregamento automático precisa ser executada toda vez que uma inclusão é feita. Na prática, descobri que o carregamento automático de vários arquivos especializados para uma solicitação não causa muitos problemas, mas você não deve carregar automaticamente todos os arquivos necessários.

  • Se você tiver talvez 10 inclusões (este é um cálculo muito posterior ao envelope), toda essa punheta não vale a pena: apenas otimize suas consultas ao banco de dados ou algo assim.

Edward Z. Yang
fonte
14
Isso tem 4 anos e, na maioria das vezes, não se aplica mais define(), require_once()e defined()todos levam cerca de 1 a 2 microssegundos cada na minha máquina.
22630 Daniel
72
Mas isso é 2 microssegundos antes que o usuário tenha a página. Mais de um ano de visualizações de página, isso pode salvar o usuário por 3 segundos! Eles podiam assistir a um décimo de um comercial naquele tempo! Pense no usuário. Não desperdice microssegundos.
Andrew Ensley
15
Para que todos saibam o sarcasmo, um microssegundo é 1/1000000 de segundo.
Andy Chase
1
@AndrewEnsley Você está simplesmente enganado com todo o seu sarcasmo. Você desconhece o fato de que o PHP também roda em microprocessadores, 1 microssegundo em seu PC é vários milissegundos em um microprocessador. Agora, que tal ter 20 arquivos incluídos, um projeto maior? Isso é 20 vezes o atraso de vários milissegundos introduzido, então já estamos alcançando um ponto que é perceptível para um humano. Se esse script for chamado com freqüência, causará problemas de desempenho no sistema. A otimização não é uma piada e o mundo inteiro não está mudando o seu PC. Existem dez milhares de CPUs em uso.
John
2
@John. Era uma piada feita de bom humor. Nenhuma malícia pretendida. Se vale a pena otimizar suas inclusões, vá em frente.
Andrew Ensley
66

Fiquei curioso e verifiquei o link de Adam Backstrom para Tech Your Universe . Este artigo descreve um dos motivos que exigem que deve ser usado em vez de require_once. No entanto, suas reivindicações não se sustentaram na minha análise. Eu estaria interessado em ver onde eu posso ter analisado mal a solução. Eu usei o PHP 5.2.0 para comparações.

Comecei criando 100 arquivos de cabeçalho que usavam require_once para incluir outro arquivo de cabeçalho. Cada um desses arquivos se parecia com:

<?php
    // /home/fbarnes/phpperf/hdr0.php
    require_once "../phpperf/common_hdr.php";

?>

Eu os criei usando um hack rápido do Bash:

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
    echo "<?php
    // $i" > $i
    cat helper.php >> $i;
done

Dessa forma, eu poderia facilmente trocar entre usar require_once e exigir ao incluir os arquivos de cabeçalho. Eu criei um app.php para carregar os cem arquivos. Parecia:

<?php
    // Load all of the php hdrs that were created previously
    for($i=0; $i < 100; $i++)
    {
        require_once "/home/fbarnes/phpperf/hdr$i.php";
    }

    // Read the /proc file system to get some simple stats
    $pid = getmypid();
    $fp = fopen("/proc/$pid/stat", "r");
    $line = fread($fp, 2048);
    $array = split(" ", $line);

    // Write out the statistics; on RedHat 4.5 with kernel 2.6.9
    // 14 is user jiffies; 15 is system jiffies
    $cntr = 0;
    foreach($array as $elem)
    {
        $cntr++;
        echo "stat[$cntr]: $elem\n";
    }
    fclose($fp);
?>

Comparei os cabeçalhos require_once com os headers require que usavam um arquivo de cabeçalho parecido com:

<?php
    // /home/fbarnes/phpperf/h/hdr0.php
    if(!defined('CommonHdr'))
    {
        require "../phpperf/common_hdr.php";
        define('CommonHdr', 1);
    }
?>

Não encontrei muita diferença ao executar isso com require vs. require_once. De fato, meus testes iniciais pareciam sugerir que require_once era um pouco mais rápido, mas eu não acredito necessariamente nisso. Repeti o experimento com 10000 arquivos de entrada. Aqui eu vi uma diferença consistente. Eu executei o teste várias vezes, os resultados estão próximos, mas usando require_once usa, em média, 30,8 jiffies do usuário e 72,6 jiffies do sistema; o uso requer usos em média de 39,4 instantes do usuário e 72,0 instantes do sistema. Portanto, parece que a carga é um pouco menor usando require_once. No entanto, o tempo do relógio de parede é ligeiramente aumentado. As 10.000 chamadas require_once usam 10,15 segundos para concluir, em média, e 10.000 exigem chamadas usam 9,84 segundos, em média.

O próximo passo é analisar essas diferenças. Eu usei o strace para analisar as chamadas do sistema que estão sendo feitas.

Antes de abrir um arquivo a partir de require_once, são feitas as seguintes chamadas do sistema:

time(NULL)                              = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL)                              = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Isso contrasta com exige:

time(NULL)                              = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL)                              = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Tech Your Universe implica que require_once deve fazer mais chamadas lstat64. No entanto, ambos fazem o mesmo número de chamadas lstat64. Possivelmente, a diferença é que não estou executando o APC para otimizar o código acima. No entanto, a seguir, comparei a saída do strace para todas as execuções:

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

Efetivamente, existem aproximadamente mais duas chamadas do sistema por arquivo de cabeçalho ao usar require_once. Uma diferença é que require_once possui uma chamada adicional para a função time ():

[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008

A outra chamada do sistema é getcwd ():

[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004

Isso é chamado porque eu decidi o caminho relativo mencionado nos arquivos hdrXXX. Se eu fizer disso uma referência absoluta, a única diferença é a chamada de tempo adicional (NULL) feita no código:

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190705 strace_1000r.out
  200705 strace_1000ro.out
  391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008

Isso parece implicar que você pode reduzir o número de chamadas do sistema usando caminhos absolutos em vez de caminhos relativos. A única diferença fora disso são as chamadas de tempo (NULL) que parecem ser usadas para instrumentar o código para comparar o que é mais rápido.

Outra observação é que o pacote de otimização da APC tem uma opção chamada "apc.include_once_override", que afirma que reduz o número de chamadas do sistema feitas pelas chamadas require_once e include_once (consulte a documentação do PHP ).

terson
fonte
6
E qualquer "otimização" que você precise executar 10.000 vezes para ver uma diferença tão minúscula nem sequer é digna de preocupação. Use um profiler e descubra onde estão os gargalos reais em seu aplicativo. Duvido que esta questão seja o gargalo.
DGM
1
O que isso tudo significa é que isso não importa. Use o que funcionar melhor para você logicamente.
Buttle Butkus
wat são jiffies
OverCoder 15/09/16
21

Você pode nos fornecer links para essas práticas de codificação que dizem para evitá-lo? Para mim, é um não-problema completo . Eu mesmo não olhei o código-fonte, mas imagino que a única diferença entre includee include_onceé que include_onceadicione esse nome de arquivo a uma matriz e verifique a matriz toda vez. Seria fácil manter essa matriz classificada, portanto, a pesquisa nela deveria ser O (log n), e mesmo um aplicativo de tamanho médio teria apenas algumas dezenas de inclusões.

nickf
fonte
um é, chazzuka.com/blog/?p=163 eles realmente não 'não', mas muitas coisas 'caro' somar. e, na verdade, tudo incluído / arquivos necessários são adicionados a uma matriz interna (theres uma função para devolvê-lo), eu acho que as do _once tem que loop que array e fazer de STRCMP, que somariam
Uberfuzzy
7

Uma maneira melhor de fazer as coisas é usar uma abordagem orientada a objetos e usar __autoload () .

Greg
fonte
3
mas o primeiro exemplo na autoloading objetos página ligada a usos require_once
Shabbyrobe
Eu não compro isso. Existem MUITAS situações em que o OO não se encaixa tão adequadamente quanto outros paradigmas; portanto, você não deve forçá-lo apenas a obter as pequenas vantagens que existem com __autoload ().
Bobby Jack
1
você pensaria que o carregamento automático levaria mais do que * _uma (assumindo que você está apenas exigindo o que precisa).
nickf
Não, não é, pelo menos não definitivamente, o Autoload ainda precisa ser incluído de alguma forma e é o último recurso para o PHP antes da falha do erro - portanto, na realidade, o PHP efetua verificações potencialmente desnecessárias em todos os locais que se aplicariam para incluir / exigir e DEPOIS QUE chamaria de carregamento automático (se houver definido) ... PS: __autoload()é desencorajado e pode ser preterido no futuro, você deve usar spl_autoload_register(...)esses dias ... PS2: não me interpretem mal, às vezes uso a funcionalidade de carregamento automático; )
jave.web
6

Não está usando a função que é ruim. É um entendimento incorreto de como e quando usá-lo, em uma base geral de códigos. Vou apenas adicionar um pouco mais de contexto a essa noção possivelmente incompreendida:

As pessoas não devem pensar que require_once é uma função lenta. Você precisa incluir seu código de uma maneira ou de outra. A velocidade de require_once()vs. require()não é o problema. Trata-se das advertências que dificultam o desempenho que podem resultar em usá-lo às cegas. Se usado amplamente sem considerar o contexto, pode levar a um enorme desperdício de memória ou código desnecessário.

O que eu vi realmente ruim é quando grandes estruturas monolíticas são usadas require_once()de todas as maneiras erradas, especialmente em um ambiente complexo orientado a objetos (OO).

Veja o exemplo do uso require_once()na parte superior de todas as classes, como visto em muitas bibliotecas:

require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
  // User functions
}

Portanto, a Userclasse foi projetada para usar as três outras classes. Justo!

Mas agora, e se um visitante estiver navegando no site e nem mesmo estiver conectado e a estrutura carregar: require_once("includes/user.php");para cada solicitação única.

Inclui 1 + 3 classes desnecessárias que nunca serão usadas durante essa solicitação específica. É assim que as estruturas inchadas acabam usando 40 MB por solicitação, em vez de 5 MB ou menos.


As outras maneiras pelas quais isso pode ser mal utilizado é quando uma classe é reutilizada por muitas outras! Digamos que você tenha cerca de 50 classes que usam helperfunções. Para garantir que helpersestejam disponíveis para essas classes quando elas forem carregadas, você obtém:

require_once("includes/helpers.php");
class MyClass{
  // Helper::functions(); // etc..
}

Não há nada errado aqui por si só. No entanto, se uma solicitação de página incluir 15 classes semelhantes. Você está executando require_once15 vezes ou para um visual agradável:

require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");

O uso de require_once () tecnicamente afeta o desempenho para executar essa função 14 vezes, além de precisar analisar essas linhas desnecessárias. Com apenas 10 outras classes altamente usadas com esse problema semelhante, ele poderia representar mais de 100 linhas desse código repetitivo bastante inútil.

Com isso, provavelmente vale a pena usar require("includes/helpers.php");na inicialização do seu aplicativo ou estrutura. Mas como tudo é relativo, tudo depende se o peso versus a frequência de uso da helpersclasse vale a pena salvar de 15 a 100 linhas de require_once(). Mas se a probabilidade de não usar o helpersarquivo em uma solicitação específica for nenhuma, então você requiredeve estar na sua classe principal. Ter require_oncecada aula separadamente torna-se um desperdício de recursos.


A require_oncefunção é útil quando necessário, mas não deve ser considerada uma solução monolítica a ser usada em qualquer lugar para carregar todas as classes.

hexalys
fonte
5

O wiki do PEAR2 (quando existia) costumava listar boas razões para abandonar todas as diretrizes de inclusão / inclusão em favor do carregamento automático , pelo menos para o código da biblioteca. Isso o vincula a estruturas de diretório rígidas quando modelos de embalagem alternativos, como o phar, estão no horizonte.

Atualização: Como a versão arquivada na Web do wiki é extremamente feia, copiei os motivos mais convincentes abaixo:

  • include_path é necessário para usar um pacote (PEAR). Isso dificulta o empacotamento de um pacote PEAR dentro de outro aplicativo com seu próprio include_path, a criação de um único arquivo contendo as classes necessárias, a movimentação de um pacote PEAR para um arquivo phar sem modificação extensiva do código fonte.
  • Quando o require_once de nível superior é misturado com o require_once condicional, isso pode resultar em um código que não pode ser alcançado por caches de código de operação, como o APC, que será fornecido com o PHP 6.
  • O require_once relativo exige que o include_path já esteja configurado com o valor correto, impossibilitando o uso de um pacote sem o include_path adequado
Steve Clay
fonte
5

As *_once()funções stat cada diretório pai para garantir que o arquivo que você está incluindo não seja o mesmo que já foi incluído. Isso faz parte do motivo da desaceleração.

Eu recomendo o uso de uma ferramenta como o Siege para benchmarking. Você pode tentar todas as metodologias sugeridas e comparar os tempos de resposta.

Mais informações require_once()estão no Tech Your Universe .

Annika Backstrom
fonte
Obrigado pelo ponteiro para o artigo. require_once () é um bom cinto de segurança para arquivos com inclusão dupla, e continuaremos a usá-lo, mas poder limpá-lo é bom.
Andy Lester
2

Mesmo que require_oncee include_once sejam mais lentos do que requiree include(ou quaisquer alternativas que possam existir), estamos falando do menor nível de micro-otimização aqui. Você gasta muito mais tempo otimizando esse loop ou consulta de banco de dados mal escrito do que se preocupando com algo assim require_once.

Agora, alguém poderia argumentar dizendo que require_oncepermite práticas de codificação ruins, porque você não precisa prestar atenção para manter suas inclusões limpas e organizadas, mas isso não tem nada a ver com a função em si e principalmente com a velocidade.

Obviamente, o carregamento automático é melhor em prol da limpeza do código e da facilidade de manutenção, mas quero deixar claro que isso não tem nada a ver com velocidade .

NeuroXc
fonte
0

Você testa, usando include, a alternativa de oli e __autoload (); e teste-o com algo como o APC instalado.

Duvido que o uso constante acelere as coisas.

Dinoboff
fonte
0

Sim, é um pouco mais caro do que o simples requerido (). Eu acho que o ponto é se você pode manter seu código organizado o suficiente para não duplicar as inclusões, não use as funções * _once (), pois isso poupará alguns ciclos.

Mas o uso das funções _once () não matará seu aplicativo. Basicamente, não use isso como desculpa para não ter que organizar suas inclusões . Em alguns casos, usá-lo ainda é inevitável, e não é grande coisa.

Lucas Oman
fonte
-2

Eu acho que na documentação do PEAR, há uma recomendação para require, require_once, include e include_once. Eu sigo essa orientação. Sua aplicação seria mais clara.

Ekkmanz
fonte
Algumas referências estariam em ordem.
Peter Mortensen
-3

Não tem nada a ver com velocidade. É sobre falhar graciosamente.

Se require_once () falhar, seu script estará pronto. Nada mais é processado. Se você usar o include_once (), o restante do seu script tentará continuar renderizando, para que seus usuários não sejam os mais sensatos a algo que falhou no seu script.

ashchristopher
fonte
1
Não necessariamente. Na verdade, você pode conectar um manipulador de erros ou um manipulador de desligamento para fornecer ao usuário uma boa página de erro (embora as pessoas raramente o façam). Como desenvolvedor, eu preferiria que as coisas erros imediatamente.
Edward Z. Yang
1
Ou, conforme o caso, não falhe normalmente - se algum arquivo vital não for necessário () corretamente, é uma boa ideia desistir e parar. Mas isso exige vs incluir, enquanto eu acho que a questão está mais focada em exigir vs exigir_once.
HoboBen 12/10/08
-4

Minha opinião pessoal é que o uso de require_once (ou include_once) é uma prática ruim, porque require_once verifica se você já incluiu esse arquivo e suprime erros de arquivos incluídos duplos, resultando em erros fatais (como declaração duplicada de funções / classes / etc.) .

Você deve saber se precisa incluir um arquivo.

Joe Scylla
fonte