фонЭффективная реализация приспособленец
Один из наиболее часто используемых структур данных в нашем приложении является пользовательский пункт структура. В последнее время мы сталкиваемся с проблемами памяти, в основном из-за чрезмерного количества экземпляров этой структуры.
Многие из этих экземпляров содержат одинаковые данные. Совместное использование одного экземпляра значительно облегчит использование памяти. Однако, поскольку мы используем 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 экземпляр для каждого из них , - Производительность не должна быть намного хуже, чем работать непосредственно с примитивными данными в структуре.
- Строговую семантику следует поддерживать.
- Интерфейс «Точка» должен оставаться тем же (то есть не следует изменять классы, которые используют «Точку»).
Есть ли способ улучшить наш подход к этим критериям? Или кто-нибудь может предложить другой подход, который мы можем предпринять?
Не имеет отношения к вашему вопросу, но: Это ужасный метод хэш-кода. Хэш-код для целого числа - это значение самого целого. Поэтому, если X или Y равно нулю, хэш-код будет равен нулю. Я предполагаю, что это может вызвать множество столкновений в контейнерах хеширования - например, словарь, который вы используете !. Вы должны использовать что-то вроде 'return X + 31 * Y;' –
@MatthewWatson Я обычно XOR HashCodes полей. Будет ли это более уместным? – Gusdor
@Gusdor XOR не очень хорош, потому что он будет равен нулю для ВСЕХ случаев, где X == Y. Если у вас есть набор данных, так что есть много случаев, когда X == Y вы получите много столкновений. –