Подтвердить что ты не робот

Создание 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();
4b9b3361

Ответ 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();