[英]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.