Номер телефона

Последнее обновление:

Способы чтения файлов в РНР: как быстрее?

Язык РНР дает несколько возможностей для чтения файлов. Их можно читать целиком, построчно и даже посимвольно (побайтово). Посмотрим, насколько эффективным является каждый из наиболее применяемых способов и стоит ли его применять. Для проверки используем файл достаточно большого объема - типичный видеоролик формата MP4 размером 348 МБ. И этот файл мы сейчас попробуем читать различными способами - при помощи доступных средств языка РНР. Если конкретно - то РНР 5.3. Конечно, в более свежих версиях РНР, начиная с РНР 7, имеются некоторые дополнительные средства, их мы пока касаться не будем. Ибо ничего особенно нового, на самом деле, разработчиками РНР не предложено.

Вот программный код, при помощи которого будем проводить тестирование:

Язык РНР дает несколько возможностей для чтения файлов. Их можно читать целиком, построчно и даже посимвольно (побайтово). Посмотрим, насколько эффективным является каждый из наиболее применяемых способов и стоит ли его применять. Для проверки используем файл достаточно большого объема - типичный видеоролик формата MP4 размером 348 МБ. И этот файл мы сейчас попробуем читать различными способами - при помощи доступных средств языка РНР. Если конкретно - то РНР 5.3. Конечно, в более свежих версиях РНР, начиная с РНР 7, имеются некоторые дополнительные средства, их мы пока касаться не будем. Ибо ничего особенно нового, на самом деле, разработчиками РНР не предложено.

Программный код

Вот программный код, при помощи которого будем проводить тестирование:

mb_internal_encoding('cp-1251');
## Функция перевода строк из кодировки Windows-1251 в UTF-8 и обратно
setlocale(LC_ALL, "ru_RU.CP1251");
// Стандартная кодировка документа
define('Encoding', 'windows-1251');
header('Content-Type: text/html; charset=utf-8');
set_time_limit(1000);
ini_set('max_execution_time',1000);

$start = microtime(true);

$i = 0;
$len = 0;
$file = new SplFileObject("videoplayback_.mp4");
while (!$file->eof()) {
   $str = $file->fgets();
   $len += strlen($str);
   $i++;
}
$finish1 = microtime(true) - $start;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ, имеющий '. $i. ' строк, прочитан построчно при помощи технологии SplFileObject (функция fgets) за время '.  round($finish1, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/><br>';

$i = 0;
$len = 0;
$fd = fopen('videoplayback_.mp4', 'r');
while (!feof($fd)){
   $data_str = fread($fd, 1000);
   $len += strlen($data_str);
   $i++;
}
fclose($fd);

$finish2 = microtime(true) - $start - $finish1;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ прочитан при помощи fread по '. 1000 . ' байт за 1 итерацию за время '.  round($finish2, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';

$i = 0;
$len = 0;
$fd = fopen('videoplayback_.mp4', 'r');
while (!feof($fd)){
   $data_str = fread($fd, 100);
   $len += strlen($data_str);
   $i++;
}
fclose($fd);

$finish3 = microtime(true) - $start - $finish1 - $finish2;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ прочитан при помощи fread по '. 100 . ' байт за 1 итерацию за время '.  round($finish3, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';


$i=0;
$len = 0;
$file = new SplFileObject("videoplayback_.mp4");
while (!$file->eof()) {
   $str = $file->fgetcsv();
   $len += strlen(implode('', $str));
   $i++;
}
$finish4 = microtime(true) - $start - $finish1 - $finish2 - $finish3;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ, имеющий '. $i. ' строк, прочитан построчно при помощи технологии SplFileObject (функция fgetcsv) за время '.  round($finish4, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';

$i = 0;
$len = 0;
$fd = fopen('videoplayback_.mp4', 'r');
while (($str = fgetcsv($fd, null , PHP_EOL)) !== false){
   $len += strlen(implode('', $str));
   $i++;
}
fclose($fd);
$finish5 = microtime(true) - $start - $finish1 - $finish2 - $finish3 - $finish4;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ, имеющий '. $i. ' строк, прочитан построчно при помощи функции fgetcsv за время '.  round($finish5, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';


$i = 0;
$len = 0;
$fd = fopen('videoplayback_.mp4', 'r');
while (!feof($fd)){
   $data_str = fgets($fd);
   $len += strlen($data_str);
   $i++;
}
fclose($fd);

$finish6 = microtime(true) - $start - $finish1 - $finish2 - $finish3 - $finish4 - $finish5;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ прочитан при помощи fgets за время '.  round($finish6, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';


$str = file_get_contents('videoplayback_.mp4');
$str_Arr = explode('\n', $str);
$str = implode('', $str_Arr);
$finish7 = microtime(true) - $start - $finish1 - $finish2 - $finish3 - $finish4 - $finish5 - $finish6;
echo 'Файл videoplayback_.mp4 объемом '. (filesize('videoplayback_.mp4')/1E6). ' МБ прочитан при помощи функции file_get_contents за время '.  round($finish7, 4). ' секунд.<br>';
echo '<br>Пиковое потребление оперативной памяти: <span style="font-weight: bold">'.(memory_get_peak_usage(true)/1000000).' МБ</span><br/>';

Это программный код читает весь файл построчно, по сколько-то байтов или целиком, затем определеяет длину считанного объема данных. По окончании чтения - определяется время, затраченное на чтение всего файла. И так - для каждого из способов.

Результаты тестирования

Тестирование проводилось в операционной системе Windows 7, виртуальный сервер Denwer (да, то самый старый-добрый и безотказный). И вот что получается:

Файл videoplayback_.mp4 объемом 364.972865 МБ, имеющий 1461131 строк, прочитан построчно при помощи технологии SplFileObject (функция fgets) за время 4.7015 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ

Файл videoplayback_.mp4 объемом 364.972865 МБ прочитан при помощи fread по 1000 байт за 1 итерацию за время 1.1091 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ
Файл videoplayback_.mp4 объемом 364.972865 МБ прочитан при помощи fread по 100 байт за 1 итерацию за время 8.4532 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ
Файл videoplayback_.mp4 объемом 364.972865 МБ, имеющий 1449400 строк, прочитан построчно при помощи технологии SplFileObject (функция fgetcsv) за время 59.2317 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ
Файл videoplayback_.mp4 объемом 364.972865 МБ, имеющий 1450027 строк, прочитан построчно при помощи функции fgetcsv за время 61.61 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ

Пиковое потребление оперативной памяти: 0.524288 МБ Файл videoplayback_.mp4 объемом 364.972865 МБ прочитан при помощи fgets за время 3.5766 секунд.

Пиковое потребление оперативной памяти: 0.524288 МБ
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 364981057 bytes) in .../read_file.php on line 89

Выводы по результатам тестирования способов чтения файлов в РНР

Как правило, файл требуется читать либо построчно, либо целиком. Так вот что можно сказать в отношении возможностей языка PHP:

  1. Самый быстрый способ построчного чтения при неизвестной длине строки - это использование функции fgets (или непосредственно, или из класса SplFileObject). Любопытно, что несмотря на то, что даже если использовать функцию, являющуюсяся членом класса, т.е. являющуюся определенной абстракцией, тем не менее, она позволяет читать почти гигабайтный файл менее чем за 5 секунд. Т.е. понятно, что она работает медленнее (примерно на 1 секунду или на 20%), чем "чистая" fgets(), так как класс - есть класс. Увлечение ООП - оно, может, и удобно, да вот только неплохо увеличивает время выполнения скриптов. Но тем не менее. Так что рекомендуем присмотреться к именно этой функции, кому нужно быстро обработать большие файлы средствами языка PHP.
  2. Наиболее быстро удалось прочитать файл посредством функции fread() по 1000 байтов - затраченное время в этом случае - всего секунда с небольшим. Если читать по 100 байт - время вырастает почти на порядок, до 8,5 секунд. Конечно, если читать файл побайтово - то время его полного прочтения будет гораздо больше. Т.е. чем меньше объем чтения из файла при каждой итерации, тем больше времени займет чтение файла в целом. Это вызвано тем, что чтение файла с жесткого диска осуществляется наиболее быстро тогда, когда чтение осуществляется поблочно, причем размер блока должен быть кратен размеру буфера, используемого, в том числе, контроллером жесткого диска. Кроме того, здесь могут иметь значение и некоторые другие виды буферов - это зависит от разновидности операционной системы, а также архитектуры компьютера (особенностей процессора, материнской платы). Иными словами, для того, чтобы прочитать файл с жесткого диска наиболее эффективно, следует определить оптимальную величину блока, читаемого в пределах одной итерации цикла чтения. В том числе, это можно сделать и экспериментально - в условиях конкретного сервера, хостинга. Мы здесь не будем углубляться в этот вопрос. Отметим лишь, что оптимальная величина размера блока чтения позволит существенно повысить скорость чтения файла с жесткого диска.
  3. Что касается функций fgetcsv(), то они обе показали отвратительный результат. Время чтения составляло около минуты. Что, конечно, никуда не годится. Вне зависимости, в рамках класса SplFileObject или без него. Правда, с классом чуть побыстрее. Поэтому применять эти функции для мало-мальски больших файлов просто-напросто не следует. По всей видимости, их удел - это небольшие файлы, максимум, где-нибудь до нескольких десятков мегабайт. И не более, иначе о быстрой работе сайта на РНР можно будет "благополучно" позабыть.
  4. что же касается функции file_get_contents(), которая пытается читать файл целиком и затем преобразовать его в массив (построчно), то она и вовсе отказалась работать после того, как было использовано всего 134217728 байт (около 134 МБ) данных из файла. Поэтому очевидно, что эту функцию тоже целесообразно применять на сравнительно небольших файлах, где-нибудь до 100 МБ.
Общий вывод

Таким образом, в целом можно сказать следующее. При работе с небольшими файлами можно, конечно, применять любой из описанных способов - все они будут более-менее приемлемо работать. А вот когда файл имеет объем в сотни мегабайт (и более) - тогда целесообразнее использовать либо функцию fgets() из класса SplFileObject, либо обычную функцию fread(). Правда, последняя неудобна тем, что не позволяет делать эффективное построчное чтение файла, но зато, судя по результатам теста, работает наиболее быстро и без особых затрат памяти.

Что же касается функций fgetcsv(), то их область применения - это сравнительно небольшие файлы. Об этом следует помнить. И если есть риск существенного "разрастания" файлов, то лучше их не применять вообще.


Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2024 Научный консалтинг