Как растровый вектор обрабатывается быстрее, чем простой вектор? - PullRequest
18 голосов
/ 13 января 2012

Это предположительно быстрее , чем вектор, но я не совсем понимаю, как локальность ссылок должна помочь этому (поскольку вектор по определению является наиболее локально упакованными данными из всех возможных - каждый элемент упакован рядом с последующим элементом, без дополнительного пробела).

Предполагается ли в тесте конкретный шаблон использования или что-то подобное?

Как это возможно?

Ответы [ 6 ]

11 голосов
/ 13 января 2012

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

Обычные векторы быстрее, например, при доступе к элементу данных по определенному индексу. Трудно превзойти прямой поиск по индексируемому массиву. И с точки зрения локальности кэша большие массивы очень хороши, если все, что вы делаете, это последовательно их циклически повторяете.

Однако растровое векторное преобразование будет намного быстрее для других операций (благодаря структурному разделению) - например, создание новой копии с одним измененным элементом без влияния на исходную структуру данных равно O (log32 n) по сравнению с O (n ) для традиционного вектора. Это огромная победа.

Вот отличное видео, которое стоит посмотреть по этой теме, которое включает множество мотивов, по которым вам могут понадобиться подобные структуры на вашем языке: Постоянные структуры данных и управляемые ссылки (доклад Rich Хики).

10 голосов
/ 14 января 2012

В других ответах много хорошего, но nobdy отвечает на ваш вопрос. PersistenVectors быстры только для большого количества случайных поисков по индексу (когда массив большой). "Как это может быть?" Вы можете спросить. «Обычному плоскому массиву нужно только переместить указатель, а PersistentVector нужно пройти несколько шагов».

Ответ - «Расположение кэша».

Кэш всегда получает диапазон из памяти. Если у вас большой массив, он не помещается в кеш. Поэтому, если вы хотите получить элемент x и элемент y, вам необходимо перезагрузить весь кэш. Это потому, что массив всегда последовательный в памяти.

Теперь с PVector, который отличается. Вокруг много маленьких массивов, и JVM очень умна в этом и помещает их в память. Так что для случайного доступа это быстро; если вы пробежите его последовательно, это будет намного медленнее.

Я должен сказать, что я не специалист по аппаратному обеспечению или по поводу того, как JVM управляет локальностью кэша, и я никогда не проверял это самостоятельно; Я просто пересказываю то, что слышал от других людей :)

Редактировать: Микера тоже об этом упоминает.

Редактировать 2: см. Этот доклад о функциональных структурах данных, перейдите к последней части, если вас интересует только вектор. http://www.infoq.com/presentations/Functional-Data-Structures-in-Scala

6 голосов
/ 18 марта 2015

Битовый векторный вектор (он же постоянный вектор) - это структура данных, изобретенная Rich Hickey для Clojure, которая была внедрена в Scala с 2010 года (v 2.8).Это его умная побитовая индексация , которая обеспечивает высокоэффективный доступ и модификацию больших наборов данных.

С Понимание постоянных векторов Clojure :

Изменяемые векторы и списки массивов, как правило, представляют собой просто массивы, которые увеличиваются и уменьшаются при необходимости.Это прекрасно работает, когда вы хотите изменчивость, но это большая проблема, когда вы хотите постоянство.Вы получаете медленные операции модификации, потому что вам придется все время копировать весь массив, и он будет использовать много памяти.Было бы идеально избегать избыточности, насколько это возможно, без потери производительности при поиске значений наряду с быстрыми операциями.Это именно то, что делает постоянный вектор Clojure, и это делается через сбалансированные упорядоченные деревья.

Идея состоит в том, чтобы реализовать структуру, которая похожа на двоичное дерево.Единственное отличие состоит в том, что внутренние узлы в дереве имеют ссылку не более чем на два подузла и не содержат самих элементов.Узлы листа содержат не более двух элементов.Элементы расположены по порядку, что означает, что первый элемент является первым элементом в самом левом листе, а последний элемент является самым правым элементом в крайнем правом листе.На данный момент мы требуем, чтобы все конечные узлы были на одной глубине 2 .В качестве примера рассмотрим дерево ниже: в нем целые числа от 0 до 8, где 0 - первый элемент, а 8 - последний.Число 9 - это размер вектора:

enter image description here

Если бы мы хотели добавить новый элемент в конец этого вектора, и мы оказались в изменчивом миремы вставили бы 9 в самый правый конечный узел, например:

enter image description here

Но вот проблема: мы не можем этого сделать, если хотим быть постоянными,И это, очевидно, не сработало бы, если бы мы хотели обновить элемент!Нам необходимо скопировать всю структуру или хотя бы ее часть.

Чтобы минимизировать копирование при сохранении полного сохранения, мы выполняем копирование пути: Мы копируем все узлы на пути до значения, о котором мы говоримобновить или вставить, и заменить значение новым, когда мы внизу.Результат нескольких вставок показан ниже.Здесь вектор с 7 элементами имеет общую структуру с вектором с 10 элементами:

enter image description here

Узлы розового цвета распределяются между векторами, а коричневыеи синий являются отдельными.Другие векторы, которые не отображаются, могут также делить узлы с этими векторами.


Дополнительная информация

Помимо Понимание постоянных векторов Clojure ,Идеи, лежащие в основе этой структуры данных и ее вариантов использования, также достаточно хорошо объяснены в лекции Дэвида Нолена 2014 года Неизменность, интерактивность и JavaScript , из которой был сделан снимок экрана ниже.Или, если вы действительно хотите глубоко погрузиться в технические детали, см. Также «Идеальные хеш-деревья» Фила Багвелла , на которых была основана первоначальная реализация Clojure Хика.

Persistent bitmap trie

5 голосов
/ 13 января 2012

Что вы подразумеваете под "простым вектором"?Просто плоский набор предметов?Это замечательно, если вы никогда не обновляете его, но если вы когда-либо меняете плоский вектор 1M-элемента, вам придется много копировать;дерево существует, чтобы позволить вам разделить большую часть структуры.

2 голосов
/ 13 января 2012

Краткое объяснение: используется тот факт, что JVM так сильно оптимизирует структуры данных для чтения / записи / копирования массива. Ключевым аспектом IMO является то, что если ваш вектор достигает определенного размера, управление индексом становится узким местом. Здесь в игру вступает очень умный алгоритм из постоянных векторов, в очень больших коллекциях он превосходит стандартный вариант. Таким образом, в основном это функциональная структура данных, которая работала так хорошо, потому что она построена на небольших изменчивых структурах JVM с высокой степенью оптимизации. Для получения дополнительной информации см. Здесь (в конце) http://topsy.com/vimeo.com/28760673

1 голос
/ 13 января 2012

Судя по названию доклада, речь идет о векторах Scala , которые даже близко не соответствуют "самым локально упакованным данным": см. Источник в https://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_1_final/src/library/scala/collection/immutable/Vector.scala.

Ваше определение относится только к Лиспу (насколько я знаю).

...