- PVSM.RU - https://www.pvsm.ru -
Всем привет.
Время от времени я, как и большинство программистов, изучаю какие-то новые структуры и алгоритмы.
В этот раз мне на глаза попались статью по cache oblivious алгоритмы, то есть такие алгоритмы, которые изначально более оптимизированы для работы с подсистемой кэширования современных процессоров.
Одним из представителей этой группы является Unrolled linked list.
Что же это такое?
Чтобы понять, что же это такое и с чем его едят, предлагаю вспомнить наиболее популярные реализации списков: ArrayList (с некоторым упрощением будем говорить о массивах вместо непосредственно списка) и LinkedList.
Рассмотрим их основные достоинства и недостатки:
ArrayList:
Достоинства
Недостатки
LinkedList:
Достоинства
Недостатки
Так вот UnrolledLinkedList пытается объединить достоинства двух списков и при этом не унаследовать их недостатки.
Рассмотрим устройство этого списка более подробно.
Попросту говоря, UnrolledLinkedList — связный(двусвязный) список массивов длины n. То есть каждая нода содержит в себе не одно значение, а массив значений. При этом массив является достаточно маленьким, чтобы опреции вставки и удаления были очень быстрыми и фрагментация памяти меньше влияла на возможность создания ноды, но достаточно большие, чтобы полностью помещаться в строку кэша, для сокращения количества cache miss. Дополнительно каждая нода хранит в себе количество добаленных в неё элементов. Графически этот список можно представить следующим образом:

Рассмотрим оперции для этого списка:
Вставка в ноду:
В случае наличия свободных ячеек, новый элемент просто добавляется в конец массива. Если массив уже полон, то мы создаём новую ноду за текущей и переносим в неё правую половину массива, создавая тем самым место для вставки новых значений. Новый элемент в таком случае будет вставлен в конец новой ноды. То же можно применить к вставке в середину массива.
Графически эту операцию можно представить следующим образом:

Удаление из ноды:
Операция досточно простая, мы просто удаляем нужный элемент из массива и делаем сдвиг остальных элементов, если это необходимо. Но есть одно но, в случае если количество элементов в массиве уменьшается до n/2, то из соседних ячеек переносятся n/2 элементов. Если после этого какая-то из ячеек оказалась пустой — она удаляется. Эта опреция нужна для редуцирования расхода памяти при постепенной очистке больших списков.
Поиск ноды по индексу:
Как и связном списке поиск начинается с головы иди хвоста, откуда ближе, отличие в том, что итерироваться по массивам нет нужды — необходимо просто суммировать значения полей содержащие количество элементов в ноде. Таким образом поиск нужной ноды происходит существенно быстрее, чем в LinkedList, хотя асимптотика тут попрежнему O(N).
Изначально мне показалось, что половина пустых ячеек в каждой ноде — это слишком расточительно. Однако как показали небольшие экперименты, в среднем уровень заполнения ячеек находится на уровне 70%, и даже с 50% заполнением расход памяти должен быть существенно меньше, чем у связного списка. Более того список позволяет настраивать себя под различные нужды. Я пытался поиграть с коэффициентом заполнения массива, но существенной выгоды не извлёк, поэтому оставил всё как есть.
Говоря про количество cache miss, то при итерации по таком списку его можно оценить как (N/n+1)(n/B), где N — длина списка, n — длина массива ноды, B — длина строки кэша. Такая оценка достаточно близка к оценке для ArrayList.
На момент написания мне также пришла в голову мысль, что подобный список может стать достаточно эффективной реализацией concurrent коллекции с возможностью блокировки только одной ноды. Однако это тема для нового исследования и новой статьи.
Исходники выложены на github [1].
Из себя они представляют достаточно сырой код, так что просьба не сильно пинать ногами за качество. Однако в любом случае буду признателен любой критике.
В исходниках есть некоторые попытки измерить и сравнить различные списки, однако я стесняюсь их представлять в статье, потому что не очень уверен, что все измерения произведены корректно и я не выстрелил себе в ногу. Буду очень рад помощи экспертов в области бенчмаркинга для окончательной расстановки точек над списками!
Спасибо за внимание.
Источники:
blogs.msdn.com/b/devdev/archive/2005/08/22/454887.aspx [2]
en.wikipedia.org/wiki/Unrolled_linked_list [3]
Автор: Falland
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/13464
Ссылки в тексте:
[1] github: https://github.com/Falland/UnrolledList
[2] blogs.msdn.com/b/devdev/archive/2005/08/22/454887.aspx: http://blogs.msdn.com/b/devdev/archive/2005/08/22/454887.aspx
[3] en.wikipedia.org/wiki/Unrolled_linked_list: http://en.wikipedia.org/wiki/Unrolled_linked_list
Нажмите здесь для печати.