php - fputs 缓慢写入磁盘
问题描述
我有一个将 csv 文件写入磁盘的 php 脚本,这是函数:
function fputcsv_content($fp, $array, $delimiter=",", $eol="\n") {
$line = "";
foreach($array as $value) {
$value = trim($value);
$value = str_replace("\r\n", "\n", $value);
if(preg_match("/[$delimiter\"\n\r]/", $value)) {
$value = '"'.str_replace('"', '""', $value).'"';
}
$line .= $value.$delimiter;
}
$eol = str_replace("\\r", "\r", $eol);
$eol = str_replace("\\n", "\n", $eol);
$line = substr($line, 0, (strlen($delimiter) * -1));
$line .= $eol;
return fputs($fp, $line);
}
服务器是 AWS 实例,CentOS 7 和 PHP 版本是 7.2
服务器规格:4GB RAM 32GB SWAP 2 核,2.5GHZ
当文件很大时,(3GB、4GB)写入过程非常慢,(每 2 或 3 秒 1MB)。
php.ini 或 apache 配置中是否有任何设置可以控制此 fputs/fwrite 功能?
我在 php.ini 中看到了 output_buffer 设置(当前设置为 4096),但我怀疑它有什么关系。
谢谢!
解决方案
不要.=
用来追加一行。使用数组,将值添加到数组中,然后内爆数组。你现在正在用不断丢弃的字符串填充你的记忆。每次做.=
旧的字符串都保留在栈上,新的空间留给新的字符串,GC只有在函数准备好时才会运行。对于 3-4gb 的文件,最终可能是该文件的许多倍,这会导致进程将交换用作额外内存,这很慢。
尝试将其重构为数组方法,看看是否可以通过使用一些内存节省技术来稍微缓解您的问题。
我添加了静态函数变量的使用,因此它们只分配一次,而不是每次迭代,这也节省了一点内存,搁置了 php 可能会或可能不会做的任何优化。
在线查看:https ://ideone.com/dNkxIE
function fputcsv_content($fp, $array, $delimiter=",", $eol="\n")
{
static $find = ["\\r","\\n"];
static $replace = ["\r","\n"];
static $cycles_count = 0;
$cycles_count++;
$array = array_map(function($value) use($delimiter) {
return clean_value($value, $delimiter);
}, $array);
$eol = str_replace($find, $replace, $eol);
$line = implode($delimiter, $array) . $eol;
$return_value = fputs($fp, $line);
/** purposefully free up the ram **/
$line = null;
$eol = null;
$array = null;
/** trigger gc_collect_cycles() every 250th call of this method **/
if($cycles_count % 250 === 0) gc_collect_cycles();
return $return_value;
}
/** Use a second function so the GC can be triggered here
* when it returns the value and all intermediate values are free.
*/
function clean_value($value, $delimeter)
{
/**
* use static values to prevent reassigning the same
* values to the stack over and over
*/
static $regex = [];
static $find = "\r\n";
static $replace = "\n";
static $quote = '"';
if(!isset($regex[$delimeter])) {
$regex[$delimeter] = "/[$delimiter\"\n\r]/";
}
$value = trim($value);
$value = str_replace($find, $replace, $value);
if(preg_match($regex[$delimeter], $value)) {
$value = $quote.str_replace($quote, '""', $value).$quote;
}
return $value;
}
推荐阅读
- python - 在多个处理器上执行时挂起的示例代码
- python-3.x - 添加与另一个数据框比较的 NaN 值
- python - Python Boolean:为什么在评估值和变量中类和类实例之间存在差异?
- python - 我可以在类声明中使用重载运算符吗?
- java - 无法在 Eclipse 控制台上打印结果
- java - 尝试使用 javac 编译 java 源代码:包 R 不存在
- ios - 在 MessageKit 中点击时放大图像视图 - Swift
- python - matplotlib如何将大数据点显示为更小的尺寸
- python-3.x - 一对一关系-尝试在序列化程序“ProfileSerializer”上获取字段“user”的值时如何修复 Got AttributeError
- html - Col 为 100% 会影响消息框大小