2011-08-26 14 views
4

Дано:Действительно ли erlang реализует запись, копирование и изменение любым умным способом?

-record(foo, {a, b, c}). 

я сделать что-то вроде этого:

Thing = #foo{a={1,2}, b={3,4}, c={5,6}}, 
Thing1 = Thing#foo{a={7,8}}. 

С семантической точки зрения, вещь и thing1 являются уникальными объектами. Однако, с точки зрения реализации языка, полная копия Thing для создания Thing1 будет очень расточительной. Например, если запись была размером в мегабайт, и я сделал тысячу «копий», каждый из которых изменял пару байтов, я только что сжег гигабайт. Если внутренняя структура отслеживала представление родительской структуры и каждая производная, помеченная таким родителем таким образом, который указывал на ее собственное изменение, но сохранял все версии elses, производные могли быть созданы с минимальными издержками памяти.

Мой вопрос заключается в следующем: erlang делает что-нибудь умное - внутренне - для поддержания накладных расходов обычной эрланговой каракули;

Thing = #ridiculously_large_record, 
Thing1 = make_modified_copy(Thing), 
Thing2 = make_modified_copy(Thing1), 
Thing3 = make_modified_copy(Thing2), 
Thing4 = make_modified_copy(Thing3), 
Thing5 = make_modified_copy(Thing4) 

... как минимум?

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

ответ

9

Точная работа сборки мусора и распределения памяти известна лишь нескольким. К счастью, они очень рады поделиться своими знаниями, и следующее основано на том, что я узнал из списка рассылки erlang-questions и обсуждая с разработчиками OTP.

При обмене сообщениями между процессами содержимое всегда копируется, поскольку между процессами нет общей кучи. Единственным исключением являются двоичные файлы размером более 64 байт, где копируется только ссылка.

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

Кортеж - это структура, которая хранит ссылки на фактические данные где-то на куче (кроме небольших целых чисел и, возможно, еще одного типа данных, который я не могу запомнить). Когда вы обновляете кортеж, используя, например, setelement/3, создается новый кортеж с замененным элементом, однако для всех остальных элементов копируется только ссылка. Существует one exception, с которым я никогда не мог воспользоваться.

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

Как всегда, Erlang дает вам некоторые инструменты, чтобы точно понимать, что происходит. The efficiency guide подробно, как использовать erts_debug:size/1 и erts_debug:flat_size/1, чтобы понять размер структуры данных при использовании внутри процесса и при копировании. Инструменты трассировки также позволяют вам понять, когда, что и сколько собирали мусор.

+1

Функции 'erts: size/1' и' erts: flat_size/1' должны быть 'erts_debug: size/1' и' erts_debug: flat_size/1'. – psyeugenic

+1

Обновлен мой ответ. Спасибо, что указал на ошибку. – knutin

+0

Привет, Зачем вы говорите, что не можете воспользоваться? спасибо ! – niahoo

5

Запись foo имеет четкость 4 (четыре слова), но вся структура имеет 14 слов. Любые немедленные (pids, ports, small integers, atom, catch и nil) могут храниться непосредственно в массиве кортежей. Любой другой термин, который не может вписываться в слово, например, другие кортежи, не сохраняется непосредственно, а ссылается на указатели в штучной упаковке (указатель в штучной упаковке - это термин erlang с адресом пересылки к реальному eterm ... просто внутренним).

В вашем случае новый кортеж же арностью создается и атом foo и все указатели копируются из предыдущего набора для индекса два, a, что указывает на новый кортеж {7,8}, который составляет 3 слова, за исключением. Во всех 5 + 3 новых словах создаются на куче, и только 3 слова копируются из старого кортежа, остальные 9 слов не затрагиваются.

Чрезмерно большие кортежи не рекомендуется. При обновлении кортежа весь кортеж, т. Е. Массив, а не глубокий контент, необходимо скопировать, а затем обновить в другом, чтобы сохранить постоянную структуру данных. Это также создаст увеличенный мусор, заставляя сборщик мусора нагреваться, что также ухудшает производительность. По этой причине модули dict и array избегают использования больших кортежей и вместо этого имеют неглубокое кортеж.

+0

Это полезно. Один вопрос - что означает «поймать и ноль» в этом контексте? – Dan

+0

nil - пустой список, т. Е. Последние cons '[]'. catch также является непосредственным и используется при обработке ошибок, но не применим в этом контексте. – psyeugenic

0

В заключение:

Thing = #foo{a={1,2}, b={3,4}, c={5,6}}, 
Thing1 = Thing#foo{a={7,8}}. 

Здесь, если Thing снова не используется, он, вероятно, будет обновляться на месте и копирования кортежа можно избежать, так как эффективность Руководство говорит. (Кортеж и запись синтаксис компилируется в нечто вроде SetElement, я думаю)

Thing = #ridiculously_large_record, 
Thing1 = make_modified_copy(Thing), 
Thing2 = make_modified_copy(Thing1), 
... 

Здесь кортежи фактически копируются каждый раз.

Я предполагаю, что теоретически возможно сделать интересную оптимизацию. Если компилятор может выполнить анализ эвакуации по возвращаемому значению make_modified_copy и обнаружить, что единственная ссылка на него - тот, который был возвращен, может сохранить эту информацию о функции. Когда он сталкивается с вызовом этой функции, он будет знать, что безопасно изменять возвращаемое значение на месте.

Это можно было бы сделать только при вызове между модулями из-за функции замены кода.

Возможно, в один прекрасный день у нас будет это.

2

Я могу определенно подтвердить, что люди уже указывали:

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

Это работает только потому, что мы имеем неизменные данные. Поэтому в вашем примере каждый раз, когда вы обновляете значение в записи #foo, ни одна из данных в элементах не копируется и создается только новый 4-элементный кортеж (5 слов). Erlang никогда не будет делать глубокую копию в этом типе операции или при передаче аргументов в вызове функций.

 Смежные вопросы

  • Нет связанных вопросов^_^