2013-11-08 4 views
6

фонЭффективная реализация приспособленец

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

Многие из этих экземпляров содержат одинаковые данные. Совместное использование одного экземпляра значительно облегчит использование памяти. Однако, поскольку мы используем structs, экземпляры не могут использоваться совместно. Также невозможно изменить его на класс, поскольку важна семантика структуры.

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

Суженую вниз версия кода выглядит примерно так:

public struct PointD 
{ 
    //Factory 
    private static class PointDatabase 
    { 
     private static readonly Dictionary<PointData, PointData> _data = new Dictionary<PointData, PointData>(); 

     public static PointData Get(double x, double y) 
     { 
      var key = new PointData(x, y); 
      if (!_data.ContainsKey(key)) 
       _data.Add(key, key); 

      return _data[key]; 
     } 
    } 

    //Flyweight data 
    private class PointData 
    { 
     private double pX; 
     private double pY; 

     public PointData(double x, double y) 
     { 
      pX = x; 
      pY = y; 
     } 

     public double X 
     { 
      get { return pX; } 
     } 

     public double Y 
     { 
      get { return pY; } 
     } 

     public override bool Equals(object obj) 
     { 
      var other = obj as PointData; 
      if (other == null) 
       return false; 
      return other.X == this.X && other.Y == this.Y; 
     } 
     public override int GetHashCode() 
     { 
      return X.GetHashCode() * Y.GetHashCode(); 
     } 
    } 


    //Public struct 

    public Point(double x, double y) 
    { 
     _data = Point3DDatabase.Get(x, y); 
    } 

    public double X 
    { 
     get { return _data == null ? 0 : _data.X; } 
     set { _data = PointDatabase.Get(value, Y); } 
    } 

    public double Y 
    { 
     get { return _data == null ? 0 : _data.Y; } 
     set { _data = PointDatabase.Get(X, value); } 
    } 
} 

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

(Пожалуйста, не говоря уже о утечки памяти или такой, это упрощенный пример кода)

Проблема

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

В качестве альтернативы мы могли бы создать две версии класса Point: один с поддержкой flyweight, как указано выше, и один, содержащий свои собственные данные (с возможными дубликатами). Все (недолговечные) вычисления могут быть выполнены во втором классе, тогда как при хранении Point для более длительных периодов времени они могут быть преобразованы в первый класс, эффективный с точки зрения памяти. Однако это означает, что все пользователи класса Point должны быть проинспектированы и настроены по этой схеме, что не представляется возможным для нас.

Что мы ищем это подход, который отвечает ниже критериям:

  • При наличии нескольких Point ы с теми же данными, использование памяти должна быть ниже, чем с другой STRUCT экземпляр для каждого из них ,
  • Производительность не должна быть намного хуже, чем работать непосредственно с примитивными данными в структуре.
  • Строговую семантику следует поддерживать.
  • Интерфейс «Точка» должен оставаться тем же (то есть не следует изменять классы, которые используют «Точку»).

Есть ли способ улучшить наш подход к этим критериям? Или кто-нибудь может предложить другой подход, который мы можем предпринять?

+3

Не имеет отношения к вашему вопросу, но: Это ужасный метод хэш-кода. Хэш-код для целого числа - это значение самого целого. Поэтому, если X или Y равно нулю, хэш-код будет равен нулю. Я предполагаю, что это может вызвать множество столкновений в контейнерах хеширования - например, словарь, который вы используете !. Вы должны использовать что-то вроде 'return X + 31 * Y;' –

+0

@MatthewWatson Я обычно XOR HashCodes полей. Будет ли это более уместным? – Gusdor

+0

@Gusdor XOR не очень хорош, потому что он будет равен нулю для ВСЕХ случаев, где X == Y. Если у вас есть набор данных, так что есть много случаев, когда X == Y вы получите много столкновений. –

ответ

0

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

Думайте об этом таким образом. На графике вы не можете отображать несколько миллионов точек сразу, потому что у вас заканчиваются пиксели (вы должны окклюзия - отбирать эти точки). Аналогично, в таблице на экране недостаточно вертикального пространства (вам нужно усечение набора данных). Рассмотрите возможность потоковой передачи данных из вашего исходного файла по мере необходимости. Если ваша исходная структура данных не подходит для динамического поиска, рассмотрите промежуточный, временный формат файла. Это один из способов .Net JITer работает так быстро!

+0

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

+0

@Steven даже с агрегацией, вам понадобится только несколько значений в памяти за раз? Когда вы попадаете в большие наборы данных, всегда есть компромисс производительности и памяти. – Gusdor

+1

Что касается твоего понимания; да, это то, что я говорю. Храните ваши данные на диске, когда можете, в формате (возможно, двоичном и индексированном), который позволяет быстро перевести его в память. Вероятно, вам нужен временный кеш в памяти, который вы можете очистить, когда закончите операцию. Возможно, встроенная база данных, такая как SQLLite, предоставит быстрое решение для сборки, но во время работы она может быть слишком медленной. Если вы перейдете по реляционному маршруту, проиндексируйте столько полей, сколько сможете. Производительность вставки не так важна, как поиск для ваших нужд. – Gusdor

Смежные вопросы