Как массивы и хэш-карты имеют постоянное время доступа? - PullRequest
15 голосов
/ 14 января 2011

В частности: с учетом хэша (или индекса массива), как машина получает данные за постоянное время?

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

Пример:

my_array = new array(:size => 20)
my_array[20] = "foo"
my_array[20] # "foo"

Доступ к «foo» в позиции 20 постоянен, потому что мы знаем, в каком ведре находится «foo». Как мы волшебным образом добрались до этого ведра, не пройдя вседругие в пути?Чтобы добраться до дома № 20 в блоке, вам все равно придется пройти мимо других 19 ...

Ответы [ 6 ]

18 голосов
/ 14 января 2011

Как мы волшебным образом добрались до этого ведра, не пропустив всех остальных по пути?

"Мы" вообще не "идем" в ведро.Физическая работа ОЗУ больше похожа на передачу номера сегмента на канал, по которому слушают все сегменты, и тот, чей номер был вызван, отправит вам его содержимое.

Вычисления происходят в ЦП.Теоретически, процессор находится на одинаковом «расстоянии» от всех областей памяти (на практике это не так из-за кэширования, которое может оказать огромное влияние на производительность).

Если вам нужны подробности, прочитайте «Что должен знать каждый программист о памяти» .

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

Затем, чтобы понять, вы должны посмотреть, как организована и доступна память.Возможно, вам придется посмотреть, как работает адресный декодер .Дело в том, что вам НЕ нужно проходить по всем другим адресам, чтобы попасть на тот, который вы хотите в памяти.Вы действительно можете перейти к тому, что вы хотите.В противном случае наши компьютеры будут работать очень медленно.

6 голосов
/ 14 января 2011

В отличие от машины Тьюринга, которая должна иметь доступ к памяти последовательно, компьютеры используют оперативную память или ОЗУ, что означает, что если они знают, где начинается массив, и они знают, что хотят получить доступ к 20-му элементу массива, они знают,на какую часть памяти смотреть.

Это меньше похоже на проезд по улице и больше похоже на выбор правильного почтового слота для вашей квартиры в общем почтовом ящике.

1 голос
/ 14 января 2011

2 вещи важны:

  1. my_array содержит информацию о том, куда в памяти компьютер должен перейти, чтобы получить этот массив.
  2. index * sizeof type смещается от начала массива.

1 + 2 = O (1), где можно найти данные

0 голосов
/ 14 января 2011

Давайте обсудим это в терминах C / C ++;Есть еще кое-что, что нужно знать о массивах C #, но на самом деле это не имеет отношения к делу.

Учитывая массив 16-битных целочисленных значений:

short[5] myArray = {1,2,3,4,5};

На самом деле произошло то, что компьютервыделил блок памяти в памяти.Этот блок памяти зарезервирован для этого массива, он точно соответствует размеру, необходимому для хранения всего массива (в нашем случае 16 * 5 == 80 бит == 10 байт), и является непрерывным.Эти факты даны;если какой-либо из них или ни один из них не соответствует действительности в любой момент времени, вы, как правило, подвержены риску сбоя вашей программы из-за нарушения доступа.

Итак, учитывая эту структуру, какая переменная myArrayна самом деле, это за кадром, это адрес памяти начала блока памяти.Это также, удобно, начало первого элемента.Каждый дополнительный элемент выстраивается в памяти сразу после первого, по порядку.Блок памяти, выделенный для myArray, может выглядеть следующим образом:

00000000000000010000000000000010000000000000001100000000000001000000000000000101
^               ^               ^               ^               ^
myArray([0])    myArray[1]      myArray[2]      myArray[3]      myArray[4]

Операция с постоянным временем считается для доступа к адресу памяти и считывания постоянного числа байтов.Как и на рисунке выше, вы можете получить адрес памяти для каждого, если знаете три вещи;начало блока памяти, размер памяти каждого элемента и индекс элемента, который вы хотите.Итак, когда вы запрашиваете myArray[3] в своем коде, этот запрос превращается в адрес памяти по следующему уравнению:

myArray[3] == &myArray+sizeof(short)*3;

Таким образом, при вычислении с постоянным временем вы нашли адрес памятичетвертого элемента (индекс 3) и с помощью другой операции с постоянным временем (или, по крайней мере, рассматриваемой так; фактическая сложность доступа - это детали аппаратного обеспечения и достаточно быстрая, чтобы вам было все равно), вы можете прочитать эту память.Вот почему, если вы когда-нибудь задумывались, почему индексы коллекций в большинстве языков стиля C начинаются с нуля;первый элемент массива начинается с местоположения самого массива, без смещения (sizeof (что-либо) * 0 == 0)

В C # есть два заметных различия.Массивы C # имеют некоторую информацию заголовка, которая используется CLR.Заголовок стоит первым в блоке памяти, и размер этого заголовка является постоянным и известным, поэтому уравнение адресации имеет только одно ключевое отличие:

myArray[3] == &myArray+headerSize+sizeof(short)*3;

C # не позволяет напрямую ссылаться на память вэто управляемая среда, но сама среда выполнения будет использовать что-то подобное для выполнения доступа к памяти из кучи.

Второе, что также характерно для большинства разновидностей C / C ++, заключается в том, что определенные типы всегдаразобрался с "по ссылке".Все, что вам нужно использовать для создания ключевого слова new, является ссылочным типом (и есть некоторые объекты, такие как строки, которые также являются ссылочными типами, хотя они выглядят как типы значений в коде).Ссылочный тип, когда создается его экземпляр, помещается в память, не перемещается и обычно не копируется.Таким образом, любая переменная, которая представляет этот объект, за кадром является просто адресом памяти объекта в памяти.Массивы являются ссылочными типами (помните, что myArray был просто адресом памяти).Массивы ссылочных типов являются массивами этих адресов памяти, поэтому доступ к объекту, являющемуся элементом массива, является двухэтапным процессом;сначала вы вычисляете адрес памяти элемента в массиве и получаете его.Это еще один адрес памяти, который является местоположением фактического объекта (или, по крайней мере, его изменяемых данных; структура составных элементов в памяти - это совершенно другое).Это все еще операция с постоянным временем;только два шага вместо одного.

0 голосов
/ 14 января 2011

Big O так не работает. Предполагается, что это мера того, сколько вычислительных ресурсов используется конкретным алгоритмом и функцией. Он не предназначен для измерения объема используемой памяти, и если вы говорите о том, как обойти эту память, это все равно постоянное время. Если мне нужно найти второй слот массива, это вопрос добавления смещения к указателю. Теперь, если у меня есть древовидная структура и я хочу найти конкретный узел, вы сейчас говорите об O (log n), потому что он не находит его при первом проходе. В среднем требуется O (log n), чтобы найти этот узел.

...