假设一个活动将有10,000到30,000个文件--每个文件大约4kb --应该写到磁盘上。
和,将有两个运动同时运行。最多10个,
目前,我采用的是通常的方式:file_put_contents。
它完成了工作,但是速度慢,它的php进程占用了100%的cpu。
fopen, fwrite, fclose,嗯,结果和file_put_contents相似。
我尝试过一些异步的东西,比如php eio和swoole。
它速度更快,但在一段时间后会产生“太多打开的文件”。
php -r 'echo exec("ulimit -n");'结果为800000。
任何帮助都将不胜感激!
这有点尴尬..。你们是对的,瓶颈在于它是如何生成文件内容的.
发布于 2016-09-16 06:57:38
在阅读了您的描述之后,我知道您正在编写许多文件,每个文件都相当小。PHP通常的工作方式(至少在Apache服务器中是如此),每个文件系统访问都需要开销:为每个文件打开和维护一个文件指针和缓冲区。由于这里没有需要检查的代码示例,所以很难看出效率低下的地方。
但是,对file_put_contents()文件使用300,000+文件的效率似乎略低于直接使用fopen()和fwrite()或fflush(),然后在完成时使用fclose()。我的意思是,基于上file_put_contents()注释中的一位同事所做的基准测试,在处理如此小的文件大小时,似乎有一个很好的机会来使用数据库而不是平面文件(我相信您以前也有过这种情况)。数据库,无论是mySQL还是PostgreSQL,都是高度优化的,可以同时访问许多记录,并且可以以文件系统访问永远不可能的方式在内部平衡CPU工作负载(记录中的二进制数据也是可能的)。除非您需要直接从服务器硬盘访问实际文件,否则数据库可以通过允许PHP通过web (即使用header()函数)将单个记录作为文件数据返回来模拟多个文件。同样,我假设这个PHP是作为web接口在服务器上运行的。
总的来说,我正在阅读的内容表明,除了文件系统访问之外,其他地方可能存在效率低下的问题。文件内容是如何生成的?操作系统如何处理文件访问?是否涉及压缩或加密?这些是图像还是文本数据?操作系统是写到一个硬盘驱动器,一个软件RAID阵列,还是其他布局?这些都是我能想到的一些问题,只是浏览一下你的问题。希望我的回答有所帮助。干杯。
发布于 2016-09-17 22:37:35
我假设您不能遵循SomeDude关于使用数据库的非常好的建议,而且您已经执行了可以执行的硬件调优(例如增加缓存、增加RAM以避免交换冲击、购买SSD驱动器)。
我会尝试将文件生成卸载到不同的进程。
例如,您可以安装Redis并将文件内容存储到keystore中,这非常快速。然后,一个不同的并行进程可以从keystore中提取数据,删除数据并写入磁盘文件。
这将从主PHP进程中删除所有磁盘I/O,并允许您监视待办事项(仍有多少键盘未刷新:理想情况为零),并集中注意内容生成中的瓶颈。你可能需要额外的内存。
另一方面,这与写入RAM磁盘并没有太大的不同。您还可以将数据输出到RAM磁盘,它可能会更快:
# As root
mkdir /mnt/ramdisk
mount -t tmpfs -o size=512m tmpfs /mnt/ramdisk
mkdir /mnt/ramdisk/temp
mkdir /mnt/ramdisk/ready
# Change ownership and permissions as appropriate在PHP中:
$fp = fopen("/mnt/ramdisk/temp/{$file}", "w");
fwrite($fp, $data);
fclose($fp);
rename("/mnt/ramdisk/temp/{$file}", "/mnt/ramdisk/ready/{$file}");然后有一个不同的过程(crontab )?还是继续运行守护进程?)将文件从RAM磁盘的“就绪”目录移动到磁盘,然后删除RAM就绪文件。
文件系统
创建文件所需的时间取决于目录中的文件数量,以及依赖于文件系统的各种依赖函数。ext4、ext3、zfs、btrfs等将表现出不同的行为。具体来说,如果文件数量超过某个数量,您可能会经历显著的减速。
因此,您可能需要尝试计时在一个目录中创建大量示例文件,并查看这一次随着数量的增加而增长的情况。请记住,访问不同目录将导致性能损失,因此再次不建议使用非常多的子目录。
<?php
$payload = str_repeat("Squeamish ossifrage. \n", 253);
$time = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$fp = fopen("file-{$i}.txt", "w");
fwrite($fp, $payload);
fclose($fp);
}
$time = microtime(true) - $time;
for ($i = 0; $i < 10000; $i++) {
unlink("file-{$i}.txt");
}
print "Elapsed time: {$time} s\n";在我的系统上创建10000个文件需要0.42秒,但是创建100000个文件(10倍)需要5.9秒,而不是4.2秒。另一方面,在8个单独的目录中创建其中的八分之一(这是我发现的最佳折衷方案)需要6.1秒,所以这是不值得的。
但是,假设创建300000个文件需要25秒而不是17.7秒;将这些文件划分到10个目录中可能需要22秒钟,从而使目录拆分成为有价值的。
并行处理:R策略
TL;DR在我的系统上工作得不太好,尽管你的里程可能会改变。如果要执行的操作是冗长的(这里没有),并且与主进程绑定不同,那么如果不产生太多的线程,那么将每个线程卸载到不同的线程是有利的。
您需要安装pcntl函数。
$payload = str_repeat("Squeamish ossifrage. \n", 253);
$time = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$pid = pcntl_fork();
switch ($pid) {
case 0:
// Parallel execution.
$fp = fopen("file-{$i}.txt", "w");
fwrite($fp, $payload);
fclose($fp);
exit();
case -1:
echo 'Could not fork Process.';
exit();
default:
break;
}
}
$time = microtime(true) - $time;
print "Elapsed time: {$time} s\n";( R策略这个花哨的名字取自生物学)。
在这个例子中,如果与每个孩子需要做的事情相比,产卵时间是灾难性的。因此,整个处理时间急剧上升。有了更复杂的孩子,事情会变得更好,但你必须小心,不要把剧本变成分叉炸弹。
如果可能的话,一种可能的方法是将要创建的文件分成10%的块。然后,每个子使用chdir()更改其工作目录,并在不同的目录中创建其文件。这将消除在不同子目录(每个子目录写入其当前目录)中写入文件的代价,同时从编写较少的文件中获益。在这种情况下,对于非常轻量级的子级操作和I/O绑定操作,这个策略也是不值得的(我得到了双倍的执行时间)。
并行处理:K策略
TL;博士这是更复杂,但工作良好.在我的系统里。您的里程可能会不同的。当r策略涉及到大量的触发和遗忘线程时,K策略需要一个有限的(可能是一个)被精心培育的孩子。在这里,我们将所有文件的创建卸载到一个并行线程中,并通过套接字与其通信。
$payload = str_repeat("Squeamish ossifrage. \n", 253);
$sockets = array();
$domain = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? AF_INET : AF_UNIX);
if (socket_create_pair($domain, SOCK_STREAM, 0, $sockets) === false) {
echo "socket_create_pair failed. Reason: ".socket_strerror(socket_last_error());
}
$pid = pcntl_fork();
if ($pid == -1) {
echo 'Could not fork Process.';
} elseif ($pid) {
/*parent*/
socket_close($sockets[0]);
} else {
/*child*/
socket_close($sockets[1]);
for (;;) {
$cmd = trim(socket_read($sockets[0], 5, PHP_BINARY_READ));
if (false === $cmd) {
die("ERROR\n");
}
if ('QUIT' === $cmd) {
socket_write($sockets[0], "OK", 2);
socket_close($sockets[0]);
exit(0);
}
if ('FILE' === $cmd) {
$file = trim(socket_read($sockets[0], 20, PHP_BINARY_READ));
$len = trim(socket_read($sockets[0], 8, PHP_BINARY_READ));
$data = socket_read($sockets[0], $len, PHP_BINARY_READ);
$fp = fopen($file, "w");
fwrite($fp, $data);
fclose($fp);
continue;
}
die("UNKNOWN COMMAND: {$cmd}");
}
}
$time = microtime(true);
for ($i = 0; $i < 100000; $i++) {
socket_write($sockets[1], sprintf("FILE %20.20s%08.08s", "file-{$i}.txt", strlen($payload)));
socket_write($sockets[1], $payload, strlen($payload));
//$fp = fopen("file-{$i}.txt", "w");
//fwrite($fp, $payload);
//fclose($fp);
}
$time = microtime(true) - $time;
print "Elapsed time: {$time} s\n";
socket_write($sockets[1], "QUIT\n", 5);
$ok = socket_read($sockets[1], 2, PHP_BINARY_READ);
socket_close($sockets[1]);--这在很大程度上依赖于系统配置。例如,在单处理器、单核、非线程CPU上,这是疯狂的--至少是运行时的两倍,但更有可能的是,运行速度将从3倍提高到10倍。
所以,这绝对不是拉皮条的方式,在旧的系统上运行。
在现代多线程CPU上,假设主内容创建循环是CPU绑定的,您可能会遇到相反的情况--脚本的运行速度可能会快十倍。
在我的系统中,上面的“分叉”解决方案运行速度比快三倍。我期待更多,但你在这里。
当然,性能是否值得增加复杂性和维护,仍有待评估。
坏消息
在上面进行实验时,我得出的结论是,在Linux中合理配置和性能良好的机器上创建文件是快速,因此不仅很难获得更多的性能,而且如果您正在经历缓慢,很可能与文件无关。尝试更详细地介绍如何创建该内容。
发布于 2016-09-19 17:53:56
的主要思想是减少文件。例:1,000个文件可以添加到100个文件中,每个文件包含10个文件,并用be解析,您在写时可以得到5倍的速度,在read+parse上可以得到14倍的速度。
通过优化file_put_contents和fwrite,您将不会获得超过1.x的速度。这个解决方案对于读/写是有用的。其他解决方案可能是mysql或其他db。
在我的计算机上用一个小字符串创建30k文件需要96.38秒,在一个文件中追加30k次相同的字符串需要0.075秒
我可以为您提供一个不寻常的解决方案,当您可以使用它的次数较少的file_put_contents函数。下面我向您展示一个简单的代码,以了解它是如何工作的。
$start = microtime(true);
$str = "Aaaaaaaaaaaaaaaaaaaaaaaaa";
if( !file_exists("test/") ) mkdir("test/");
foreach( range(1,1000) as $i ) {
file_put_contents("test/".$i.".txt",$str);
}
$end = microtime(true);
echo "elapsed_file_put_contents_1: ".substr(($end - $start),0,5)." sec\n";
$start = microtime(true);
$out = '';
foreach( range(1,1000) as $i ) {
$out .= $str;
}
file_put_contents("out.txt",$out);
$end = microtime(true);
echo "elapsed_file_put_contents_2: ".substr(($end - $start),0,5)." sec\n";这是一个包含1000个文件和运行时间的完整示例。
with 1000 files writing file_put_contens: elapsed: 194.4 sec writing file_put_contens APPNED :elapsed: 37.83 sec ( 5x faster ) ............ reading file_put_contens elapsed: 2.401 sec reading append elapsed: 0.170 sec ( 14x faster )
$start = microtime(true);
$allow_argvs = array("gen_all","gen_few","read_all","read_few");
$arg = isset($argv[1]) ? $argv[1] : die("php ".$argv[0]." gen_all ( ".implode(", ",$allow_argvs).")");
if( !in_array($arg,$allow_argvs) ) {
die("php ".$argv[0]." gen_all ( ".implode(", ",$allow_argvs).")");
}
if( $arg=='gen_all' ) {
$dir_campain_all_files = "campain_all_files/";
if( !file_exists($dir_campain_all_files) ) die("\nFolder ".$dir_campain_all_files." not exist!\n");
$exists_campaings = false;
foreach( range(1,10) as $i ) { if( file_exists($dir_campain_all_files.$i) ) { $exists_campaings = true; } }
if( $exists_campaings ) {
die("\nDelete manualy all subfolders from ".$dir_campain_all_files." !\n");
}
build_campain_dirs($dir_campain_all_files);
// foreach in campaigns
foreach( range(1,10) as $i ) {
$campain_dir = $dir_campain_all_files.$i."/";
$nr_of_files = 1000;
foreach( range(1,$nr_of_files) as $f ) {
$file_name = $f.".txt";
$data_file = generateRandomString(4*1024);
$dir_file_name = $campain_dir.$file_name;
file_put_contents($dir_file_name,$data_file);
}
echo "campaing #".$i." done! ( ".$nr_of_files." files writen ).\n";
}
}
if( $arg=='gen_few' ) {
$delim_file = "###FILE###";
$delim_contents = "@@@FILE@@@";
$dir_campain = "campain_few_files/";
if( !file_exists($dir_campain) ) die("\nFolder ".$dir_campain_all_files." not exist!\n");
$exists_campaings = false;
foreach( range(1,10) as $i ) { if( file_exists($dir_campain.$i) ) { $exists_campaings = true; } }
if( $exists_campaings ) {
die("\nDelete manualy all files from ".$dir_campain." !\n");
}
$amount = 100; // nr_of_files_to_append
$out = ''; // here will be appended
build_campain_dirs($dir_campain);
// foreach in campaigns
foreach( range(1,10) as $i ) {
$campain_dir = $dir_campain.$i."/";
$nr_of_files = 1000;
$cnt_few=1;
foreach( range(1,$nr_of_files) as $f ) {
$file_name = $f.".txt";
$data_file = generateRandomString(4*1024);
$my_file_and_data = $file_name.$delim_file.$data_file;
$out .= $my_file_and_data.$delim_contents;
// append in a new file
if( $f%$amount==0 ) {
$dir_file_name = $campain_dir.$cnt_few.".txt";
file_put_contents($dir_file_name,$out,FILE_APPEND);
$out = '';
$cnt_few++;
}
}
// append remaning files
if( !empty($out) ) {
$dir_file_name = $campain_dir.$cnt_few.".txt";
file_put_contents($dir_file_name,$out,FILE_APPEND);
$out = '';
}
echo "campaing #".$i." done! ( ".$nr_of_files." files writen ).\n";
}
}
if( $arg=='read_all' ) {
$dir_campain = "campain_all_files/";
$exists_campaings = false;
foreach( range(1,10) as $i ) {
if( file_exists($dir_campain.$i) ) {
$exists_campaings = true;
}
}
foreach( range(1,10) as $i ) {
$campain_dir = $dir_campain.$i."/";
$files = getFiles($campain_dir);
foreach( $files as $file ) {
$data = file_get_contents($file);
$substr = substr($data, 100, 5); // read 5 chars after char100
}
echo "campaing #".$i." done! ( ".count($files)." files readed ).\n";
}
}
if( $arg=='read_few' ) {
$dir_campain = "campain_few_files/";
$exists_campaings = false;
foreach( range(1,10) as $i ) {
if( file_exists($dir_campain.$i) ) {
$exists_campaings = true;
}
}
foreach( range(1,10) as $i ) {
$campain_dir = $dir_campain.$i."/";
$files = getFiles($campain_dir);
foreach( $files as $file ) {
$data_temp = file_get_contents($file);
$explode = explode("@@@FILE@@@",$data_temp);
//@mkdir("test/".$i);
foreach( $explode as $exp ) {
$temp_exp = explode("###FILE###",$exp);
if( count($temp_exp)==2 ) {
$file_name = $temp_exp[0];
$file_data = $temp_exp[1];
$substr = substr($file_data, 100, 5); // read 5 chars after char100
//file_put_contents("test/".$i."/".$file_name,$file_data); // test if files are recreated correctly
}
}
//echo $file." has ".strlen($data_temp)." chars!\n";
}
echo "campaing #".$i." done! ( ".count($files)." files readed ).\n";
}
}
$end = microtime(true);
echo "elapsed: ".substr(($end - $start),0,5)." sec\n";
echo "\n\nALL DONE!\n\n";
/*************** FUNCTIONS ******************/
function generateRandomString($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
function build_campain_dirs($dir_campain) {
foreach( range(1,10) as $i ) {
$dir = $dir_campain.$i;
if( !file_exists($dir) ) {
mkdir($dir);
}
}
}
function getFiles($dir) {
$arr = array();
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
$arr[] = $dir.$file;
}
}
closedir($handle);
}
return $arr;
} https://stackoverflow.com/questions/39436975
复制相似问题