【问题标题】:PHP Phar creation slow on a powerful PC, how to speed up (loading/reading ~3000 files)?PHP Phar 在功能强大的 PC 上创建速度很慢,如何加快速度(加载/读取 ~3000 个文件)?
【发布时间】:2014-07-20 18:25:21
【问题描述】:

我正在尝试使用 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();

【问题讨论】:

  • 你在任务管理器中检查过进程吗? cpu 或 RAM 负载高吗?
  • 听起来你的机器不是瓶颈。看看 Jake 建议的 php 设置。
  • 也看看这个:phing.info/trac/ticket/782
  • 是的,内存限制只是一个限制,而不是分配。除非您的脚本因无法分配更多内存的错误而死,否则增加它不会有任何影响。

标签: php performance symfony phar


【解决方案1】:

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

【讨论】:

【解决方案2】:

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

【讨论】:

    【解决方案3】:

    使用 class 而不是将 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 传递给函数,您将避免“复制”对象。这种方式就像指针一样。

    【讨论】:

    • 这不会改变任何东西,因为 PHP 从版本 5 开始不再复制对象方法参数。php.net/manual/en/language.oop5.references.php .
    • 不仅当你使用参考符号? "&" $a = &$b;那么它是参考,但是当 $a = $b 那么 $a 是 $b 的副本?有时英文解释对我来说很难理解。我已经开始学习 C,这让我很生气
    • 不,你描述的是PHP4方式。看看我提供的链接
    • 是的,我已经检查了文档,但解释对我来说很复杂。但是你的 cmets 足够理解所以谢谢你
    【解决方案4】:

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

    在我相当慢的 ubuntu 机器上花费

    【讨论】:

    • 最佳答案,从 10:39 到 00:10
    【解决方案5】:

    尝试像composer did那样禁用GC

    gc_disable();
    

    【讨论】:

    • gc_disable() 神奇地修复了所有 PHP 代码。没有严重的循环依赖垃圾收集器在 10.000 个对象中启动,并且只有一个 new 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 的过滤器排除缓存或日志文件,如果它是一个导致长时间滞后的大文件。查看 composer 链接,了解其实现方式的完整详细信息。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          我建议挖掘 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

          我希望这将有助于您加快您的操作。

          【讨论】:

          • realpath_cache_size = 10240k 没有帮助。
          猜你喜欢
          • 2010-11-12
          • 1970-01-01
          • 2018-04-24
          • 2015-05-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多