Скажем, у меня есть два значительных массива, a и b.
Я хочу иметь b=a+b, но мне на самом деле не нужен a впоследствии, и я бы предпочел сохранить память, чтобы избежать подкачки.
Я подумал о том, чтобы просто использовать:
b.unshift( *a.slice!(0..-1) )
Но у вышеизложенного есть обратная сторона - по-прежнему хранится копия a до завершения процедуры. Другой вариант:
while ! a.empty?() do
b.unshift( a.pop() )
end
Хотя это не изящно и, возможно, даже медленнее (это итерации, я не знаю, насколько быстро работает оператор *, возможно, он на более низком уровне), это сводит к минимуму промежуточный объем памяти.
Есть что-нибудь более элегантное?
Откуда вы знаете, что Ruby не будет выделять N копий a (и, следовательно, тратить эту память), пока вы добавляете элементы один за другим? Если вы делать заботитесь о памяти, вам необходимо профилировать память для кода и посмотреть, сколько объектов выделяет каждый вариант.
@Stefan, тогда это будет b = a.concat(b); a = [], то же самое, что и b.unshift(*a); a = []. Я считаю, что это не совсем то, о чем просил @ user1134991.



Is there something more elegant?
Используя как unshift, так и pop:
a = [1,2,3]
b = [4,5,6]
b.unshift(*a.pop(a.size))
p a, b
# []
# [1, 2, 3, 4, 5, 6]
Или, если вы не хотите использовать *:
a = [1,2,3]
b = [4,5,6]
b.unshift(a.pop(a.size)).flatten!
p a, b
# []
# [1, 2, 3, 4, 5, 6]
Это, вероятно, создает намного больше мусора, чем первоначальная попытка, так что имейте в виду.
@tadman "наверное"? Вы не совсем понимаете, как это работает? Позвольте мне вам это объяснить. a.pop(a.size) вернет весь массив a, а также удалит все элементы из массива a. То, что происходит с памятью, определяется реализацией Ruby. Оба варианта, описанные в ответе, полностью освобождают память для массива a при использовании Ruby по умолчанию.
Это «вероятно» не тот способ, которым вы должны что-то объяснять кому-либо, особенно тем, кто ответил на тысячи вопросов о Ruby. Я говорю, вероятно, только потому, что в разных реализациях, даже в разных версиях Ruby есть различия в том, как это обрабатывается, но это ни в коем случае не является гарантированным «лучшим» решением.
@tadman, если вы настолько опытны в «ответах на тысячи вопросов о Ruby», то вам следовало бы протестировать мои решения, прежде чем сказать, что «это ... создает мусор гораздо больше, чем первоначальная попытка ...». Поступив так, вы бы поняли, что это неправда. Более того, способ обработки не изменился, по крайней мере, с Ruby 1.9.3 до текущей версии.
Профилирование памяти этих фрагментов и фрагментов OP поможет убедить аудиторию в том, что иногда это работает лучше. На первый взгляд, оба этих фрагмента выделяют как минимум один новый массив a.size. Примерно так же, как код в вопросе.
@SergioTulentsev согласился. Я нашел первый комментарий, оставленный г-ном @tadman, неуместным, потому что в нем говорилось, что мой код работает хуже, чем оригинал, но на самом деле это не так. Перед тем, как это сказать, не было проведено никакого анализа. Несомненно, мистер @tadman очень опытен, но этот комментарий был непрофессиональным. Обратите внимание, что я никогда не говорил, что отрицаю тот факт, что b += a лучше работает с памятью. Мой ответ на вопрос «Есть ли что-нибудь более элегантное?» начинается со слов «Использование как unshift, так и pop ...».
взял на себя смелость указать точный вопрос, на который вы отвечали.
@ MOPO3OB Я сделал это наблюдение, потому что у меня было скрытое подозрение, что это может быть шагом назад. Вы можете оспорить это, но словесно ругать кого-то за просто скептицизм - это действительно неуместно. Я уже обращался к запросам, связанным с производительностью, и иногда самая быстрая вещь очень удивительна из-за внутренней оптимизации Ruby. Проблема, с которой я столкнулся с этим решением, заключается в том, что постепенно расширяет целевой массив, что может привести к нескольким более крупным выделениям. Операция конкатенации может выделить новый размер один раз.
@tadman, почему вы говорите, что он расширяет целевой массив постепенно?
Это то, что делает unshift, он добавляет по одному элементу за раз. Если бы вы делали это с массивом длиной 1e6, для которого потребовалось бы несколько расширений размера этого массива, каждое из которых является новым выделением + копией существующих данных (указателей). Большинство динамических массивов изменяют размер с шагом 1,5-2x. Это означает, что для перехода от длины 0 к длине 1 000 000 требуется около 19–39 распределений.
@tadman это делает? Разве ary_memcpy0(ary, 0, argc, argv, target_ary); ARY_SET_LEN(ary, len + argc); не делает это за один шаг?
Я спрашиваю, откуда у начальной операции unshift достаточно контекста, чтобы Ruby знал, что это первая из миллиона несмещений, которые вы собираетесь выполнить. Очевидно, что это не так, поэтому он просто расширяет массив постепенно, используя обычные правила. При выполнении concat он точно знает, какой длины должен быть новый массив, так что да, тогда он может расширяется за один раз.
Я думаю, что и concat, и unshift знают, какой длины будет новый массив. Они оба используют RARRAY_LEN() для расчета этого.
Нет гарантии, что память, выделенная для a, будет освобождена только потому, что вы удалили все элементы. Большинство систем динамических массивов выделяют память по мере увеличения размера массива, что является жесткой необходимостью, но они более расслаблены, когда дело доходит до уменьшения размера.
Лучший подход - сделать b += a, позволить a выпасть из области видимости, а затем позволить сборщику мусора разобраться с этим.
Если вы не можете создать конкретный тест, показывающий, что ваш запутанный подход работает лучше, чего, вероятно, нет, поскольку операция splat создает дополнительный мусор, который необходимо собирать, вам следует сделать самое простое, что работает.
Были времена, когда МРТ вообще не высвобождала память (ее куча только увеличивалась, а не сокращалась). Думаю, сейчас лучше. :)
@SergioTulentsev Ах, те 1,8 дня, когда нам приходилось обрабатывать и снимать их, потому что они стали слишком толстыми и старыми.
Было только до 1.8? Моя память нечеткая, но я совершенно уверен, что 1.9 тоже вела себя так.
@SergioTulentsev 1.8 был настолько плох, что существовал "Enterprise Ruby" для решения этой проблемы. 1.9 сделал большие улучшения, 2.0 в значительной степени лучше.
А как насчет
b = a.concat(b)?