PHP将一个CSV文件处理成19个较小的CSV文件非常慢

nkoocmlb  于 2023-05-20  发布在  PHP
关注(0)|答案(2)|浏览(145)

我创建了这个php脚本,它需要很长时间才能将CSV文件过滤成19个较小的文件。CSV的链接是在这个谷歌驱动器行
https://drive.google.com/drive/folders/1Bju4kkVgo21xu3IFeyKWNJ1uClTn534J?usp=share_link
我让进程逐行运行以保存内存,但这超过了PHP脚本中的最大计数时间。是否有改进的方法来实现此文件分解?

<?php

@date_default_timezone_set("GMT");

ini_set('memory_limit', '512M');
ini_set('max_execution_time', '300');
ini_set('auto_detect_line_endings', TRUE);
ini_set('display_errors', 1);

$inputFile = '/Users/declanryan/Desktop/UNI3.0/year3/WEB/workshop/assgienment/air-quality-data-2003-2022.csv';
$outputDirectory = 'output';

// create the output directory if it doesn't exist
if (!file_exists($outputDirectory)) {
    mkdir($outputDirectory);
}

$handle = fopen($inputFile, 'r');

if ($handle) {
    $headerRow = null;
    $siteIDs = array();

    while (($data = fgets($handle)) !== false) {
        $row = str_getcsv($data, ";");

        if ($headerRow === null) {
            $headerRow = $row;
            continue; // Skip processing the header row
        }

        $siteID = $row[array_search('SiteID', $headerRow)];
        if (!in_array($siteID, $siteIDs)) {
            $siteIDs[] = $siteID;

            $newCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
            $f = fopen($newCSVFilename, 'w');

            fputs($f, implode(';', $headerRow) . PHP_EOL);
        }

        $currentCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
        $f = fopen($currentCSVFilename, 'a');

        fputs($f, implode(';', $row) . PHP_EOL);

        fclose($f);
    }

    fclose($handle);
}

echo "Done.";
echo "<br>";
echo 'Script took ' . round((microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]), 2) . ' seconds to run.';

?>

它已经花了足够的时间,甚至得到文件处理发生。我本来想把格式改成getcsv,但是我的讲座告诉我这种方法实际上比较慢?
作为对Sammath回答,像这样的东西是否更符合必要性?

@date_default_timezone_set("GMT");

ini_set('memory_limit', '512M');
ini_set('max_execution_time', '300');
ini_set('auto_detect_line_endings', TRUE);
ini_set('display_errors', 1);

$inputFile = '/Users/declanryan/Desktop/UNI3.0/year3/WEB/workshop/assgienment/air-quality-data-2003-2022.csv';
$outputDirectory = 'output';

// create the output directory if it doesn't exist
if (!file_exists($outputDirectory)) {
    mkdir($outputDirectory);
}

$source = fopen($inputFile, 'r');
if (!$source) {
    exit('Unable to open input file.');
}

$headerRow = fgetcsv($source, 0, ';');
if (!$headerRow) {
    exit('Unable to read header row.');
}

$columnIndexes = array_flip($headerRow);
$siteIDColumn = $columnIndexes['SiteID'];

$handles = [];

while (($row = fgetcsv($source, 0, ';')) !== false) {
    $siteID = $row[$siteIDColumn];
    if (!isset($handles[$siteID])) {
        $newCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
        $handles[$siteID] = fopen($newCSVFilename, 'w');
        if (!$handles[$siteID]) {
            exit('Unable to open output file for SiteID: ' . $siteID);
        }
        fputcsv($handles[$siteID], $headerRow, ';');
    }

    fputcsv($handles[$siteID], $row, ';');
}

foreach ($handles as $handle) {
    fclose($handle);
}

fclose($source);

echo "Done.";
echo "<br>";
echo 'Script took ' . round((microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]), 2) . ' seconds to run.';
tktrz96b

tktrz96b1#

接触文件系统有一个不平凡的数量最慢的IO计算机通常做,PHP抽象了很多优化。但是,当你反复打开一个文件,写入非常少量的数据,然后关闭它时,你不仅使这些优化毫无意义,而且还做了你能做的最糟糕的事情:不断刷新磁盘上的微小写入。
对于这样的东西,你应该打开这些句柄 * 一次 *,它可能看起来大致如下:

$source = fopen('somefile.csv', 'r');
$handles = [];

$header = fgetcsv($source, ';');

while( $row = fgetcsv($source) ) {
  $some_id = $row[X];
  if( ! key_exists($some_id, $handles) ) {
    $handles[$some_id] = fopen("foo/$some_id.csv", w);
  }
  fputcsv($handles[$some_id], $row, ';');
}

foreach($handles as $handle) {
  fclose($handle);
}
fclose($source);

此外,fgetcsv()str_getcsv(fgets())在功能上几乎没有区别,但是implode(';', $row)不是一个合适的CSV编码方法,因为它不会执行任何字符串引用/转义等。

tvokkenx

tvokkenx2#

您的执行时间有三个来源:

  • 关闭和重新打开文件是非常昂贵的。
  • 一次写入少量数据(一行)是没有效率的。
  • 这种情况下不需要解析CSV,因为您只需要知道每一行的siteId,并且文件格式已知

例如:

define('BUFFERSIZE', 1048576);

$buffers = [];
$handles = [];

$start  = microtime(true);
$memory = memory_get_peak_usage(true);

$fp     = fopen("air-quality-data-2003-2022.csv", "r");
fgets($fp, 10240);
while(!feof($fp)) {
    $line = fgets($fp, 10240);
    if (empty($line)) {
        break;
    }
    [ , , , , $siteId ] = explode(';', $line);
    if (isset($handles[$siteId])) {
        if (strlen($buffers[$siteId]) > BUFFERSIZE) {
            fwrite($handles[$siteId], $buffers[$siteId]);
            $buffers[$siteId] = '';
        }
    } else {
        $handles[$siteId] = fopen("air-quality-{$siteId}.csv", "w");
        $buffers[$siteId] = '';
    }
    $buffers[$siteId] .= $line;
}
fclose($fp);

foreach ($handles as $siteId => $fp) {
    fwrite($fp, $buffers[$siteId]);
    fclose($fp);
}

print "Time elapsed: " . (microtime(true) - $start) . " seconds, memory = " . (memory_get_peak_usage(true) - $memory) . " bytes \n";

yields(在我的系统上):

Time elapsed: 0.9726489448547 seconds, memory = 20971520 bytes

我用不同的BUFFERSIZE做了一些实验(报告的内存是脚本已经分配的内存之外的内存)。

Buffer = 4096, time elapsed: 1.3162 seconds, memory = 0 bytes
Buffer = 32768, time elapsed: 1.0094 seconds, memory = 0 bytes
Buffer = 131072, time elapsed: 0.9834 seconds, memory = 2097152 bytes
Buffer = 262144, time elapsed: 0.9104 seconds, memory = 4194304 bytes
Buffer = 500000, time elapsed: 0.9812 seconds, memory = 10485760 bytes
Buffer = 400000, time elapsed: 0.9854 seconds, memory = 8388608 bytes
Buffer = 300000, time elapsed: 0.9675 seconds, memory = 6291456 bytes
Buffer = 262144, time elapsed: 1.0102 seconds, memory = 4194304 bytes
Buffer = 262144, time elapsed: 0.9599 seconds, memory = 4194304 bytes

注意可变性(我可能应该重新启动或至少运行sync并在测试之间该高速缓存),以及它不需要太多缓冲来提高速度的事实(在某个点之后,效率将再次开始下降,因为PHP正在努力处理非常大的字符串连接)。缓冲区的实际大小取决于底层文件系统:如果它是缓存支持的,就像我的一样,那么很可能大的BUFFERSIZE不会有太大的改变。

相关问题