Уменьшение памяти похожих объектов - PullRequest
4 голосов
/ 13 ноября 2009

Я смотрю на уменьшение потребления памяти таблицей, подобной объекту коллекции.

Учитывая структуру класса, такую ​​как

Class Cell
{
    public property int Data;
    public property string Format;
}

Class Table
{
    public property Dictionary<Position, Cell> Cells;
}

При наличии большого количества ячеек свойство Data класса Cell может быть переменным, но свойство Format может повторяться много раз, например, ячейки заголовка могут иметь пустую строку формата для заголовков, а все ячейки данных могут иметь значение «0,00».

Одна идея заключается в следующем:

Class Cell
{
    public property int Data;
    public property int FormatId;
}
Class Table
{
    public property Dictionary<Position, Cell> Cells;
    private property Dictionary<Position, string> Formats;

    public string GetCellFormat(Position);
}

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

Есть ли лучшая реализация, чем эта? Я посмотрел на шаблон в полулегком, но не уверен, что он соответствует этому.

Более сложная реализация, которую я рассматриваю, - это полное удаление свойства Format из класса Cell и вместо этого сохранение форматов в словаре, который группирует соседние ячейки вместе
например таких записей может быть 2
<item rowFrom=1 rowTo=1 format="" />
<item romFrom=2 rowTo=1000 format="0.00" />

Ответы [ 3 ]

5 голосов
/ 13 ноября 2009

Что касается строк, вы можете посмотреть на интернирование; либо со встроенным интернером, либо (желательно) с персонализированным интернером - в основном Dictionary<string,string>. Это означает, что каждая идентичная строка использует одну и ту же ссылку - и дубликаты могут быть собраны.

Ничего не делай с int; это уже оптимально.

Например:

using System;
using System.Collections.Generic;
class StringInterner {
    private readonly Dictionary<string, string> lookup
        = new Dictionary<string, string>();
    public string this[string value] {
        get {
            if(value == null) return null;
            if(value == "") return string.Empty;
            string result;
            lock (lookup) { // remove if not needed to be thread-safe     
                if (!lookup.TryGetValue(value, out result)) {
                    lookup.Add(value, value);
                    result = value;
                }
            }
            return result;
        }
    }
    public void Clear() {
        lock (lookup) { lookup.Clear(); }
    }
}
static class Program {
    static void Main() {
        // this line is to defeat the inbuilt compiler interner
        char[] test = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };

        string a = new string(test), b = new string(test);
        Console.WriteLine(ReferenceEquals(a, b)); // false
        StringInterner cache = new StringInterner();
        string c = cache[a], d = cache[b];
        Console.WriteLine(ReferenceEquals(c, d)); // true
    }
}

Вы можете продолжить это с WeakReference, если хотите.

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

4 голосов
/ 13 ноября 2009

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

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

0 голосов
/ 13 ноября 2009

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

Концепция заключается в том, что для заданного диапазона ячеек будет храниться один формат (либо строка, либо класс), например 1-1000. Чтобы извлечь из этого пользу, нужно внести некоторые изменения в дизайн ... Свойство format необходимо удалить из класса ячейки. Вместо этого формат должен быть зарегистрирован либо в классе таблицы, либо предпочтительно в другом классе. Например

public class CellFormats 
{ ....
public void Register(int start, int finish, string format);
}

Класс форматов ячеек будет содержать разреженную матрицу, которая будет содержать формат для диапазона.

Класс Table будет затем использовать класс CellFormats. Вместо метода GetCellFormat у него будет метод со следующей сигнатурой

void ApplyCellFormat(Position cellPosition)

Это позволит получить формат ячейки из класса CellFormats (разреженная матрица) и применить его к ячейке.

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

...