fputs 缓慢写入磁盘
fputs slowly writing to disk
我有一个 php 脚本,可以将 csv 文件写入磁盘,这是函数:
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内存
32GB 交换空间
2 核,2.5GHZ
当文件很大时,(3GB,4GB)写入过程非常慢,(每2或3秒1MB)。
php.ini 或 apache 配置中是否有控制此 fputs/fwrite 功能的设置?
我在 php.ini 中看到了一个 output_buffer 设置(当前设置为 4096),但我怀疑它与此无关。
谢谢!
不要使用 .=
来追加一行。使用数组,将值添加到数组,然后内爆数组。你现在正在用不断丢弃的字符串填满你的记忆。
每次做 .=
旧字符串保留在栈中,而 new space 保留给新字符串,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;
}
我有一个 php 脚本,可以将 csv 文件写入磁盘,这是函数:
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内存 32GB 交换空间 2 核,2.5GHZ
当文件很大时,(3GB,4GB)写入过程非常慢,(每2或3秒1MB)。
php.ini 或 apache 配置中是否有控制此 fputs/fwrite 功能的设置?
我在 php.ini 中看到了一个 output_buffer 设置(当前设置为 4096),但我怀疑它与此无关。
谢谢!
不要使用 .=
来追加一行。使用数组,将值添加到数组,然后内爆数组。你现在正在用不断丢弃的字符串填满你的记忆。
每次做 .=
旧字符串保留在栈中,而 new space 保留给新字符串,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;
}