【问题标题】:fputs slowly writing to diskfputs 缓慢写入磁盘
【发布时间】:2021-11-06 22:25:52
【问题描述】:

我有一个将 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 内存 32GB 交换 2核,2.5GHZ

当文件很大时,(3GB、4GB)写入过程很慢,(每 2 或 3 秒 1MB)。

php.ini 或 apache 配置中是否有任何设置可以控制这个 fputs/fwrite 函数?

我在 php.ini 中看到了 output_buffer 设置(当前设置为 4096),但我怀疑它有什么关系。

谢谢!

【问题讨论】:

  • 可能与问题无关,但您应该首先在这里使用php.net/manual/en/function.fputcsv.php,而不是编写自己的转义逻辑...
  • (或者它甚至可能是相关的,如果你正在做的正则表达式和替换的东西实际上是大部分时间消耗的东西。你有没有以某种方式验证它实际上是写入速度本身,这么慢?)
  • 是的,因为我可以监控文件写入过程,而且速度很慢,我假设一旦文件出现在我放置它的文件夹中,它就已经在 fputs 行并且已经通过了所有前面的逻辑在函数中

标签: php performance fwrite fputs


【解决方案1】:

不要使用.= 追加一行。使用数组,将值添加到数组中,然后内爆数组。您现在正在用不断丢弃的字符串填充您的记忆。 每次做.=时,旧的字符串都保留在堆栈上,新的空间为新的字符串保留,只有在函数准备好时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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-28
    • 2019-01-09
    • 2014-04-29
    • 2016-08-23
    • 1970-01-01
    • 2010-09-15
    • 2010-09-10
    • 1970-01-01
    相关资源
    最近更新 更多