【问题标题】:PHP looping through huge text file is very slow, can you improve?PHP循环遍历巨大的文本文件非常慢,你能改进吗?
【发布时间】:2014-06-27 04:17:16
【问题描述】:

文本文件(实际上是 .dat)中包含的数据如下所示:

LIN*1234*UP*abcde*33*0*EA
LIN*5678*UP*fghij*33*0*EA
LIN*9101*UP*klmno*33*23*EA

文件中实际上有超过 500,000 行这样的行。

这是我现在使用的:

//retrieve file once        
$file = file_get_contents('/data.dat'); 
$file = explode('LIN', $file);

    ...some code

foreach ($list as $item) { //an array containing 10 items
     foreach($file as $line) { //checking if these items are on huge list
         $info = explode('*', $line);
         if ($line[3] == $item[0]) {
             ...do stuff...                     
             break; //stop checking if found
          }
      }         
 }

问题在于它运行得太慢 - 每次迭代大约需要 1.5 秒。我单独确认影响速度的不是“……做事……”。相反,它是在寻找正确的项目。

如何加快速度?谢谢。

【问题讨论】:

  • 如果你想搜索一个 500,000 行的文件 10 次,它可能总是需要很长时间......
  • 是的,虽然从技术上讲,它搜索的是数组而不是文件。我想这里一定有一些数组函数可以提供帮助。
  • 您可以在整个文件上运行正则表达式传递而不将其拆分为数组,这取决于您在...do stuff... 部分中执行的操作以及您是否需要返回行号或其他部分该行等编辑,甚至只是一个简单的strstr() 查找该字符串...
  • 只使用grep 怎么样?将输出写入文件,然后用 PHP 读取?

标签: php performance loops


【解决方案1】:

如果每个项目都在自己的行中,而不是将整个项目加载到内存中,最好使用fgets() 代替:

$f = fopen('text.txt', 'rt');

while (!feof($f)) {
    $line = rtrim(fgets($f), "\r\n");
    $info = explode('*', $line);
    // etc.
}

fclose($f);

PHP 文件流是缓冲的 (~8kB),所以它在性能方面应该不错。

另一段逻辑可以这样重写(而不是多次迭代文件):

if (in_array($info[3], $items)) // look up $info[3] inside the array of 10 things

或者,如果 $items 被适当索引:

if (isset($items[$info[3]])) { ... }

【讨论】:

  • 感谢您的回复,但 fopen 实际上似乎放慢了速度。
  • @user2029890 不确定应用我的答案后你最终得到了什么代码:) 另外,如果性能很重要,你可能应该改用数据库,SQLite 用于实例...
  • @Jack 看看原始发帖人的问题中的这一行,foreach ($list as $item) { //an array containing 10 items 每个项目循环 500,000 个项目 10 次似乎也是一个主要的性能问题。
  • @JakeGould 这就是为什么我建议在内部循环中使用in_array();其实现在想一想,如果$items的keys可以使用的话,if isset($items[$info[3]])可以做得更快。
【解决方案2】:

file_get_contents 将整个文件作为数组加载到内存中,然后您的代码对其进行操作。从the official PHP fgets documentation 改编这个示例代码应该会更好:

$handle = @fopen("test.txt", "r");
if ($handle) {
    while (($buffer = fgets($handle, 4096)) !== false) {
        $file_data = explode('LIN', $buffer);
        foreach($file_data as $line) {
            $info = explode('*', $line);
            $info = array_filter($info);
            if (!empty($info)) {
                echo '<pre>';
                print_r($info);
                echo '</pre>';
            }
        }         
    }
    if (!feof($handle)) {
        echo "Error: unexpected fgets() fail\n";
    }
    fclose($handle);
}

上述代码使用你的数据的输出是:

Array
(
    [1] => 1234
    [2] => UP
    [3] => abcde
    [4] => 33
    [6] => EA

)
Array
(
    [1] => 5678
    [2] => UP
    [3] => fghij
    [4] => 33
    [6] => EA

)
Array
(
    [1] => 9101
    [2] => UP
    [3] => klmno
    [4] => 33
    [5] => 23
    [6] => EA
)

但仍然不清楚您缺少的代码,因为该行声明:

foreach ($list as $item) { //an array containing 10 items

这似乎是另一个真正的瓶颈。

【讨论】:

    【解决方案3】:

    当您执行file_get_contents 时,它会将内容加载到内存中,因此您只能想象该过程可能会占用多少资源。更不用说你有一个嵌套循环,那就是(O)n^2

    如果可能,您可以拆分文件或使用fopenfgetsfclose 逐行读取它们。

    如果我是你,如果我真的需要速度,我会使用另一种语言,例如 C++Go

    【讨论】:

    • 考虑到该进程将主要受磁盘限制,我怀疑另一种语言是否能够挤出更多性能:)
    • @Jack 没错。获取数据并将其放入数据库然后对其采取行动可能是更好的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-19
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多