Recentemente ouvi novamente que o pessoal do PHP ainda fala sobre aspas simples versus aspas duplas e que usar aspas simples é apenas uma micro otimização, mas se você se acostumar a usar aspas simples o tempo todo, você economizará um monte de CPU ciclos!
"Tudo já foi dito, mas ainda não por todos" – Karl Valentin
É com esse espírito que estou escrevendo um artigo sobre o mesmo assunto que Nikita Popov escreveu há 12 anos (se você está lendo o artigo dele, pode parar de ler aqui).
PHP realiza interpolação de strings, na qual busca o uso de variáveis em uma string e as substitui pelo valor da variável utilizada:
$juice = "apple"; echo "They drank some $juice juice."; // will output: They drank some apple juice.
Este recurso é limitado a strings entre aspas duplas e heredoc. Usar aspas simples (ou nowdoc) produzirá um resultado diferente:
$juice = "apple"; echo 'They drank some $juice juice.'; // will output: They drank some $juice juice.
Veja só: o PHP não procurará variáveis nessa string entre aspas. Então poderíamos começar a usar aspas simples em todos os lugares. Então as pessoas começaram a sugerir mudanças como esta ..
- $juice = "apple"; $juice = 'apple';
.. porque será mais rápido e economizará vários ciclos de CPU com cada execução desse código porque o PHP não procura variáveis em strings entre aspas simples (que são inexistentes no exemplo de qualquer maneira) e todos estão felizes, caso encerrado.
Obviamente, há uma diferença no uso de aspas simples e aspas duplas, mas para entender o que está acontecendo, precisamos nos aprofundar um pouco mais.
Mesmo que o PHP seja uma linguagem interpretada, ele usa uma etapa de compilação na qual certas partes são executadas juntas para obter algo que a máquina virtual possa realmente executar, que são os opcodes. Então, como passamos do código-fonte PHP para os opcodes?
O lexer verifica o arquivo de código-fonte e o divide em tokens. Um exemplo simples do que isso significa pode ser encontrado na documentação da função token_get_all(). Um código fonte PHP de apenas
T_OPEN_TAG (Podemos ver isso em ação e brincar com isso neste trecho 3v4l.org.
O analisador
O analisador pega esses tokens e gera uma árvore de sintaxe abstrata a partir deles. Uma representação AST do exemplo acima fica assim quando representada como JSON:
{ "data": [ { "nodeType": "Stmt_Echo", "attributes": { "startLine": 1, "startTokenPos": 1, "startFilePos": 6, "endLine": 1, "endTokenPos": 4, "endFilePos": 13 }, "exprs": [ { "nodeType": "Scalar_String", "attributes": { "startLine": 1, "startTokenPos": 3, "startFilePos": 11, "endLine": 1, "endTokenPos": 3, "endFilePos": 12, "kind": 2, "rawValue": "\"\"" }, "value": "" } ] } ] }Caso você queira brincar com isso também e ver como é o AST para outro código, encontrei https://phpast.com/ de Ryan Chandler e https://php-ast-viewer.com/ que ambos mostram o AST de um determinado trecho de código PHP.
O compilador
O compilador pega o AST e cria opcodes. Os opcodes são as coisas que a máquina virtual executa, é também o que será armazenado no OPcache se você tiver essa configuração e habilitada (o que eu recomendo fortemente).
Para visualizar os opcodes temos múltiplas opções (talvez mais, mas eu conheço essas três):
- use a extensão dumper lógico vulcan. Também está incluído em 3v4l.org
- use phpdbg -p script.php para despejar os opcodes
- ou use a configuração INI opcache.opt_debug_level para OPcache para fazer com que ele imprima os opcodes
- um valor de 0x10000 gera opcodes antes da otimização
- um valor de 0x20000 gera opcodes após a otimização
$ echo ' foo.php $ php -dopcache.opt_debug_level=0x10000 foo.php $_main: ... 0000 ECHO string("") 0001 RETURN int(1)Hipótese
Voltando à ideia inicial de economizar ciclos de CPU ao usar aspas simples versus aspas duplas, acho que todos concordamos que isso só seria verdade se o PHP avaliasse essas strings em tempo de execução para cada solicitação.
O que acontece em tempo de execução?
Então vamos ver quais opcodes o PHP cria para as duas versões diferentes.
Aspas duplas:
0000 ECHO string("apple") 0001 RETURN int(1)vs. aspas simples:
0000 ECHO string("apple") 0001 RETURN int(1)Ei, espere, algo estranho aconteceu. Isso parece idêntico! Para onde foi minha micro otimização?
Bem, talvez, apenas talvez a implementação do manipulador de opcode ECHO analise a string fornecida, embora não haja nenhum marcador ou algo mais que diga para fazer isso ... hmm ?
Vamos tentar uma abordagem diferente e ver o que o lexer faz para esses dois casos:
Aspas duplas:
T_OPEN_TAG (vs. aspas simples:
Line 1: T_OPEN_TAG (Os tokens ainda distinguem entre aspas duplas e simples, mas verificar o AST nos dará um resultado idêntico para ambos os casos - a única diferença é o rawValue nos atributos do nó Scalar_String, que ainda possui aspas simples/duplas, mas o valor usa aspas duplas em ambos os casos.
Nova hipótese
Será que a interpolação de strings é realmente feita em tempo de compilação?
Vamos verificar com um exemplo um pouco mais "sofisticado":
Os tokens para este arquivo são:
T_OPEN_TAG (Veja os dois últimos tokens! A interpolação de strings é tratada no lexer e, como tal, é uma questão de tempo de compilação e não tem nada a ver com tempo de execução.
Para completar, vamos dar uma olhada nos opcodes gerados por isso (após a otimização, usando 0x20000):
0000 ASSIGN CV0($juice) string("apple") 0001 T2 = FAST_CONCAT string("juice: ") CV0($juice) 0002 ECHO T2 0003 RETURN int(1)Este é um código de operação diferente do que tínhamos em nosso simples
Vá direto ao ponto: devo concatenar ou interpolar?
Vamos dar uma olhada nessas três versões diferentes:
O primeiro opcode atribui a string "apple" à variável $juice:
0000 ASSIGN CV0($juice) string("apple")
A primeira versão (interpolação de strings) usa uma corda como estrutura de dados subjacente, que é otimizada para fazer o mínimo possível de cópias de strings.
0001 T2 = ROPE_INIT 4 string("juice: ") 0002 T2 = ROPE_ADD 1 T2 CV0($juice) 0003 T2 = ROPE_ADD 2 T2 string(" ") 0004 T1 = ROPE_END 3 T2 CV0($juice) 0005 ECHO T1
A segunda versão é a mais eficaz em termos de memória, pois não cria uma representação de string intermediária. Em vez disso, ele faz várias chamadas para o ECHO, o que é uma chamada de bloqueio do ponto de vista de E/S, portanto, dependendo do seu caso de uso, isso pode ser uma desvantagem.
0006 ECHO string("juice: ") 0007 ECHO CV0($juice) 0008 ECHO string(" ") 0009 ECHO CV0($juice)
A terceira versão usa CONCAT/FAST_CONCAT para criar uma representação de string intermediária e, como tal, pode usar mais memória do que a versão em corda.
0010 T1 = CONCAT string("juice: ") CV0($juice) 0011 T2 = FAST_CONCAT T1 string(" ") 0012 T1 = CONCAT T2 CV0($juice) 0013 ECHO T1
Então... qual é a coisa certa a fazer aqui e por que é interpolação de strings?
A interpolação de string usa um FAST_CONCAT no caso de echo "juice: $juice"; ou opcodes ROPE_* altamente otimizados no caso de echo "juice: $juice $juice";, mas o mais importante é que comunica a intenção claramente e nada disso foi um gargalo em qualquer um dos aplicativos PHP com os quais trabalhei até agora, então nada disso realmente importa.
A interpolação de strings é uma questão de tempo de compilação. Concedido, sem o OPcache o lexer terá que verificar as variáveis usadas em strings entre aspas duplas em cada solicitação, mesmo que não haja nenhuma, diminuindo os ciclos de CPU, mas honestamente: o problema não são as strings entre aspas duplas, mas não usando o OPcache!
No entanto, há uma ressalva: PHP até 4 (e acredito que mesmo incluindo 5.0 e talvez até 5.1, não sei) fazia interpolação de strings em tempo de execução, então usando essas versões... hmm, acho que se alguém realmente ainda usa PHP 5, o mesmo que acima se aplica: O problema não são as strings entre aspas duplas, mas o uso de uma versão desatualizada do PHP.
Atualize para a versão mais recente do PHP, habilite o OPcache e viva feliz para sempre!
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3