So I had another think about what you meant and thought of potential solutions for this as I think you may actually have a good point. As step 1 all I did was essentially skip the autoloader.
In a very small application that doesn’t use a database, sessions or anything else but loads ~30 files, I had a ~30% speed increase. There are more caveats than I have the time to go into here (superglobals, shared sessions) and hurdles I haven’t even considered yet but here’s my current implementation:
- Run a PHP script as a server that runs indefinitely. This can have a mysql connection, load all the files, do all the bootstrap work
- Have that server register a socket
- Have a minimalist client script which connects, via sockets to the server and just calls functions on it. This allows the same PHP script instance to serve multiple requests.
Some code:
class SocketServer {
private $sockFile;
private $functions = [];
public function __construct($sockFile = '/tmp/mysock2') {
$this->sockFile = $sockFile;
if (file_exists($this->sockFile)) unlink($this->sockFile);
}
public function addFunction($name, \Closure $function) {
$this->functions[$name] = $function;
}
public function start() {
$this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
if (!$this->socket) throw new \Exception('Could not create socket');
if (!socket_bind($this->socket, $this->scokFile)) throw new \Exception('Could not bind socket ' . $this->host . ':' . $this->port . ' (' . socket_last_error($this->socket) . ')');
if (!socket_listen($this->socket, 3)) throw new \Exception('Could not listen on socket ' . $this->host . ':' . $this->port . ' (' . socket_last_error($this->socket) . ')');
$this->listen();
}
private function listen() {
while ($spawn = socket_accept($this->socket)) {
$message = socket_read($spawn, 2048);
list($function, $data) = explode('#SOCKETMESSAGE#', $message);
if ($function == 'SOCKETCLOSE') $this->stop;
try {
if (isset($this->functions[$function])) $output = $this->functions[$function](unserialize($data));
else $output = 'Invalid socket call';
}
catch (\Exception $e) {
$output = $e->getMessage();
}
if (!socket_write($spawn, $output, strlen($output))) echo socket_last_error($this->socket);
socket_close($spawn);
}
}
public function stop() {
socket_close($this->socket);
die;
}
}
//This was the index.php bootstrap file for my framework
set_time_limit(0);
chdir('../framework');
require_once 'Conf/Core.php';
$conf = new \Config\Core;
foreach ($conf->autoInclude as $file) require_once $file;
//Create the DIC
$dic = new $conf->dic(new $conf->dicConfig);
//Use the DIC to consturct the autoloader
$autoLoader = $dic->create($conf->autoloader);
//Now, instead of constructing the entry point and echoing the output, allow a socket connection to get the output from the entry point
$server = new SocketServer();
$server->addFunction('getoutput', function($data) use ($dic, $conf) {
$entryPoint = $dic->create($conf->entryPoint, [$data['get'], $data['post'], $data['server']]);
return $entryPoint->output();
});
$server->start();
Then the client script looks like this:
class Client {
private $sockFile;
private $socket;
public function __construct($sockFile = '/tmp/mysock2') {
$this->sockFile = $sockFile;
do {
$this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0) or die("Could not create socket\n");
$connected = @socket_connect($this->socket, $this->sockFile);
//If there's no server running, run it.
if (!$connected) {
exec(sprintf("%s > %s 2>&1 & echo $! >> %s", 'php server.php', 'serverlog.txt', 'server.pid'));
sleep(1);
}
} while (!$connected);
}
public function sendMessage($function, $data = null) {
$message = $function . '#SOCKETMESSAGE#' . serialize($data);
socket_write($this->socket, $message, strlen($message));
return socket_read($this->socket, 1024, PHP_BINARY_READ);
}
public function __destruct() {
socket_close($this->socket);
}
}
$client = new Client();
echo $client->sendMessage('getoutput', ['get' => $_GET, 'post' => $_POST, 'server' => $_SERVER]);
All requests that used to go to index.php now go to client.php and it all just falls into place. Clearly there are issues with sessions, database concurrency… oh and if you use TCP rather than UNIX sockets the performance is about 800 times worse than just running everything through the original index.php that was both the client and server.
My framework is set up so that $_GET
, $_POST
and other superglobals are not referenced anywhere but the very top level then passed into the framework entry point… obviously if you’re using superglobals at arbitrary points in the code this method will not be workable, you won’t even be able to overwrite them $_GET = $data['get'];
because requests will interfere with each other and give you very unpredictable results… and if your DIC is backwards like most of them and creates shared instances of objects by default you’ll also be in a world of pain because application state will be stored across each request… if your DIC is sensible, you’ll have shared objects which are actually shared among all requests. Neat.