Tentando mudar o PHP para melhor com C
Fala pessoal,
Meu nome é Henrique e queria falar um pouco sobre um projeto que estou trabalhando e muito animado com o retorno da comunidade PHP no Reddit até agora. Conheci hoje esse projeto incrível que o Rafael criou e pensei em compartilhar com a comunidade dev brasileira.
Minha relação com PHP
Eu comecei a programar com PASCAL quando tinha 10 anos, e aos 12 pra 13 anos conheci o C. Mais tarde, arranjei meu primeiro emprego como desenvolvedor PHP aos 17, de lá pra cá (tenho 31) passei por Java, Javascript, Python e hoje trabalho mais com Python e .NET Core. Porém, sempre acompanhei a linguagem PHP e os projetos em torno dela (Laravel, etc) por ser uma linguagem que gosto e levo de coração por me dar meu primeiro emprego haha.
O problema do PHP 8
Alguns anos atrás me deparei com esse problema em relação a performance do PHP para executar cálculos matemáticos em larga escala, principalmente álgebra linear, aritmética em grande quantidade de números e afins.
As arrays do PHP são excelentes como arrays genéricas aonde você pode armazenar tudo, mas são péssimas quando você precisa iterar para acumular, somar e acessar valores. Para os casos aonde você só precisa iterar 100 vezes, ótimo, você provavelmente vai achar que estou falando bobagem. Mas, se você em que carregar 1 milhão de linhas de um arquivo pra fazer uma soma/divisão/multiplicação igual em todas elas, você vai ver que a demora pode começar a ficar inaceitável.
Isso acontece porque as arrays do PHP nada mais são que HashTables no C, ou seja, para cada associação de chave valor você tem um algoritmo de hashing para encontrar seus índices lá no C. Isso torna ela bem prática de usar já que você pode gerar chaves até com strings, porém, torna ela bem lenta por conta do algoritmo de hashing.
Esse problema afeta também o PHP de ser efetivo em outros problemas como:
- Machine Learning e afins
- Visão computacional e processamento de imagens
- Sistemas financeiros ou produtos que o core trabalha com matemática em larga escala
O PHP tem até uma ótima biblioteca de Machine Learning chamada RubixML, mas infelizmente para a maior parte dos casos a lentidão é um impeditivo para de fato implementar os algoritmos em produção e larga escala.
Consertando a array do PHP criando um novo tipo de array especializada em números
Pensando nessa brecha que existia no ecosistema do PHP, decidi encarar o desafio e criei uma extensão para o PHP em C que cria uma nova array chamada NDArray.
use \NDArray as nd;
$a = nd::array([[1, 2], [3, 4]]);
print_r($a);
[[1, 2]
[3, 4]]
No código acima a gente criou uma NDArray através de uma array do PHP. Agora podemos tratar $a
como um array normal. Você pode usar foreach e acessar as linhas e colunas usando o índice que você usaria em um array do PHP ($a[1]
).
Porém, esse é um array especial que te oferece uma lista considerável de operações matemáticas especializadas. Por exemplo, se eu quiser multiplicar todos os valores de $a por 2, basta eu escrever:
$b = $a * 2;
print_r($b)'
[[2, 4]
[6, 8]]
Não só é uma forma mais prática de fazer isso (ao invés de um bando de foreachs) como é em média 10 vezes mais rápido que a mesma coisa usando somente o PHP.
Existem dezenas de operações de estatística, lógica, aritmética, manipulação, álgebra linear e afins. Se você quiser o produto da matriz $a
pela matriz $b
:
$produto = nd::matmul($a, $b);
A biblioteca foi propositalmente construída para lembrar as funções de mesmo nome do NumPy no Python.
Você pode selecionar colunas e manipular sua array com muito mais facilidade:
$slice = $a->slice([], [1, 2]); // [] significa todas as linhas, [1, 2] significa coluna de 1 a 2 com step 1, ou seja, apenas a segunda coluna
print_r($slice);
[[2]
[4]]
E depois que terminar tudo pode transformar em um array do PHP chamando o método toArray
:
$array_php = $a->toArray();
Suporte para GPU NVIDIA (CUDA)
Construi a biblioteca também para ser compátivel com placas de vídeo NVIDIA. Para realizar operações na GPU, basta executar o código extremamente complexo abaixo:
$a_gpu = $a->gpu();
$b_gpu = $b->gpu();
$a_x_b = $a_gpu * $b_gpu;
No código acima copiamos nossas duas arrays para a GPU e multiplicamos elas. A multiplicação é totalmente executada pela GPU e $a_x_b
também estará na GPU. 95% das operações da biblioteca são compátiveis com a GPU e possuem kernels CUDA customizados.
Pra copiar a array de volta para a CPU, mais um código complexo:
$a = $a_gpu->cpu();
Suporte para imagens GD
Para manipulação de imagens, uma imagem GD é compátivel com a NDArray:
$img = imgcreatefromjpg("test_img.jpg");
$img_array = nd::array($img);
// Operações na imagem aqui...
$img = $img_array->toImage(); // Converte a NDArray para GDImage novamente
Agora temos um tensor de tamanho (3, h, w)
sendo h
a altura da imagem, w
a largura da imagem e 3
os 3 canais RGB.
Reações e o futuro
Acredito que isso poderá influenciar muitas áreas no PHP, algumas pessoas no reddit pareciam quase que aliviadas e isso me deu muito ânimo. Lancei no sabádo e cada estrelinha lá no Github me deu ainda mais ânimo pra continuar.
Estou trabalhando para a release estável e em uma biblioteca de processamento de imagem super rápida e com suporte para GPU utilizando as NDArray. Eu acho que as bibliotecas GD e ImageMagick são cansativas demais, então pensei em criar algo usando essa tecnologia.
O criador da biblioteca RubixML, Andrew Dalpino, entrou em contato comigo e disse que pretende sugerir a comunidade do Rubix que adote a minha extensão como array padrão para a próxima versão. Isso vai ajudar muito no crescimento do ecosistema no PHP e a democratizar o acesso a machine learning que atualmente tem sido comandado pelo Python exatamente por possuir esses recursos e um ecosistema sólido ao redor dele.
Links e Documentação
Lançamento do Preview no Reddit: https://www.reddit.com/r/PHP/comments/156doyf/i_created_a_better_array_for_mathematical/
Site: https://numpower.org
GitHub: https://github.com/NumPower/numpower
DockerHub: https://hub.docker.com/r/numpower/numpower