Уменьшение объема памяти приложения C#
Я разрабатываю приложение c#, которое должно обрабатывать приблизительно 4,000,000 английских предложений. Все эти предложения хранятся в дереве. Где каждый узел в дереве является классом, который имеет следующие поля:
class TreeNode
{
protected string word;
protected Dictionary<string, TreeNode> children;
}
моя проблема в том, что приложение использует всю ОЗУ (у меня есть 2 ГБ ОЗУ), когда она достигает 2,000,000-го предложения. Таким образом, ему удается обработать только половину предложений, а затем он резко замедляется.
что я могу сделать, чтобы попытаться уменьшить объем памяти приложения?
EDIT: позвольте мне объяснить немного больше моего приложения. Таким образом, у меня есть приблизительно 300 000 английских предложений, и из каждого предложения я генерирую следующие предложения:
пример: Предложение: футбол-очень популярный вид спорта Sub предложения мне нужно:
- футбол-очень популярный вид спорта
- - это очень популярный вид спорта
- очень популярный вид спорта
- очень популярный вид спорта
- популярный вид спорта
- спорт
каждое предложение хранится в дереве слово за словом. Поэтому, учитывая приведенный выше пример, у меня есть класс TreeNode со словом field = "футбол", а в списке детей есть TreeNode для слова "is". Ребенок "- это" узел "а" узел. Ребенка на "а" узел "очень" узел. Мне нужно хранить предложения слово за словом, так как мне нужно иметь возможность искать все предложения, начинающиеся с Пример: "футбол есть".
поэтому в основном для каждого слова в предложении я создаю новое (под-предложение). И именно по этой причине я в конечном итоге получаю 4 миллиона различных предложений. Хранение данных в базе данных-это не вариант, так как приложение должно работать на всю структуру сразу. И это еще больше замедлит процесс, если мне придется продолжать записывать все данные в базу данных.
спасибо
9 ответов
что вы используете в качестве ключа? Откуда вы берете данные? Если это слова (не полные setences), мне интересно, есть ли у вас много дублированный ключи (разные string
экземпляры с тем же фундаментальным значением), в этом случае вы можете воспользоваться реализацией локального интернера для повторного использования значений (и позволить переходным копиям собирать мусор).
public sealed class StringCache {
private readonly Dictionary<string,string> values
= new Dictionary<string,string>(StringComparer.Ordinal);
public string this[string value] {
get {
string cached;
if (!values.TryGetValue(value, out cached)) {
values.Add(value, value);
cached = value;
}
return cached;
}
}
}
создайте экземпляр этого при построении дерева и используйте (когда вы думаю, значение, скорее всего, будет дублироваться):
StringCache cache = new StringCache(); // re-use this instance while building
// your tree
...
string s = ... // whatever (from reading your input)
s = cache[s];
сам тип словаря может потреблять много памяти. Вы рассматривали возможность использования ? Родовое List
использует намного меньше памяти на экземпляр, чем универсальный Dictionary
.
конечно, ограничение использования списка вместо словаря заключается в том, что вы не получаете автоматического индексирования по строкам. Это явный обмен между временем и пространством. Если списки короткие, это может быть даже быстрее, чем словарь (линейный поиск ~10 ключей часто происходит чтобы быть быстрее, чем поиск hashtable). Даже если по крайней мере большинство из списков короткие, это все еще может быть большим улучшением (например, если 95% списков имеют 10 или меньше элементов, а другие 5% имеют максимум, возможно, 100 элементов).
вы даже можете использовать Collection<KeyValuePair<string, TreeNode>>
, который использует даже меньше памяти, чем List<T>
.
Если ваше требование для производительности, и вы чувствуете, как будто вам нужны все слова в памяти, я бы предложил вам использовать строковый массив, чтобы содержать все слова. Затем сохраните все индексы в отсортированном двоичном дереве.
не могли бы вы сопоставить каждое слово в int? Таким образом, у вас есть одна карта int to string, которая содержит уникальные английские слова и древовидную структуру, содержащую такие предложения:
class TreeNode
{
protected int word;
protected Dictionary<int, TreeNode> children;
}
Dictionary<string, int> _AllWords;
теперь _AllWords
коллекция не является оптимальным для поиска слов на основе ключа как. То, что вы, вероятно, хотите здесь,-это что-то вроде списка с несколькими ключами, где вы можете сделать быстрый поиск на основе ключа и значения. CodeProject есть статья об этом.
Это может быть излишним для вашей ситуации, но вы могли бы хранить свои узлы в файлах на диске и использовать B-Tree реализация для максимизации производительности ввода-вывода. Это то, что большинство баз данных используют внутри, потому что слишком много данных для хранения в памяти.
некоторые моменты, о которых нужно подумать.
- когда вы инициализируете свой словарь, передайте максимальное количество элементов, которые вам нужны. Это заставит его выделить достаточное количество ведер при запуске. По умолчанию инициализируется с помощью 0 ведер, что равно 3 (prime). После добавления дополнительных элементов словарь должен повторно инициализировать и скопировать все элементы в новое более крупное хранилище. Если программа никогда не бездельничает, то ГК не будет собирать старые словари.
- вы могли бы сэкономить пространство кодировка строк. Строки будут использовать два байта на символ в памяти. С помощью некоторых вспомогательных функций вы можете иметь свой класс следующим образом:
class TreeNode
{
protected byte[] word;
protected Dictionary<byte[], TreeNode> children;
public string Word
{
get { return Encoding.UTF8.GetString(word); }
set { word = Encoding.UTF8.GetBytes(value); }
}
public TreeNode GetChildByKey( string key )
{
TreeNode node;
if(children.TryGetValue( Encoding.UTF8.GetBytes(key), out node ))
{
return node;
}
return null;
}
}
[редактирование] И я забыл, что вам также нужен новый компаратор для ключа byte [].
var children = new Dictonary<string,TreeNode>(new ByteArrayComparer);
public class ByteArrayComparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[] x, byte[] y)
{
if (x.Length != y.Length)
return false;
for (int i = 0; i < x.Length; i++)
{
if (x[i] != y[i])
return false;
}
return true;
}
public int GetHashCode(byte[] a)
{
return a[0] | (int)a[1] << 8 | (int)a[2] << 16 | (int)a[3] << 24;
}
}
единственный способ, которым вы можете значительно уменьшить использование памяти, не сохраняя наказание в памяти.
чего вы пытаетесь достичь? Зачем ты строишь дерево? Если вы что-то подсчитываете, подсчитайте и отбросьте строки по мере их чтения. Если вы строите график (т. е. чтобы проанализировать отношения между предложением и/или словами), попробуйте перечислить предложения и слова, чтобы они могли быть уникальными / ключевыми по этому идентификатору. Используйте этот id в памяти вместо.
надеюсь, это поможет.
чтобы уменьшить объем памяти, вы должны искать Последовательный Кэш Данных.
Это позволяет снизить нагрузку на память в коллекции. (Элемент коллекции должен быть помечен как [Serializable])
вы даже можете сделать коллекцию постоянной, передав deleteOnClose: false параметр
пример
using (var c = SequentialDataCache<TreeNode>.Initialize(deleteOnClose: false))
{
//add items to collection
for (int i = 0; i < 1000; i++)
{
var treeNode = new TreeNode()
{
Word = string.Format("Word{0}", i),
Children = new Dictionary<string, TreeNode>()
};
for (int j = 0; j < 100; j++)
{
var child = new TreeNode() { Word = string.Format("Word{0}", j) };
treeNode.Children.Add(string.Format("key{0}{1}", i, j), child);
}
c.Add(treeNode);
}
//assert query
Assert.AreEqual("Word0", c[0].Word);
Assert.AreEqual("Word1", c[0].Children["key01"].Word);
Assert.AreEqual("Word100", c[100].Word);
}
и TreeNode...
[Serializable]
class TreeNode
{
private string word;
private Dictionary<string, TreeNode> children;
public string Word
{
get { return word; }
set { word = value; }
}
public Dictionary<string, TreeNode> Children
{
get { return children; }
set { children = value; }
}
}
Отличный вопрос, и некоторые отличные ответы. Я многому научился. Идея StringCache заслуживает некоторого исследования.
Я хочу ответить на пункт" я не могу использовать базу данных, потому что мне нужно все это в памяти". Во многих случаях база данных на самом деле является лучшим решением.
считайте, что надежный SQL Database engine (я парень MSSQL):
- может содержать гораздо больше данных, размер диска, а не размер памяти и пространства подкачки. (База данных SQL может также используйте память и диск на отдельной машине, тем самым увеличивая доступный след с компромиссом задержки сети.)
- индексирует данные для быстрого извлечения
- динамически кэширует данные, используемые чаще всего и освобождает менее используемые данные, когда давление памяти диктует.
- использует алгоритмы хранения, извлечения и кэширования, разработанные большой командой на протяжении многих лет и настроенные для динамической адаптации к широкому спектру положения.
динамическое кэширование может быть огромным преимуществом для данного множества решений. Предполагая, что ваш корпус состоит только из" нормальных " предложений, распределение слов не будет равномерным. Наиболее частые слова будут доступны на несколько порядков чаще, чем наименее частые. Также вероятно, что частые слова будут добавлены в словарь очень рано, и поэтому будут храниться близко друг к другу в базе данных. Хороший SQL-движок будет кэшировать больше всего часто используемые блоки в памяти, что, естественно, способствует типу поиска, который вы описываете.
гибридное решение может выглядеть так:
-
таблица с соответствующими индексами
create table myWords (wordKey int identity, word varchar(50)) create unique index iword on myWords(word) -- used for adds and retrieval create unique index iwordKey on myWords(wordKey) -- used for mapping keys back to words
-
хранимая процедура добавления / поиска слов. Хранимые процедуры удобно возвращают int.
create procedure addWord (@word varchar(50)) as begin declare @wordKey int, @rows int insert myWords (word) select @word where not exists (select 1 from myWords where word = @word) select @wordKey = @@identity, @rows = @@rowcount if @rows = 0 begin select @wordKey = wordKey from myWords where word = @word end return @wordKey end
приложение добавляет слова в базу данных, строит дерево в памяти, используя только wordKey ценности.
- поиск соответствующего предложения начнется с запроса, чтобы получить значения wordKey для рассматриваемых слов, затем проанализируйте дерево, соберите wordKeys, необходимые для построения полных предложений, и, наконец, получить эти слова с помощью второго запроса.
вы можете торговать небольшой скоростью создания базы данных для дальнейшей оптимизации выгоды от кэширования наиболее частых слов.
- добавить поле в таблицу (
usageCount int
). Вставки установите его в 1, обновляет инкремент. - только с индексом на word, заполнить таблицу словаря из вашего корпуса
- добавьте кластеризованный индекс в usageCount (desc), который будет реорганизован, чтобы приблизить наиболее частые слова. (Может быть, отбросьте его снова - хорошая работа завершена.)
- построить свое дерево.
даже если ваш корпус растет в будущем, частоты слов вряд ли изменятся достаточно, чтобы повлиять эффективность.