PHP Phar在功能强大的PC上创建速度慢,如何加速(加载/读取~3000个文件)?

时间:2014-05-30 22:00:52

标签: php performance symfony phar

我正在尝试使用Phar打包我的Web应用程序(Symfony 2项目)。我已经成功地在一个合理的时间内(1-2分钟)打包了Silex,一个包含数百个文件的微框架。

问题在于我的开发机器(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)的问题?

编辑:禁用缓冲无法解决问题。从字符串添加相同的速度:

// 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);
}

编辑:完整的快速和脏脚本(可能有帮助)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();

9 个答案:

答案 0 :(得分:11)

尝试使用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);
}

答案 1 :(得分:9)

Phar::addFromString()Phar:addFromFile()非常慢。 像@sectus said 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

采取&lt;在我非常慢的ubuntu机器上5秒钟。

答案 2 :(得分:4)

我建议挖掘php配置。

首次推荐 - 如果启用了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描述中所述

  

确定PHP使用的实际路径缓存的大小。在PHP打开许多文件的系统上应该增加这个值,以反映执行的文件操作的数量。

http://www.php.net/manual/en/ini.core.php#ini.realpath-cache-size

我希望这可以帮助你加快你的行动。

答案 3 :(得分:3)

你知道,我已经意识到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/');

答案 4 :(得分:1)

我建议使用Preloader将所有文件连接到一个文件中,然后将该单个文件添加到phar中。

答案 5 :(得分:1)

我知道你说从字符串添加文件名并没有提高性能,但是加载文件名的另一种方法可能会提高性能以及使用字符串中的文件名。 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的过滤器排除缓存或日志文件,如果它是一个导致长滞后的大文件。查看作曲家链接,了解有关其实现方式的详细信息。

答案 6 :(得分:0)

使用类而不是将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传递给函数,你将避免&#34;复制&#34;对象。这种方式就像指针一样。

答案 7 :(得分:0)

你可以创建线程并减少总时间,当然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个线程并不是一个好主意。你可能需要创建好的线程工作者逻辑。

答案 8 :(得分:-2)

尝试像composer did

一样禁用GC
gc_disable();