В С#/.NET динамический тип занимает меньше места, чем объект?

У меня есть консольное приложение, которое позволяет пользователям указывать переменные для обработки. Эти переменные бывают трех разновидностей: строковые, двойные и длинные (наиболее часто используемые типы — это двойные и длинные). Пользователь может указать любые переменные, которые ему нравятся, и в любом порядке, поэтому моя система должна это обрабатывать. С этой целью в моем приложении я хранил их как объект, а затем применял/удалял их по мере необходимости. Например:

public class UnitResponse
{
    public object Value { get; set; }
}

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

Мой вопрос: было бы более эффективно использовать динамическое ключевое слово для хранения этих значений? Это может обойти проблему упаковки/распаковки, и если это будет более эффективно, как это повлияет на производительность?

РЕДАКТИРОВАТЬ

Чтобы обеспечить некоторый контекст и предотвратить «вы уверены, что используете достаточно оперативной памяти, чтобы беспокоиться об этом» в моем худшем случае, у меня есть 420 000 000 точек данных, о которых нужно беспокоиться (60 переменных * 7 000 000 записей). Это в дополнение к множеству других данных, которые я храню о каждой переменной (включая несколько логических значений и т. д.). Таким образом, сокращение памяти имеет ОГРОМНОЕ влияние.


person Jeffrey Cameron    schedule 27.01.2011    source источник
comment
Вы занимались профилированием? Действительно ли этот бокс/распаковка является узким местом? Растет ли использование ОЗУ вашей программой? Или вы занимаетесь микрооптимизацией?   -  person cdhowie    schedule 28.01.2011
comment
Если был такой простой способ избежать бокса, как вы думаете, почему, по вашему мнению, бокс вообще существует?   -  person Timwi    schedule 28.01.2011
comment
@cdhowie: Моя программа потребляет много оперативной памяти, в некоторых случаях 20-30 ГБ. Сокращение использования памяти является обязательным.   -  person Jeffrey Cameron    schedule 28.01.2011
comment
@Timwi: dynamic не существовало до .NET4, поэтому он появился после упаковки.   -  person Jeffrey Cameron    schedule 28.01.2011
comment
Лично я бы хранил свои семь миллионов записей в базе данных и позволил команде SQL Server беспокоиться о том, как оптимизировать использование памяти. Есть ли причина, по которой это не вариант?   -  person Eric Lippert    schedule 28.01.2011
comment
@Jeffrey: Вы упустили мою мысль. Если бы был простой способ избежать бокса и добиться лучших характеристик памяти и производительности, то бокс никогда бы не изобрели. Но это было, и не зря. Чтобы отрицать это, вы должны были бы поверить, что они придумали какой-то новый способ сделать это в C# 4.0, о котором они почему-то не догадывались ранее.   -  person Timwi    schedule 28.01.2011
comment
@Эрик. К сожалению, есть причина, по которой записи хранятся в памяти, производительность. Программа выполняет донорское вменение недействительных/несовместимых ответов. Он делает это путем случайного поиска каждой единицы, требующей вменения, в волнообразном образце от неисправной единицы. Представьте себе набор данных с записями о населении Канады (36 000 000+), 25% из которых требуют вменения. Это МНОГО пульсирующего поиска. Я оценил использование кеша и объектно-ориентированной базы данных для хранения записей, но это кажется слишком медленным. Любые другие мысли?   -  person Jeffrey Cameron    schedule 29.01.2011
comment
@Timwi: это именно то, на что я надеялся. Я не очень хорошо знаком с тем, как типы управляются в памяти в динамических языках, но я надеялся, что лучшие умы мира нашли способ решить эту проблему. Мой папа всегда говорил мне, что спросить никогда не помешает :)   -  person Jeffrey Cameron    schedule 29.01.2011
comment
@Jeffrey: Можно ли потратить 2 тысячи долларов на создание пользовательской коробки с 24 ГБ или ОЗУ?   -  person Brian    schedule 31.01.2011
comment
@Brian: Прямо сейчас мы запускаем его на виртуальном сервере, 4 ЦП с 32 ГБ ОЗУ. При необходимости можем обновить до большего.   -  person Jeffrey Cameron    schedule 31.01.2011
comment
@Jeffrey: Я думаю, ты упустил точку зрения Эрика. Вы говорите, что вам нужна производительность. Угадайте, что делает оптимизатор в движке SQL. Правильно, он оптимизирует производительность. И за 5 лет работы с MSSQL меня постоянно впечатляла его способность это делать. Если у вас 32 ГБ ОЗУ и вы постоянно обращаетесь к одним и тем же 36 миллионам записей, оптимизатор SQL будет хранить их в памяти.   -  person Timwi    schedule 01.02.2011
comment
@Timwi: Прошу прощения, но ты второй раз говоришь, что я что-то упустил. Я, должно быть, совсем тупой. К сожалению, это вы не понимаете. MSSQL может быть довольно оптимизированным (хотя это очень открыто для интерпретации), но его использование откроет совершенно новое узкое место, перемещая данные из SQL туда и обратно. Данные, о которых я говорю, представлены в форме объекта с различными свойствами, представляющими то, что произошло с записью. Чтобы использовать MSSQL в качестве резервной копии, мне нужно использовать NHibernate или какой-либо другой инструмент для сопоставления объектов. Это ОЧЕНЬ медленно...   -  person Jeffrey Cameron    schedule 01.02.2011
comment
... тем более, что NHibernate/MSSQL будет отправлять отдельные операторы INSERT/UPDATE/SELECT при каждом произвольном доступе. Я думал об использовании OODB, такой как db4o (упомянутый в другом комментарии), потому что у них есть лучшие сериализаторы (и кэширование, такое как MSSQL). Для проверки я просто попытался загрузить все данные за один раз в базу данных. Это заняло в 8 раз больше времени, чем чтение из плоских файлов. Добавьте к этому произвольный доступ и запись, и у вас получится узкое место.   -  person Jeffrey Cameron    schedule 01.02.2011


Ответы (4)


Итак, настоящий вопрос здесь таков: «У меня есть чертовски огромный набор данных, который я храню в памяти, как мне оптимизировать его производительность как по времени, так и по объему памяти?»

Несколько мыслей:

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

Динамичный, не так ли? это бокс плюс много других накладных расходов. (Динамика C# очень быстра по сравнению с другими системами динамической диспетчеризации, но в абсолютном выражении она не является быстрой или малой).

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

  • Длинные, двойные или веревочные? Не может быть int, float или string? Действительно ли данные превышают несколько миллиардов величин или точны до 15 знаков после запятой? Разве int и float не выполнят эту работу в 99% случаев? Они вдвое меньше.

Обычно я не рекомендую использовать float вместо double, потому что это ложная экономия; люди часто экономят таким образом, когда у них есть ОДИН номер, например, экономия четырех байтов будет иметь значение. Разница между 42 миллионами поплавков и 42 миллионами двойников значительна.

  • Есть ли регулярность в данных, которые вы можете использовать? Например, предположим, что из ваших 42 миллионов записей имеется только 100 000 фактических значений, скажем, для каждого типа long, 100 000 значений для каждого типа double и 100 000 значений для каждой строки. В этом случае вы создаете какое-то индексированное хранилище для длинных, двойных и строковых значений, а затем каждая запись получает целое число, где младшие биты являются индексом, а верхние два бита указывают, из какого хранилища ее нужно получить. Теперь у вас есть 42 миллиона записей, каждая из которых содержит целое число, а значения хранятся в какой-то удобной компактной форме где-то еще.

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

  • Помните, что память на самом деле является дисковым пространством; Оперативная память — это просто удобный кеш поверх нее. Если набор данных окажется слишком большим для хранения в ОЗУ, тогда что-то выгружает его обратно на диск и считывает позже; это можете быть вы или это может быть операционная система. Возможно, вы знаете о местоположении своих данных больше, чем операционная система. Вы можете записать свои данные на диск в какой-нибудь удобной для страниц форме (например, в виде b-дерева) и более эффективно хранить данные на диске и загружать их в память только тогда, когда они вам нужны.

person Eric Lippert    schedule 29.01.2011
comment
ВОТ ЭТО ДА! Спасибо, Эрик, здесь есть несколько отличных идей. Это правда, я могу обойтись без int и float, а не с long и double, мне нужно проверить наши данные, чтобы убедиться, что это возможно (у нас есть по крайней мере один идентификатор, который требует long, но он может не использоваться в данных). Учитывая мое ограниченное пространство, я отвечу на ваши вопросы в разных комментариях: - person Jeffrey Cameron; 30.01.2011
comment
1. У нас есть некоторая закономерность в данных, которые мы могли бы использовать, наиболее распространенное значение, с которым мы работаем, — это закодированное значение, в котором часто имеется менее 100 различных значений. Мы могли бы сделать много места здесь - person Jeffrey Cameron; 30.01.2011
comment
2. Хранение логических значений в байтах — гениальная идея, которая должна сэкономить место и быть простой для размещения. - person Jeffrey Cameron; 30.01.2011
comment
3. Проблема с поиском по страницам заключается в используемом нами алгоритме поиска по пульсации. Как только я получаю неудачный модуль, который попадает на границу страницы, когда я выполняю поиск вокруг него, я получаю много перебора страниц. Я думал об использовании кеша LRU, чтобы обойти это (поскольку он остановил бы переборку, попав в кеш), но моя проблема всегда заключалась в том, что мне делать с материалом, срок действия которого истекает из кеша, если мне нужно загрузить его обратно в Память? У меня есть определенная область поиска, поэтому я полагаю, что могу просто сделать кеш достаточно большим, чтобы мне вообще не нужно было ничего перезагружать обратно в память, ... пища для размышлений - person Jeffrey Cameron; 30.01.2011
comment
О, StructLayoutAttribute выглядит интересно, но, как вы сказали, довольно уродливо. Я бы подумал, но только в крайнем случае. - person Jeffrey Cameron; 30.01.2011

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

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

person Eric Lippert    schedule 27.01.2011
comment
Я не знал, что динамические объекты были заключены в коробку внизу. После того, как я прочитал это, я сделал несколько быстрых тестов с образцом проекта и увидел, что они были заключены в коробку внизу. Где-то это указано в документации? Спасибо! - person Jeffrey Cameron; 28.01.2011
comment
@Jeffrey: я отсылаю вас к разделу 4.7 спецификации C # 4, в котором говорится, что динамический тип неотличим от объекта во время выполнения. - person Eric Lippert; 28.01.2011

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

Кроме того, действительно ли все эти усилия стоят 12 байтов на объект? Наверняка есть лучшее применение вашему времени, чем экономия нескольких килобайт (если что) оперативной памяти? Вы доказали, что использование ОЗУ вашей программой на самом деле является проблемой?

person cdhowie    schedule 27.01.2011
comment
12 байт на объект кажутся небольшими, но когда у вас их 420 000 000 (как я делаю в некоторых сценариях), разница становится существенной. Добавьте к этому, что для каждой точки данных (12 байт на объект) мне нужно хранить несколько логических значений и несколько ссылок, и у вас есть тонна памяти. В некоторых тестах мы уже использовали 8 ГБ оперативной памяти. - person Jeffrey Cameron; 28.01.2011
comment
Хорошо, просто проверяю. Если это так, вам следует рассмотреть возможность использования дженериков или строго типизированных структур данных. - person cdhowie; 28.01.2011

Нет. Dynamic просто сохранит его как объект.

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

person Washu    schedule 27.01.2011