[英]PHP Phar creation slow on a powerful PC, how to speed up (loading/reading ~3000 files)?
我正在尝试使用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();
尝试使用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);
}
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
在我非常慢的ubuntu机器上花费<5秒。
我建议挖掘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
我希望这能帮助你加快你的行动。
你知道,我已经意识到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/');
我建议使用Preloader将所有文件连接到一个文件中,然后将该单个文件添加到phar中。
我知道你说从字符串添加文件名并没有提高性能,但是加载文件名的另一种方法可能会提高性能以及使用字符串中的文件名。 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的过滤器排除缓存或日志文件,如果它是一个导致长滞后的大文件。 查看作曲家链接,了解其实现方式的完整详细信息。
如何使用类而不是将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传递给函数,你将避免“复制”对象。 这种方式就像指针一样。
你可以创建线程并减少总时间,当然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个线程并不是一个好主意。 您可能需要创建良好的线程工作者逻辑。
尝试像composer那样禁用GC
gc_disable();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.