Реализует ли erlang копирование и изменение записей каким-либо умным способом? - PullRequest
4 голосов
/ 26 августа 2011

Дано:

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

Я делаю что-то вроде этого:

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

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

У меня такой вопрос: эрланг делает что-то умное - внутренне - чтобы не тратить время на обычную писанину эрланга;

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)

... до минимума?

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

Ответы [ 4 ]

9 голосов
/ 26 августа 2011

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

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

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

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

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

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

5 голосов
/ 27 августа 2011

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

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

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

2 голосов
/ 31 августа 2011

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

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

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

0 голосов
/ 27 августа 2011

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

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),
...

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

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

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

Может быть, однажды у нас это будет.

...