- PVSM.RU - https://www.pvsm.ru -
Прошло почти 3 недели с момента публикации моего первого поста о коллекциях объектов в PHP [1]. За это время было сделано много работы и получено много опыта, которым я хочу поделиться. Наибольшее количество изменений претерпели карты, большая часть внимания будет уделена именно им.
В этом посте вы увидите:
После публикации моего прошлого поста и окончания его обсуждения, я составил план дальнейшего развития:
Кроме вышеуказанного списка были еще кандидаты, которые не вошли в план текущего релиза:
Разработанные ранее интерфейсы коллекций отреагировали достаточно стабильно на новый план развития. Диаграмма интерфейсов текущей версии выглядит так:
Как видно из диаграммы все интерфейсы коллекций наследуют интерфейс IteratorAggregate, что дает возможность выполнять обход коллекций с помощью foreach(). Проблема с warning, который выскакивает при обработке карт <объект — объект> с помощью foreach() все еще осталась, но по скольку такие ситуации действительно бывают редко, я решил закрыть на это глаза.
Карты представлены реализациями HashMap, HashSet и HashTable. Каждая из данных реализаций имеет собственные специализации для упрощенного использования.
Внутренняя структура карты HashTable построена на основе единственного PHP массива. Это дает возможность быстрой работы по ключу O(1), но плохо влияет на возможность работы по значению, которая выполняется обычным перебором O(n).
Ключи карты HashTable являются уникальными. Значения карты HashTable не являются уникальными, что позволяет ассоциировать одно значение с несколькими ключами.
Карта HashTable поддерживает строки и целые числа для ключей и строки, целые числа и объекты для значений.
Хеширование ключей и значений не выполняется. Методы getKeyHash() и getValueHash() выбрасывают исключение.
Диаграмма классов HashTable. [2]
Внутренняя структура карт HashMap и HashSet построена на основе сети ассоциативных связей. Это дает возможность реализовывать все операции карт с помощью ассоциативных выборок. Сложность работы алгоритмов карт равна O(1), что означает, что время установки/получения объектов не изменяется в зависимости от размера карт.
Ключи карты HashMap являются уникальными. Значения карты HashMap не являются уникальными, что позволяет ассоциировать одно значение с несколькими ключами.
Ключи и значения карты HashSet являются уникальными, что позволяет организовывать хранилище уникальных ассоциаций.
Карты HashMap и HashSet поддерживают строки, целые числа и объекты для ключей и значений.
Диаграмма классов HashMap. [3]
Диаграмма классов HashSet. [4]
Реализация наборов претерпела некоторые косметические изменения и обросла специализациями для упрощенного использования.
Диаграмма классов наборов. [5]
Реализации списков и очередей остались практически без изменений.
Диаграмма классов списков и очередей. [6]
Весь представленный код покрыт PHPUnit [7] тестами.
Это были мои первые Unit тесты и я очень доволен полученным опытом. Данный вид тестирования позволил отловить мне порядка десяти опечаток и одну фундаментальную ошибку, для фиксации которой пришлось менять реализацию.
Хочется отметить три вещи:
Наиболее интересным для меня было то, на сколько производительны полученные коллекции. Реализации наборов и последовательных списков я отбросил в сторону и сосредоточился на тестировании карт.
Объекты тестирования:
Критерии тестирования:
Платформу тестирования детально описывать не буду, так как не вижу в этом особого смысла. Дело в том, что данные тесты не предназначены для определения количественных характеристик, а скорее для демонстрации соотношений. Соотношения результатов тестов практически не менялись ни на разном железе, ни на разных операционных системах.
Результаты тестов в этом посте я снимал со своего ноутбука на Windows Vista 32 bit.
График:
Файлы тестов:
График:
Файлы тестов:
График:
Файлы тестов:
График:
Файлы тестов:
В процессе работы всплыли некоторые интересные факты, которыми хочется поделиться.
Думаю, многим знакома функция spl_object_hash() [30].
Оказывается у нее есть одна, на мой взгляд, не очень хорошая особенность — непостоянство хешей.
Тест непостоянства spl_object_hast():
<?php
$object1 = new stdClass();
$hash1 = spl_object_hash($object1);
unset($object1);
$object2 = new stdClass();
$hash2 = spl_object_hash($object2);
var_dump($hash1);
var_dump($hash2);
var_dump($hash1 === $hash2);
//string '000000004974d03c000000005560c408' (length=32)
//string '000000004974d03c000000005560c408' (length=32)
//boolean true
Что из этого следует и почему данная особенность неприятна?
Для меня и моей работы такое поведение было не подходящим, так как именно на эту функцию я полагался, как на источник быстрого хеширования объектов.
В комментариях к моему прошлому посту пользователь Athari [31] оставил ценный комментарий, часть которого я процитирую:
Зачем для строк вычислять md5? Похапэшные массивы — и так хэштаблицы, не нужно вычислять хэши на двух уровнях. Только память и вычислительные ресурсы впустую расходуются.
В этом действительно большая доля смысла, но все же я решил проверить.
Результаты:
Все тот же пользователь Athari [31] оставил еще один дельный комментарий к прошлому посту:
Вызов serialize забавен. Понимаю, «нужно» все типы поддерживать, но не такой же ценой.
Для воссоздания контекста напомню, что речь шла о том, что нужно было поддерживать массив как тип данных ключей коллекции. Для этого выполнялась его сериализация для приведения к строке, а после хеширование этой строки с помощью md5().
Действительно «дорогая» цена. В текущей реализации я вообще отказался от поддержки массивов в качестве ключей карты.
Причины по которым я отказался от поддержки массивов:
Особенно хочется остановить на пункте «Небезопасно.» Дело в отсутствие обратной связи между данными и хешем (как бы «дорого» мы его не получили). Если клиентский код сохранит ссылку на массив, который мы захешировали, и изменит его, то хеш устареет. Получить информацию о том, что это тот же массив возможности уже не будет. Тоже самое относится и к другим скалярным типам данных.
Есть ли выход? Единственное, что приходит на ум, это использовать массив через объект обертку (ArrayObject, etc...). В таком случае можно хотя бы получить «непостоянный» spl_object_hash().
Пожалуй, самый главный вопрос, на который нужно дать ответ: «Готовы ли Вы заплатить такую цену по производительности за это решение?». Я уверен, что никто не готов. И я в том числе. Когда я создал графики результатов тестирования производительности, я был удивлен. Я ожидал гораздо меньших соотношений по сравнению со стандартными реализациями, хотя прекрасно понимал, что данное решение явно проигрывает в этом аспекте. Я сперва даже пытался найти решение проблем, но, увы, ничего не вышло. Основная проблема заключается в том, что даже вызов метода является «дорогой» операцией в этом случае. Что касается объема потребляемой оперативной памяти, то до сих пор не пойму, на что расходуется столько ресурсов. Единственное полезное новшество — это двустороннее хеширование карты, что дает возможность выбирать из нее по значению в рамках O(1).
В целом, считаю, что данный проект потерпел фиаско, и причиной этому является аспект производительности.
Исходя из моей аналитики подобное решение все же появится со временем. Лучшее место для него — пакет SPL [32]. Может быть это будет и не SPL, а какой-то другой PECL модуль, который со временем войдет в стандартную сборку PHP. В любом случае что-то удовлетворительное или хорошее получится только при грамотной реализации на С. Кстати, если кто-то заинтересовался данным проектом и хочет помочь реализовать его в качестве PECL модуля, буду рад сотрудничеству.
Возможно, просто довольствоваться тем, что имеем на текущий момент и ждать. Ведь даже сейчас все достаточно неплохо.
Лично я решил создать более легковесные реализации, которые просто будут немного помогать в повседневной жизни и не будут чем-то большим, чем есть стандартные PHP массивы. Уже сейчас есть некоторые наброски:
Диаграмма классов:
MixedArray & ObjectArray UML Diagram [33]
Исходные коды:
MixedArray [34]
ObjectArray [35]
Если это кому-то интересно, пишите, я расскажу о идее более детально.
https://github.com/rmk135/Rmk-Framework [36]
Благодарю всех за конструктивные комментарии к прошлому посту. Они были очень полезны. Рад буду услышать Ваше мнение еще раз.
Спасибо, что дочитали до конца и извините за обилие графики, я старался размещать ее только там, где необходимо.
Автор: rmk
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/9301
Ссылки в тексте:
[1] поста о коллекциях объектов в PHP: http://habrahabr.ru/post/144182/
[2] Диаграмма классов HashTable.: http://habrastorage.org/storage2/f67/825/286/f678252861787922e93fd53c4670e3cf.jpg
[3] Диаграмма классов HashMap.: http://habrastorage.org/storage2/77b/baa/f52/77bbaaf52fe754fab7dc0379b566e1c3.jpg
[4] Диаграмма классов HashSet.: http://habrastorage.org/storage2/d84/745/7a4/d847457a4b7aff1bb64ff4799729017a.jpg
[5] Диаграмма классов наборов.: http://habrastorage.org/storage2/b9a/8fe/7a6/b9a8fe7a61521dba7fbea4a287ffe7e4.jpg
[6] Диаграмма классов списков и очередей.: http://habrastorage.org/storage2/22f/811/9d3/22f8119d361d2f621507168d358bcfc1.jpg
[7] PHPUnit: https://github.com/sebastianbergmann/phpunit/
[8] документацию по PHPUnit: http://www.phpunit.de/manual/current/en/index.html
[9] ArrayObject: http://ua.php.net/manual/ru/class.arrayobject.php
[10] Тест обычного PHP массива: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/set/array.php
[11] Тест ArrayObject: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/set/arrayobject.php
[12] Тест HashTable: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/set/hashtable.php
[13] Тест HashSet: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/set/hashset.php
[14] Тест HashMap: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/set/hashmap.php
[15] Тест обычного PHP массива: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/get/array.php
[16] Тест ArrayObject: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/get/arrayobject.php
[17] Тест HashTable: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/get/hashtable.php
[18] Тест HashSet: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/get/hashset.php
[19] Тест HashMap: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/get/hashmap.php
[20] Тест обычного PHP массива: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/keyof/array.php
[21] Тест ArrayObject: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/keyof/arrayobject.php
[22] Тест HashTable: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/keyof/hashtable.php
[23] Тест HashSet: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/keyof/hashset.php
[24] Тест HashMap: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/keyof/hashmap.php
[25] Тест обычного PHP массива: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/memory/array.php
[26] Тест ArrayObject: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/memory/arrayobject.php
[27] Тест HashTable: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/memory/hashtable.php
[28] Тест HashSet: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/memory/hashset.php
[29] Тест HashMap: https://github.com/rmk135/Rmk-Framework/blob/master/www/tests/map/memory/hashmap.php
[30] spl_object_hash(): http://ua.php.net/manual/ru/function.spl-object-hash.php
[31] Athari: http://habrahabr.ru/users/athari/
[32] пакет SPL: http://ua.php.net/manual/ru/book.spl.php
[33] MixedArray & ObjectArray UML Diagram: http://habrastorage.org/storage2/891/af1/2ad/891af12ad5f61928dcb69a881fd46973.jpg
[34] MixedArray: https://github.com/rmk135/Rmk-Framework/blob/master/library/Rmk/Util/MixedArray.php
[35] ObjectArray: https://github.com/rmk135/Rmk-Framework/blob/master/library/Rmk/Util/ObjectArray.php
[36] https://github.com/rmk135/Rmk-Framework: https://github.com/rmk135/Rmk-Framework
Нажмите здесь для печати.