Как массивы и хеш-карты постоянны в их доступе?

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

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

Пример:

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

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

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

В отличие от машины turing, которая будет иметь доступ к памяти последовательно, компьютеры используют оперативную память или RAM, что означает, что если они знают, где начинается массив, и они знают, что хотят получить доступ к 20-му элементу массива, они знают какую часть памяти посмотреть.

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

Ответ 4

2 важно:

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

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

Ответ 5

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

Ответ 6

Обсудить это в терминах C/С++; есть некоторые дополнительные материалы, чтобы знать о массивах С#, но это не очень важно для точки.

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

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

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

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

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

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

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

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

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

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

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

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