Как стать автором
Обновить

Комментарии 24

А демо рабочее есть?
В данный момент нет. Раньше было, но сейчас мне его особо негде разместить.
На самом деле создание демо для моего файлового менеджера требует отдельной конфигурации веб-сервера, чтобы люди не могли ничего сломать. В файловом менеджере есть даже эмулятор терминала (ссылок на него в новом дизайне нет), поэтому нужно, по сути, создавать отдельного пользователя с максимально урезанными правами, желательно на отдельной виртуалке :).
Проблема в том, что "." и ".." не всегда идут первыми, у меня есть VPSка на CentOS, так там эти каталоги идут в середине списка. Очень удивлялся, когда оптимизировал получение списка файлов :)

В итоге сделал так:

$parent_dir = 2;
while (($file = readdir($dh)) !== false) {
	if($parent_dir && ($file === '.' || $file === '..'))  {$parent_dir--;continue;}
	// ...
}

В таком случае мы убираем 2 сравнения строк, сразу после того, как нашли «точечные» каталоги.
Забавно… Тогда, видимо, можно написать как-то так, чтобы всё-таки убрать дополнительные операции в цикле:
<?php
$parent = 2;
while (false !== ($f = readdir($dh))) {
    if ($f == '.' || $f == '..') {
        $parent--;
        if ($parent == 0) break;
        continue;
    }
    $files .= "$f/";
}
if (false !== $f) {
    while (false !== ($f = readdir($dh))) $files .= "$f/";
}
Та просто разница уже совсем незначительная, поэтому разделять не стал на 2 цикла. Тестил на 10 млн. цикле.

без условий                              7.78 сек.
$parent && ($f === '.' || $f === '..')   7.88 сек.
$f === '.' || $f === '..'                8.46 сек.
$f == '.' || $f == '..'                  9.73 сек.

Такой код, на самом деле, позволяет открыть папку в 500к файлов, потребляя меньше 32 Мб, причём это с учётом json_encode. Поскольку мы составляем очень простую структуру данных — просто строку, время работы json_encode тоже будет очень небольшим, как и затраты на парсинг со стороны браузера.

Я правильно понимаю, что, если отбросить нюансы, результат работы d_filelist_simple используется в ajax-запросе (и причем практически напрямую, без обработки)?
Да, сервер отдает просто строку с разделителем "/", а также информацию о первых нескольких сотнях файлов, чтобы не делать 2 запроса в начале.
А вы не пробовали сравнивать производительность вашего с решения с типовым решением для таких операций, когда результаты не конкатенируются, а просто по одному пишутся сразу в результирующий поток?

PS Немного арифметики. Имя файла может быть длинной до 255 символов (консервативная оценка). Предполпожим, что у нас однобайтовая кодировка (тоже консервативная оценка). 255*500000=127500000 (bytes) = 121Mb.
О том, чтобы сразу отдавать json, я не подумал, если честно. Основная мысль в накоплении результата в том, чтобы потом это сжать в gzip и отдать клиенту раз в 5 меньше трафика :). С другой стороны, если очень поизвращаться с chunked ответом, наверное можно совместить gzip и отдачу результата клиенту кусочками.

И да, на самом деле объем потребляемой памяти зависит от длины имен файлов. Потрбление памяти приведено для случая, когда средняя длина файла составляет примерно 10 символов
Основная мысль в накоплении результата в том, чтобы потом это сжать в gzip и отдать клиенту раз в 5 меньше трафика

gzip умеет работать с потоковыми данными. Да, это не так эффективно, зато экономится память (если у вас стояла цель ее сэкономить).

Потрбление памяти приведено для случая, когда средняя длина файла составляет примерно 10 символов

Излишне оптимистично.
Излишне оптимистично.

На самом деле, я об этом особо не думал, когда создавал тестовую директорию — я просто назвал файлы «file$i», где $i находится в диапазоне от 0 до 1 000 000.
gzip умеет работать с потоковыми данными. Да, это не так эффективно, зато экономится память (если у вас стояла цель ее сэкономить).

Чисто теоретически — я согласен, можно было бы сделать и так, но пришлось бы поизвращаться :).

Существуют и другие причины, по которым дальше оптимизировать этот код не имеет большого смысла. Я в статье особо об этом не говорил, но даже простой массивчик на 1 000 000 элементов с именами файлов заставляет тот же Safari сжирать пару сотен мегабайт оперативной памяти. То есть, отдать-то, в принципе, можно и больше, только мы начнем упираться в то, что память начнет кончаться на клиенте. Ну и тот грид, который используется для отображения списка файлов, в Safari перестает работать на 1 млн файлов, поэтому я поставил ограничение в 500к файлов при показе.
Чисто теоретически — я согласен, можно было бы сделать и так, но пришлось бы поизвращаться

Возможно, в PHP пришлось бы, не знаком с окружением. В asp.net это делается вообще прозрачно для программиста.

То есть, отдать-то, в принципе, можно и больше, только мы начнем упираться в то, что память начнет кончаться на клиенте.

Вот поэтому и используют пейджинг.
Вот поэтому и используют пейджинг.

А я использовал (об этом есть упоминание в статье). Но для списка файлов эффективно реализовать постраничную навигацию нелегко, вы ведь не можете начать чтение директории с середины. Согласитесь, навигация без страниц, а с обычной прокруткой списка намного приятнее. Если не отдавать клиенту список файлов, то при скроллинге большого списка он будет всё время видеть надпись «загружается...».

Возможно, в PHP пришлось бы, не знаком с окружением. В asp.net это делается вообще прозрачно для программиста.

В PHP это было бы что-то вроде «ob_start('ob_gzhandler', 4096);», где 4096 — размер чанков. Вот только я когда-то читал, что это далеко не везде работает.
Но для списка файлов эффективно реализовать постраничную навигацию нелегко, вы ведь не можете начать чтение директории с середины.

Могу. За счет перераспределения нагрузки с клиента на сервер. Дальше считаем компромисы.

Согласитесь, навигация без страниц, а с обычной прокруткой списка намного приятнее.

В зависимости от (а) задачи и (б) того, во что обошлась эта «обычная прокрутка списка».

Интересность приведенного мной решения заключается в том, что оно очень простое, очень быстрое, потребляет очень мало памяти и решает поставленную мной задачу — сделать возможность открывать папки до 1 млн файлов в моём файловом менеджере.

Ну, насчет миллиона вы сами признались, что перестает работать.
Для сокращения потребления памяти на клиенте можно тоже не делать split('/'), а пройтись по строке, создать массив из индексов начал имен файлов (используя UInt32Array)…
Интересность приведенного мной решения заключается в том, что оно очень простое, очень быстрое, потребляет очень мало памяти и решает поставленную мной задачу — сделать возможность открывать папки до 1 млн файлов в моём файловом менеджере.
Оптимизацией «последних двух байт» можно заниматься очень долго, вот только толку от этого немного. Мой файловый менеджер итак является самым быстрым из всех, что я видел, причём на порядки.
Мда, вынос условий в отдельный цикл — красиво. Экономит 7 миллисекунд на 50К файлов на моей машине.
И по потреблению CPU/память, похоже, что действительно самый оптимальный вариант.
У substr нет выигрыша в скорости, но удвоенный расход памяти, а ls -m через system() и вовсе оказался в 4 раза медленнее.
С кэшированием в мемкешеде, правда, по скорости получается в 6 раз быстрее, 10 мс всего, но это лишняя память (для всех просмотренных каталогов!) плюс для однопользовательского файлменеджера это вообще не вариант, скорее всего.

Но я всё равно такой экономией на спичках заниматься не стал бы =)
Но если б меня эти миллисекунды прям так запарили, то точки на сервере я бы не стал отрезать вовсе, а так и отправил в браузер. Там цикл на порядки меньше и эта проверка погоды уже не сделает, как мне кажется. Но клиентская сторона — единственное, что я не пробовал профилировать, так что могу здесь ошибаться =)

Имена файлов — 32 байта, но 32-битная система, так что расход памяти такой же — 1,6М
Проблема в том, что большинство писателей файлменеджеров вообще не задумываются, о том что может быть ситуации когда в каталоге несколько тысяч файлов. Как раз недавно встретился с ситуацией, когда у Joomla сломалась система кєширования, в итоге на небольшом сайтике с сотней страниц, папка кєша разрослась до 5 ГБ, и более 100 тысяч файлов. Так их даже почистить штатными средствами была большая проблема, пришлось писать скрипт который всё чистит сам.
Блин, во тупняк-то. Так и знал, что где-нибудь накосячу =)
Когда я с самого начала тестировал по сети, выдавал только размер строки, а не её саму. И получил разницу в несколько rps. Что меня удивило и сподвигло на остальные тесты.

А сейчас провел siege нормально, с возвратом всей строки, плюс не в локалке, а через инет.
И получил ожидаемые 0,38 rps, на фоне которых наши 7 миллисекунд потерялись абсолютно.
Что и требовалось доказать: вынос проверки точек за цикл в условиях веб-приложения — экономия на спичках :-P
!@$dh — подобные конструкции меня всегда отвращали в PHP
Этот код писался мной очень давно :). И без @ там можно было бы обойтись, а уж в цикле чтения директории они там точно не нужны. Но тогда мне это казалось хорошей идеей :)
ф-ция d_filelist_extreme содержит 300 строк кода. Не много?
Повсеместное использование eval() или очень запутанная логика в этой функции вас, при этом, не смущают, значит… ;)?
«Евал» там много где есть… начиная с индекса, в последнем вложении и далее по тексту.

Запутанный код, в стиле — «ошибки сложно найти, так как код сложный»

Однако, поскольку сам люблю такое вытворять, чтобы заказчики меня не выгоняли из проектов, и потому, что у меня коллеги на фирме такое творят, что ум за разум заходит (а мне за ними фиксить!), — то мне понравилось.

Кстати интересная штука, поставил, проверил, попробовал.
Единственное, что допиливать надо, ну и не заработала вообще «упрощенная» версия интерфейса.

Но задумка классная, здоровья автору.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории