Преаллокация памяти в эрланге

May 11, 2013 01:11

Чем хорош С? Тем, что в нём можно выделить кусок памяти и писать в эту память, ничего не перемещая и не занимаясь сборкой мусора.
Есть, правда, маленькие трудности с тем, что сложно остановиться и вспомнить, что памяти было выделено меньше, чем уже затерто…

В эрланге с этим вроде как похуже, потому что все данные немутабельны. Т.е. нельзя выделить кусок памяти и писать в него. Однако это то, что на поверхности. Под капотом там кроется такая крутая штука, как referenced binary. Выглядит это так:



1) сначала создается килобайтный бинарник:

A = <<>>.

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

2) в его конец можно дописать данные:

B = <>

Теперь у нас есть два разных бинарника A и B, которые на самом деле ссылаются на одну область памяти, просто с разными длинами.

3) при необходимости происходит копирование

С = <>

Теперь C ссылается на совершенно другую область памяти.

Внезапно оказалось, что в эрланге кое где есть хинтинг: когда рантайм может достоверно угадать, сколько памяти будет использовано в каком-то куске кода, он заранее преаллоцирует бинарник нужного размера, что позволяет сильно сэкономить на перекопированиях и маллоке.

Это можно использовать в своём коде. Код ниже преаллоцирует фиксированного размера бинарник и заполняет его распакованными смещениями атома ctts из mp4 moov.

parse_ctts(CTTS, SampleCount) ->
parse_ctts0(CTTS, mpegts_alloc:new(SampleCount*4)).

parse_ctts0(<<>>, New) -> New;
parse_ctts0(<>, New) ->
parse_ctts1(CTTS, New, Offset, Count).

parse_ctts1(CTTS, New, _, 0) ->
parse_ctts0(CTTS, New);
parse_ctts1(CTTS, New, Offset, Count) ->
parse_ctts1(CTTS, <>, Offset, Count - 1).

Сам код mpegts_alloc выглядит так:

{function, new, 1, 2}.
{label,1}.
{line,[{location,"mpegts_alloc.S",13}]}.
{func_info,{atom,mpegts_alloc},{atom,new},1}.
{label,2}.
{line,[{location,"mpegts_alloc.S",16}]}.
{test,is_integer,{f,1},[{x,0}]}.
{test,is_ge,{f,1},[{x,0},{integer,0}]}.
bs_init_writable.
return.

Да, да, это самый настоящий эрланговский ассемблер.

Написано здесь примерно следующее:

1) проверить, что в регистре x0 лежит число. В x0 передается первый аргумент функции
2) проверить, что x0 больше нуля
3) вызвать bs_init_writable, который в x0 ожидает длину требуемого бинарника, в x0 положит ссылку на новый бинарник
4) выйти из функции. В x0 по соглашению лежит ответ.

Компилируемый код mpegts_alloc.S на гитхабе.

Такой код может работать гораздо быстрее, чем накапливать в список, потом lists:reverse и iolist_to_binary. Быстрота достигается за счёт того, что сокращается множественное перекопирование данных столь характерное для немутабельных языков.

fp, erlang, trick

Previous post Next post
Up