Каковы ваши стратегии по снижению использования памяти?

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

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

Редактировать: Я нашел хорошую статью на эту тему. здесь - старая, но все еще интересная.

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
29
0
4 881
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Я не разработчик Ruby, но думаю, что некоторые приемы и методы применимы к любому языку:

Используйте переменную минимального размера, подходящую для задания
Удалите и закройте переменные и соединения, когда они не используются Однако, если у вас есть объект, который вам нужно будет использовать много раз, подумайте о том, чтобы оставить его в области видимости. Любые циклы с манипуляциями с большой строкой выполняются с меньшей строкой, а затем добавляются к большей строке

Используйте приличную (наконец, попробуйте поймать) обработку ошибок, чтобы убедиться, что объекты и соединения закрыты.

При работе с наборами данных возвращайте только необходимый минимум

В Ruby это немного хуже, потому что сборка мусора может занять много времени, и вы не можете напрямую уничтожить переменную.

unexist 28.09.2008 02:05

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

gbjbaanb 28.09.2008 02:47

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

alexmac 28.09.2008 12:29

«Используйте переменную минимального размера, подходящую для работы» - это не относится к Ruby.

Andrew Grimm 02.02.2011 04:23

Я новичок в Ruby, но пока не считаю необходимым делать что-то особенное в этом отношении (то есть помимо того, что я обычно делаю как программист). Возможно, это связано с тем, что память дешевле, чем время, которое потребовалось бы для ее серьезной оптимизации (мой код Ruby работает на машинах с 4–12 ГБ ОЗУ). Это также может быть связано с тем, что задания, для которых я его использую, не выполняются долго (т.е. это будет зависеть от вашего приложения).

Я обнаружил, что Ruby Enterprise Edition от Phusion (ответвление основного Ruby со значительно улучшенной сборкой мусора) существенно изменил использование памяти ... Кроме того, они сделали его чрезвычайно простым в установке (и удалении, если вы найти потребность).

Вы можете узнать больше и скачать его на их сайт.

За исключением крайних случаев использование памяти не о чем беспокоиться. На время, которое вы потратите на сокращение использования памяти, вы купите МНОГО гигабайт.

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

Если у вас есть приложение, в котором память будет ограничивающим фактором, то Ruby может быть не лучшим выбором. Тем не менее, я обнаружил, что мои приложения Rails обычно потребляют около 40-60 МБ ОЗУ на один экземпляр Mongrel. По сути, это не так уж и много.

Возможно, вы сможете запустить свое приложение на JVM с JRuby - виртуальная машина Ruby в настоящее время не так продвинута, как JVM, для управления памятью и сборки мусора. В выпуске 1.9 добавлено много улучшений, а также в разработке находятся альтернативные виртуальные машины.

Проблема не в том, что экземпляру mongrel требуется 60M - это больше, что мне приходится перезапускать mongrel каждый день, чтобы избежать огромной утечки - но я имел в виду не это, задавая этот вопрос. ;)

unexist 28.09.2008 23:13

Совершенно верно. Я думаю, что одна из БОЛЬШИХ выгод от Passenger заключается в управлении экземплярами. Я еще не переставлял свой сервер, но собираюсь сделать это в ближайшее время.

Toby Hede 29.09.2008 03:32

На самом деле я сейчас предпочитаю Thin, а не Mongrel, но проблема во внутреннем Ruby. Включает ли 1.9 какие-либо исправления для сборщика мусора?

unexist 29.09.2008 12:55

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

Linuxios 09.12.2012 04:21

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

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

Ответ принят как подходящий
  1. Выбирайте структуры дат, которые являются эффективными представлениями, хорошо масштабируются и делают то, что вам нужно.
  2. Используйте алгоритмы, которые работают с эффективными структурами данных, а не с раздутыми, но более простыми алгоритмами.
  3. Посмотрите еще где. В Ruby есть мост C, и в C гораздо легче учитывать память, чем в Ruby.

С-образный мост плохо документирован, и вам всегда придется смотреть самому. Большинство ссылок Google - это устаревшие документы / ссылки на несуществующие страницы. ;)

unexist 28.09.2008 23:22

Взгляните на Программное обеспечение с малой памятью - шаблоны для систем с ограниченной памятью. Вы не указываете, какое ограничение памяти, но я предполагаю RAM. Хотя это и не относится к Ruby, я думаю, вы найдете в этой книге несколько полезных идей - шаблоны охватывают RAM, ROM и вторичное хранилище и делятся на основные методы малых структур данных, распределения памяти, сжатия, вторичного хранилища и малого архитектура.

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

Решение состоит в том, чтобы убедиться, что вы используете RMagick версии 2, и вызвать Image#destroy!, когда вы закончите использовать свой образ.

Избегайте такого кода:

str = ''
veryLargeArray.each do |foo|
  str += foo
  # but str << foo is fine (read update below)
end

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

Вместо этого используйте Array#join:

str = veryLargeArray.join('')

Это реализовано в C очень эффективно и не требует накладных расходов на создание String.

ОБНОВЛЕНИЕ: Джонас прав в комментарии ниже. Мое предупреждение относится к +=, но не к <<.

все более длинные струны - will it really? += has that problem but << is an append.
Jonas Elfström 02.02.2010 11:42

Это хорошо знать! Я обновил свой ответ. Таким образом, << деструктивно обновляет вызываемую строку, а не возвращает новую строку, как это делает +=. Спасибо!

Grant Hutchins 10.02.2010 22:58

Я использую Python, но думаю, что стратегии похожи.

Я стараюсь использовать небольшие функции / методы, чтобы локальные переменные автоматически собирали мусор, когда вы возвращаетесь к вызывающей стороне.

В более крупных функциях / методах я явно удаляю большие временные объекты (например, списки), когда они больше не нужны. Как можно раньше закрытие ресурсов тоже может помочь.

Это решение, но в Ruby нет возможности явно удалить объект. Нет даже деструкторов. Иногда Ruby-way немного странный. ;)

unexist 02.10.2008 12:56

Или наоборот, деструкторы не на Ruby странны :) Если копать дальше, деструкторы / финализаторы на самом деле являются странная концепция и реализованы каким-то странным образом на многих языках. Но если вам это действительно нужно, см. этот вопрос, чтобы узнать о ObjectSpace.define_finalizer. Конечно, этот метод, не похожий на ООП, является странным и обескураживающим, потому что финализаторы странны и не приветствуются :)

skalee 10.12.2012 01:41

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

Скорее: используйте много символов, но помните их цель. Никогда не относитесь к ним как к легким струнам. [ruby-lang.org/en/documentation/ruby-from-other-languages/]

skalee 10.12.2012 01:51
  1. При развертывании веб-приложения Rails / Rack используйте REE или какой-либо другой интерпретатор, поддерживающий копирование при записи.
  2. Настройте сборщик мусора (например, см. https://www.engineyard.com/blog/tuning-the-garbage-collector-with-ruby-1-9-2)
  3. Постарайтесь сократить количество используемых вами внешних библиотек / драгоценных камней, поскольку дополнительный код использует память.
  4. Если у вас есть часть вашего приложения, которая действительно интенсивно использует память, возможно, стоит переписать ее в расширении C или завершить, вызвав другие / более быстрые / лучше оптимизированные программы (если вам нужно обрабатывать большие объемы текстовых данных, возможно, вы может заменить этот код вызовами grep, awk, sed и т. д.)

Есть ли доказательства того, что загрузка кода занимает много памяти?

Andrew Grimm 05.12.2012 01:15

Если я создаю новое приложение Rails и запускаю консоль, оно использует около 65 МБ памяти. Если я добавлю Gemfile большого приложения Rails и снова запущу консоль, он будет использовать около 180 МБ памяти.

severin 05.12.2012 15:03

ссылка не работает!

Cyzanfar 10.04.2018 14:01

Ссылка @Cyzanfar исправлена, но я уверен, что где-то еще можно найти более свежую информацию;)

severin 11.04.2018 16:12

Разработчикам Ruby очень повезло, поскольку им не нужно управлять памятью самостоятельно.

Имейте в виду, что рубин выделяет объекты, например такие простые вещи, как

100.times{ 'foo' }

выделяет 100 строковых объектов (строки изменяемы, и для каждой версии требуется собственное выделение памяти).

Убедитесь, что если вы используете библиотеку, выделяющую много объектов, другие альтернативы недоступны, и ваш выбор стоит того, чтобы оплатить сборщик мусора. (у вас может быть не так много запросов / с или несколько десятков мс на запросы).

Создание хеш-объекта действительно выделяет больше, чем объект, например

{'joe' => 'male', 'jane' => 'female'}

не выделяет 1 объект, а 7. (один хэш, 4 строки + 2 строки ключей)

Если вы можете использовать символьные ключи, поскольку они не будут собираться мусором. Однако, поскольку они не будут собираться сборщиком мусора, вы не должны использовать полностью динамические ключи, такие как преобразование имени пользователя в символ, в противном случае вы «утечите» память.

Пример: Где-то в вашем приложении вы применяете to_sym к имени пользователя, например:

hash[current_user.name.to_sym] = something

Когда у вас сотни пользователей, это нормально, но что происходит, если у вас миллион пользователей? Вот цифры:

ruby-1.9.2-head >
# Current memory usage : 6608K
# Now, add one million randomly generated short symbols
ruby-1.9.2-head > 1000000.times { (Time.now.to_f.to_s).to_sym }

# Current memory usage : 153M, even after a Garbage collector run.

# Now, imagine if symbols are just 20x longer than that ?
ruby-1.9.2-head > 1000000.times { (Time.now.to_f.to_s * 20).to_sym }
# Current memory usage : 501M

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

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

Вот несколько ссылок относительно:

http://merbist.com

http://blog.monitis.com

Следует помнить о жизненном цикле ваших объектов. Если ваши объекты не передаются так часто, сборщик мусора в конечном итоге сработает и освободит их. Однако, если вы продолжите ссылаться на них, сборщику мусора может потребоваться несколько циклов, чтобы их освободить. Это особенно верно в Ruby 1.8, где сборщик мусора использует плохую реализацию метода отметки и очистки.

Вы можете столкнуться с этой ситуацией, когда попытаетесь применить некоторые «шаблоны проектирования», такие как декоратор, которые надолго сохраняют объекты в памяти. Это может быть неочевидно при тестировании отдельного примера, но в реальных приложениях, где одновременно создаются тысячи объектов, затраты на увеличение памяти будут значительными.

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

Будьте осторожны при использовании методов гемов / библиотек. Возможно, они не оптимизированы для памяти. Например, класс Ruby PG :: Result имеет метод values, который не оптимизирован. Это будет использовать много дополнительной памяти. Я еще не сообщал об этом.

Где вы узнали, что массивы отличаются от других структур данных с точки зрения памяти и что числа с плавающей запятой занимают память?

Andrew Grimm 10.12.2012 02:20

Обычно целые числа занимают 4 байта, а числа с плавающей запятой занимают 8 байтов. stackoverflow.com/questions/5067249/…

Martin Velez 10.12.2012 11:33

Кроме того, предполагается, что массивы обычно требуют меньше накладных расходов. Конечно, разные реализации Ruby могут отличаться. Но это хорошее практическое правило.

Martin Velez 10.12.2012 11:33

Замена реализации malloc (3) на джемаллок немедленно снизит потребление памяти до 30%. Я создал драгоценный камень jemalloc, чтобы добиться этого мгновенно.

Другие вопросы по теме