Создание PHP Phar медленное на мощном ПК, как ускорить (загрузка/чтение ~ 3000 файлов)?
Я пытаюсь упаковать свое веб-приложение (проект Symfony 2) с помощью Phar. Я успешно упаковал Silex, микро-каркас со сто файлов в разумные сроки (1-2 минуты).
Проблема заключается в моей машине разработки (i7 4770k, 16GB, SSD Raid 0, проект на RAM-диске), создание архива очень медленно, для каждого файла требуется ~ 1 с. Мне действительно нужно найти способ ускорить работу.
Одной итерации чтения/загрузки файла является медленная. Я добавляю файлы, используя:
function addFile(Phar $phar, SplFileInfo $file)
{
$root = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
$path = strtr(str_replace($root, '', $file->getRealPath()), '\\', '/');
$phar->addFromString($path, file_get_contents($file));
}
$phar = new Phar(/* ... */);
$phar->startBuffering();
// ...
foreach ($files as $file) {
addFile($phar, $file);
}
// ...
$phar->setStub(/* ... */);
$phar->stopBuffering();
Как ускорить чтение/добавление файлов? Может ли моя ОС (Windows) проблема?
EDIT: отключение буферизации не решило проблему. То же добавление скорости из строк:
// This is VERY fast (~ 1 sec to read all 3000+ files)
$strings = array();
foreach ($files as $file) {
$root = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
$path = strtr(str_replace($root, '', $file->getRealPath()), '\\', '/');
$strings[$path] = file_get_contents($file->getRealPath());
}
// This is SLOW
foreach ($strings as $local => $content) {
$phar->addFromString($local, $content);
}
EDIT: полный быстрый & грязный script (может помочь) app/build
:
#!/usr/bin/env php
<?php
set_time_limit(0);
require __DIR__.'/../vendor/autoload.php';
use Symfony\Component\Finder\Finder;
use Symfony\Component\Console\Input\ArgvInput;
$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), 'dev');
function addFile(Phar $phar, SplFileInfo $file)
{
$root = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
$path = strtr(str_replace($root, '', $file->getRealPath()), '\\', '/');
$phar->addFromString($path, file_get_contents($file));
}
$phar = new Phar(__DIR__ . "/../symfony.phar", 0, "symfony.phar");
$phar->startBuffering();
$envexclude = array_diff(array('dev', 'prod', 'test'), array($env));
// App
$app = (new Finder())
->files()
->notPath('/cache/')
->notPath('/logs/')
->notName('build')
->notname('/._('.implode('|', $envexclude).')\.yml$/')
->in(__DIR__);
// Vendor
$vendor = (new Finder())
->files()
->ignoreVCS(true)
->name('*.{php,twig,xlf,xsd,xml}')
->notPath('/tests/i')
->notPath('/docs/i')
->in(__DIR__.'/../vendor');
// Src
$src = (new Finder())
->files()
->notPath('/tests/i')
->in(__DIR__.'/../src');
// Web
$web = (new Finder())
->files()
->in(__DIR__.'/../web')
->notname('/._('.implode('|', $envexclude).')\.php$/');
$all = array_merge(
iterator_to_array($app),
iterator_to_array($src),
iterator_to_array($vendor),
iterator_to_array($web)
);
$c = count($all);
$i = 1;
$strings = array();
foreach ($all as $file) {
addFile($phar, $file);
echo "Done $i/$c\r\n";
$i++;
}
$stub = <<<'STUB'
Phar::webPhar(null, "/web/app_phar.php", null, array(), function ($path) {
return '/web/app_phar.php'.$path;
});
__HALT_COMPILER();
STUB;
$phar->setStub($stub);
$phar->stopBuffering();
Ответы
Ответ 1
Попробуйте использовать Phar:: addFile ($ file) вместо Phar:: addFromString (file_get_contents ($ file))
то есть.
function addFile(Phar $phar, SplFileInfo $file)
{
$root = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
$path = strtr(str_replace($root, '', $file->getRealPath()), '\\', '/');
//$phar->addFromString($path, file_get_contents($file));
$phar->addFile($file,$path);
}
Ответ 2
Phar::addFromString()
или Phar:addFromFile()
невероятно медленный.
Как @sectus сказал Phar::buildFromDirectory()
намного быстрее. Но в качестве простой альтернативы с небольшими изменениями в вашем коде вы можете использовать Phar::buildFromIterator()
.
Пример:
$all = $app->append($vendor)->append($src)->append($web);
$phar->buildFromIterator($all, dirname(__DIR__));
вместо:
$all = array_merge(
iterator_to_array($app),
iterator_to_array($src),
iterator_to_array($vendor),
iterator_to_array($web)
);
$c = count($all);
$i = 1;
$strings = array();
foreach ($all as $file) {
addFile($phar, $file);
echo "Done $i/$c\r\n";
$i++;
}
$ time app/build
real 0m4.459s
user 0m2.895s
sys 0m1.108s
принимает < 5 секунд на моей довольно медленной машине ubuntu.
Ответ 3
Я предлагаю копать в php config.
Первая рекомендация - отключить open_basedir, если она включена. Поскольку я понимаю внутренности php, когда вы пытаетесь получить доступ к любому местоположению файла с помощью php, необходимо, чтобы php проверял, соответствует ли расположение файла совпадению с деревом каталогов. Поэтому, если есть много файлов, эта операция будет предварительно сформирована для каждого файла, и это может значительно замедлить процесс. С другой стороны, если open_basedir отключен, проверка никогда не выполняется.
http://www.php.net/manual/en/ini.core.php#ini.open-basedir
Вторые - это проверить realpath_cache_size и realpath_cache_ttl.
Как написано в описании php
Определяет размер кэша realpath, который будет использоваться PHP. Это значение должно быть увеличено в системах, где PHP открывает много файлов, чтобы отразить количество выполняемых файловых операций.
http://www.php.net/manual/en/ini.core.php#ini.realpath-cache-size
Надеюсь, это поможет ускорить работу ur.
Ответ 4
Знаешь, я понял, что Phar::buildFromDirectory
довольно быстро.
$phar->buildFromDirectory('./src/', '/\.php$/');
Но вам нужно написать более сложное регулярное выражение. Но вы можете называть buildFromDirectory
несколько раз с разными аргументами.
Или создайте временную папку и скопируйте все файлы в $all
. Что-то вроде этого
function myCopy($src, $dest)
{
@mkdir(dirname($dest), 0755, true);
copy($src, $dest);
}
foreach ($all as $file)
{
//$phar->addFile($file);
myCopy($file, './tmp/' . $file);
}
$phar->buildFromDirectory('./tmp/');
Ответ 5
Я бы предложил использовать Preloader, чтобы объединить все ваши файлы в один файл, а затем просто добавить этот единственный файл в phar.
Ответ 6
Я знаю, что вы сказали, что добавление имени файла из строки не улучшило производительность, но, возможно, другой способ загрузки имен файлов может повысить производительность наряду с использованием имени файла из строки. Composer довольно быстро, но я никогда не приурочил его. Попробуйте загрузить файлы по группам типов файлов и добавив их как группы отдельно.
Он использует класс из Symfony, который вы не можете или хотите изменить.
use Symfony\Component\Finder\Finder;
$phar = new \Phar(/* ... */);
$phar->setSignatureAlgorithm(\Phar::SHA1);
$phar->startBuffering();
$finder = new Finder();
//add php files with filter
$finder->files()
->ignoreVCS(true)
->name('*.php')
->notName('Compiler.php')
->notName('ClassLoader.php')
->in(__DIR__.'/..')
;
foreach ($finder as $file) {
$this->addFile($phar, $file);
}
$this->addFile($phar, new \SplFileInfo(/* ... */), false);
$finder = new Finder();
$finder->files()
->name('*.json')
->in(__DIR__ . '/../../res')
;
foreach ($finder as $file) {
$this->addFile($phar, $file, false);
}
$this->addFile($phar, new \SplFileInfo(/* ... */), false);
$phar->setStub($this->getStub());
$phar->stopBuffering();
Возможно, вы можете исключить кеш файл или файл журнала с помощью фильтров Finer, если каким-то образом его один большой файл вызывает долгое отставание. Посмотрите на ссылку композитора, чтобы узнать подробности о ее реализации.
Ответ 7
как насчет использования класса вместо передачи phar в func? просто кусок кода для понимания.. или когда-либо слышал о пределе памяти php.ini или других настройках, которые могут замедлить работу.
class XY {
private $phar;
function addFile(SplFileInfo $file)
$root = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR);
$path = strtr(str_replace($root, '', $file->getRealPath()), '\\', '/');
$this->phar->addFromString($path, file_get_contents($file));
}
// other code here
}
Исправьте меня, если я ошибаюсь, но таким образом вместо того, чтобы передавать phar в функцию, вы избежите "копирования" объекта. этот путь похож на указатель.
Ответ 8
вы можете создавать потоки и сокращать общее время, конечно, Symfony должна поддерживать одновременную загрузку. это не лучший ответ на ваш вопрос, но он может значительно уменьшить общее время загрузки.
class Loader extends Thread {
public $phar;
public $file;
public $done;
public function run() {
$self->done = false;
addFile($self->phar, $self->file);
$self->done = true;
}
}
$threads = array();
foreach ($files as $file) {
$t = new Loader();
$t->phar = $phar;
$t->file = $file;
addFile($phar, $file);
$t->start();
$threads[] = $t;
}
while(true){
$finished = true;
foreach ($t as $threads) {
if ($t->done == false){
$finished = false;
sleep(1);
break;
}
}
if ($finished)
break;
}
и, создавая 3000 потоков, не очень хорошая идея. вам может понадобиться создать логику потока нитей.
Ответ 9
Попробуйте отключить GC, например .
gc_disable();