Я написал скрипт PHP, который открывает два очень больших файла (> 1 ГБ), каждый из которых содержит 4 столбца. Сценарий выполняет вычисление соответствующих значений каждой строки в каждом файле и записывает результат в третий файл.
Мой метод невероятно медленный. Я использую SplFileObject
читать исходные файлы и перемещать внутренний указатель построчно, как описано в приведенном ниже коде.
Затем он записывает результат, строка за строкой. Однако и расчет, и выписка выполняются очень медленно (скрипт работает медленно, даже если я отключаю запись). Я предполагаю, что мой метод чтения / записи файлов очень неэффективен, и я был бы признателен за советы по оптимизации.
function generate_adjusted($WGFile, $RFile) {
// File Read objects
$WGObj = new SplFileObject($WGFile);
$RObj = new SplFileObject($RFile);
// File write object
$adjHandle = fopen("outputfile.txt", 'w+');
foreach ($WGObj as $line) {
// Line 0: ID1 (int), 1: ID2 (int), 2: NSNPs (int), 3: Relationship (real)
$WGline = explode("\t", $WGObj->current());
// Seek to the same line of second file
$RObj->seek($WGObj->key());
$Rline = explode("\t", $RObj->current());
$A1 = floatval($WGline[2] * $WGline[3]);
$A2 = floatval($Rline[2] * $Rline[3]);
$ANSNP = $WGline[2] - $Rline[2];
$A3 = round(floatval(($A1 - $A2) / $ANSNP), 3);
// Construct the adjusted line
$adjLine = $WGline[0] . "\t" . $WGline[1] . "\t" . $ANSNP . "\t" . $A3 . "\r\n";
fwrite($adjHandle, $adjLine);
}
fclose($adjHandle);
}
generate_adjusted('inputfile1.txt', 'inputfile2.txt');
Первый и лучший совет: отметьте это!
Не принимайте никаких советов, которые вы получаете как окончательный факт (даже мой). Производительность зависит от вашей операционной системы, аппаратного обеспечения и версии PHP.
Следующее должно быть быстрым подходом и напрямую включает в себя микро-тест для вас. Пожалуйста, проверьте это и дайте нам знать.
<?php
$start = microtime(true);
function get_file_handle($file, $mode) {
$h = fopen(__DIR__ . DIRECTORY_SEPARATOR . $file, "{$mode}b");
if (!$h) {
trigger_error("Could not read {$file}.", E_USER_ERROR);
}
// Make sure nobody else is reading or writing to our file.
if (flock($h, LOCK_SH | LOCK_EX) === false) {
trigger_error("Could not acquire lock for {$file}", E_USER_ERROR);
}
return $h;
}
// We only want to read and not write.
$input_handle1 = get_file_handle("input1", "r");
$input_handle2 = get_file_handle("input2", "r");
// We only want to write and not read.
$output_handle = get_file_handle("output", "w");
// Read from both files at the same time the next line.
// NOTE: This only works if lines are always corresponding in both files.
while (($buffer1 = fgets($input_handle1)) !== false && ($buffer2 = fgets($input_handle2)) !== false) {
$buffer1 = explode("\t", $buffer1);
$buffer2 = explode("\t", $buffer2);
// Forget floatval, let PHP do its dynamic casting.
// NOTE: If precision is important use e.g. bcmath!
$a1 = $buffer1[2] * $buffer1[3];
$a2 = $buffer2[2] * $buffer2[3];
$ansnp = $buffer1[2] - $buffer2[2];
$a3 = round(($a1 - $a2) / $ansnp, 3);
if (fwrite($output_handle, "{$buffer1[0]}\t{$buffer1[1]}\t{$ansnp}\t{$a3}\r\n") === false) {
trigger_error("Could not write result to output file.", E_USER_ERROR);
}
}
// Release locks on and close all file handles.
foreach (array($input_handle1, $input_handle2, $output_handle) as $delta => $handle) {
if (flock($handle, LOCK_UN) === false) {
trigger_error("Could not release lock!", E_USER_ERROR);
}
if (fclose($handle) === false) {
trigger_error("Could not close file handle!", E_USER_ERROR);
}
}
echo "Finished processing after " , (microtime(true) - $start) , PHP_EOL;
Конечно, это можно сделать и в оригинальном стиле, за исключением исключений и т. Д.
// Determines how many lines to buffer between each calculation/write.
$lines_to_buffer = 1000;
while (!feof($input_handle1) && !feof($input_handle2)) {
$c1 = $c2 = 0;
// Read lines from first handle, then read files from second handle.
// NOTE: Reading multiple lines from the same file in a row allows us to make best use of the hard disk, if it isn't
// an SSD, since we consecutively read from the same location which yields minimum seeks. But also keep in mind that
// this might not be true if multiple processes are running in parallel, since they might read from different files
// at the same time.
foreach (array(1 => $input_handle1, 2 => $input_handle2) as $i => $handle) {
while (($line = fgets($handle)) !== false) {
${"buffer{$i}"}[] = explode("\t", $line);
// Break if we read enough lines.
if (++${"c{$i}"} === $lines_to_buffer) {
break;
}
}
}
// Validate?
if ($c1 !== $c2) {
trigger_error("Lines from input files differ, aborting.", E_USER_ERROR);
}
for ($i = 0; $i < $lines_to_buffer; ++$i) {
$a1 = $buffer1[$i][2] * $buffer1[$i][3];
$a2 = $buffer2[$i][2] * $buffer2[$i][3];
$ansnp = $buffer1[$i][2] - $buffer2[$i][2];
$a3 = round(($a1 - $a2) / $ansnp, 3);
$result .= "{$buffer1[0]}\t{$buffer1[1]}\t{$ansnp}\t{$a3}\r\n";
}
fwrite($output_handle, $result);
// Reset
$result = $buffer1 = $buffer2 = null;
}
Других решений пока нет …