Я разрабатываю приложение C#, которое должно обработать приблизительно 4 000 000 английских предложений. Все эти предложения хранятся в дереве. Где каждый узел в дереве является классом, который имеет эти поля:
class TreeNode
{
protected string word;
protected Dictionary<string, TreeNode> children;
}
Моя проблема состоит в том, что приложение израсходовало всю RAM (у меня есть 2 ГБ RAM), когда это достигает 2,000,000-го предложения. Таким образом, этому только удается обработать половину предложений, и затем это замедляется решительно.
Что я могу сделать, чтобы попытаться уменьшить объем потребляемой памяти приложения?
Править: Позвольте мне объяснить немного больше своего приложения. Таким образом, у меня есть приблизительно 300 000 английских предложений, и от каждого предложения я генерирую далее sub предложения как это:
Пример: Предложение: Футбол является очень популярным видом спорта Предложения Sub, в которых я нуждаюсь:
Каждое предложение хранится в дереве пословно. Так рассматривая пример выше, у меня есть Класс TreeNode с полем слова = "Футбол", и дети перечисляют, имеет TreeNode для слова ",". Ребенок "является" узлом, "a" узел. Ребенок для "a" узла является "самым" узлом. Я должен сохранить предложения пословно, так как я должен быть в состоянии искать все предложения, запускающиеся с Примера: "Футбол".
Так в основном для каждого слова в предложении я создаю новое (подпредложение). И это - причина, я в конечном счете заканчиваю с 4 000 000 различных предложений. Хранить данные в базе данных не является опцией, так как приложение должно работать над целой структурой сразу. И это далее замедлит процесс, если я должен был остаться пишущим все данные к базе данных.
Спасибо
Что вы используете в качестве ключа? Откуда вы берете данные? Если это слова (не полные наборы), то мне интересно, много ли у вас дублированных ключей (различные строковые
экземпляры с одним и тем же фундаментальным значением), и в этом случае вам может пригодиться реализация локального интернера для повторного использования значений (и пусть переходные копии будут собирать мусор).
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];
Не могли бы вы сопоставить каждое слово с информацией? Таким образом, у Вас есть одна карта int - строка, содержащая уникальные английские слова, и древовидная структура, содержащая такие предложения:
class TreeNode
{
protected int word;
protected Dictionary<int, TreeNode> children;
}
Dictionary<string, int> _AllWords;
Теперь коллекция _AllWords
не является оптимальной для поиска слов, основанных на ключе "как есть". Скорее всего, вам нужен нечто вроде многоклавишного списка, в котором вы можете быстро искать как по ключевому слову, так и по его значению. CodeProject содержит статью об этом.
Если вам нужна производительность, и вы чувствуете, что вам нужны все слова в памяти, то я бы посоветовал использовать строковый массив, чтобы содержать все слова. Затем хранить все индексы в отсортированном двоичном дереве.
.Некоторые моменты, о которых следует подумать.
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;
}
}
Это может быть чересчур для вашей ситуации, но вы можете хранить ваши узлы в файлах на диске и использовать B-дерево реализацию, чтобы максимизировать производительность ввода-вывода. Это то, что большинство баз данных используют внутри, потому что просто слишком много данных, чтобы хранить их в памяти.
Сам тип Dictionary может потреблять много памяти. Рассматривали ли вы использование вместо этого List
? Общий Список
использует намного меньше памяти для каждого экземпляра, чем общий Словарь
.
Конечно, ограничение использования списка вместо словаря состоит в том, что вы не получаете автоматического индексирования по строкам. Это был бы очевидный компромисс между временем и пространством. Если списки короткие, это может быть даже быстрее, чем словарь (линейный поиск ~ 10 ключей часто будет быстрее, чем поиск по хеш-таблице). Даже если по крайней мере большинство списков короткие, это все равно может быть большим улучшением (например, если 95% списков содержат 10 или меньше элементов, а остальные 5% содержат максимум, возможно, 100 элементов. ).
Вы даже можете использовать Collection
, который использует даже меньше памяти, чем List
.
Единственный способ значительно Уменьшить использование памяти - не сохраняя предложения в памяти.
Что вы пытаетесь достичь? Почему вы строите дерево? Если вы что-то подсчитываете, подсчитаете и отбросьте строки в качестве их прочитанного. Если вы строите график (т. Е. Для анализа отношений между предложением и / или слов), попробуйте перечислить предложения и слова, чтобы они могли быть уникальными / ключом этим удостоверением. Вместо этого используйте этот идентификатор в памяти.
Я надеюсь, что это поможет.