Использование полей массива вместо огромного количества объектов - PullRequest
7 голосов
/ 10 июля 2011

В свете этой статьи , мне интересно, что люди думают о хранении массивных наборов данных (скажем,> 10 000 000 объектов) в памяти, используя массивы для хранения полей данных вместо создания экземпляров миллионов объектов и стоек увеличение объема памяти (скажем, 12-24 байта на объект, в зависимости от того, какую статью вы прочитали). Данные по свойствам варьируются от предмета к предмету, поэтому я не могу использовать строгий шаблон Flyweight, но могу предположить нечто подобное.

Моя идея такого рода представления заключается в том, что у человека есть «шаблонный объект» ...

class Thing
{
  double A;
  double B;
  int    C;
  string D;
}

А затем объект-контейнер с методом создания объекта по запросу ...

class ContainerOfThings
{
  double[] ContainerA;
  double[] ContainerB;
  int[]    ContainerC;
  string[] ContainerD;

  ContainerOfThings(int total)
  {
    //create arrays
  }

  IThing GetThingAtPosition(int position)
  {
     IThing thing = new Thing(); //probably best done as a factory instead
     thing.A = ContainerA[position];
     thing.B = ContainerB[position];
     thing.C = ContainerC[position];
     thing.D = ContainerD[position];

     return thing;
  }
}

Так что это простая стратегия, но не очень универсальная, например, нельзя создать подмножество (в виде списка) «Thing», не дублируя данные и не отказываясь от цели хранения полей массива. Я не смог найти хороших примеров, поэтому я был бы признателен за ссылки или отрывки кода из лучших способов справиться с этим сценарием от кого-то, кто это сделал ... или за лучшую идею.

Ответы [ 8 ]

5 голосов
/ 10 июля 2011

Это зависит от вашего конкретного сценария.В зависимости от того, как часто создаются ваши объекты, вы можете:

  1. Если объекты сериализуются, сохраните их в MemoryMappedFile (получая некоторое сочетание средней / низкой производительности и низкого потребления памяти).

  2. Сопоставить поля между различными объектами: я имею в виду, если объект изначально имеет значения по умолчанию, все они находятся в отдельной базе и действительно выделяют новое пространство, если это значение отличается от значения по умолчанию.(это естественно для ссылочных типов).

  3. Другое решение снова сохранить объекты в базе SqlLite.Намного проще управлять, чем MemoryMappedFiles, поскольку вы можете использовать простой SQL.

Выбор за вами, поскольку он зависит от ваших конкретных требований к проекту.

С уважением.

5 голосов
/ 18 июля 2011

В свете этой статьи мне интересно, как люди воспринимают хранение массивных наборов данных (скажем,> 10 000 000 объектов) в памяти, используя массивы для хранения полей данных вместо создания экземпляров миллионов объектов и увеличения объема памяти… .

Я полагаю, что есть несколько способов приблизиться к этому, и, действительно, вы находитесь на возможном решении ограничить данные в памяти. Тем не менее, я не уверен, что сокращение вашей структуры даже на 24? байты принесут вам много хорошего. Ваша структура составляет около 79 байтов (для строки из 15 символов) = 8 + 8 + 4 + 24? + 4 + 1 + (2 * длина символа), поэтому ваш общий выигрыш в лучшем случае составляет 25%. Это не кажется очень полезным, поскольку вам нужно быть в положении, когда 10 миллионов * 80 байт помещается в память, а 10 миллионов * 100 байт - нет. Это будет означать, что вы разрабатываете решение, которое находится на грани катастрофы, слишком много больших строк или слишком много записей, или какая-то другая программа перегружает память, а на вашей машине недостаточно памяти.

Если вам требуется поддержка произвольного доступа к n ​​небольшим записям, где n = 10 миллионов, то вы должны стремиться проектировать как минимум 2n или 10n. Возможно, вы уже рассматриваете это в свои 10 миллионов? В любом случае существует множество технологий, которые могут поддерживать доступ к данным такого типа.

Одна из возможностей - если строка ограничена максимальной длиной (мл) разумного размера (скажем, 255), тогда вы можете пойти в простое хранилище ISAM. Каждая запись будет 8 + 8 + 4 + 255 байт, и вы можете просто сместить в плоский файл, чтобы прочитать их. Если размер записи является переменным или, возможно, большим, вы можете использовать для этого другой формат хранения и сохранять смещения в файле.

Другая возможность - если вы просматриваете значения по какому-либо ключу, я бы порекомендовал что-то вроде встроенной базы данных или BTree, в которой вы можете отключить часть согласованности диска, чтобы повысить производительность. Как это случилось, я написал BPlusTree для клиентских кэшей больших объемов данных. Подробная информация о с использованием дерева B + здесь .

2 голосов
/ 14 июля 2011

На самом деле ADO.NET DataTable использует аналогичный подход для хранения данных. Может быть, вы должны посмотреть, как это реализовано там. Итак, вам понадобится объект типа DataRow, который внутренне содержит указатель на таблицу и индекс данных строки. Это было бы самое легкое решение, которое я считаю.

В вашем случае: a) Если вы создаете Thing каждый раз, когда вызываете метод GetThingAtPosition, вы создаете объект в куче, который удваивает информацию, уже находящуюся в вашей таблице. Плюс «данные об объекте».

b) Если вам нужен доступ к каждому элементу в вашем ContainerOfThings, то необходимая память будет удвоена + 12 байтов * количество объектов наверху. В таком сценарии было бы лучше иметь простой набор вещей, не создавая их на лету.

1 голос
/ 19 июля 2011

Вы создаете массив из System.Array с элементом для каждого свойства вашего типа.Размер этих подмассивов равен количеству объектов, которые у вас есть.Доступ к свойству:

masterArray [propertyIndex] [objectIndex]

Это позволит вам использовать массивы типов значений вместо массивов объекта.

1 голос
/ 18 июля 2011

[обновление 2011-07-19]

доступна новая версия: http://www.mediafire.com/file/74fxj7u1n0ppcq9/MemStorageDemo-6639584-2011_07_19-12_47_00.zip

Я все еще пытаюсь отладить некоторые из повторных операций, что раздражает, но из свежей сессии xUnit я могу запустить тест, который создает 10 миллионов объектов (мне кажется, у меня сейчас сокращен размер строки для тестирования, но у меня было 10 миллионов строк с переменной длиной от 3 до 15 байт, у меня еще не было возможности попробовать что-то большее. В моей системе я шел от нагрузки примерно 1,95 г до 2,35 г с 10 миллионами объектов, и я до сих пор ничего не сделал со строками, кроме очень простого вспомогательного класса, который использует фактические управляемые строки.

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

В любом случае, вот основные идеи:

  1. MemoryArray класс: использует неуправляемую память, выделенную Marhsal.AllocHGlobal() для хранения структур. я тестировал с одним большим MemoryArray, но для каждого члена есть только несколько полей, и я думаю, что пока вы сохраняете размер массива достаточно большим, для его разделения не будет большой разницы в потреблении памяти. MemoryArray реализует IEnumerable<MemoryArrayItem>, что я и использовал для заполнения массивов при тестировании.

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

MemoryArray имеет только стандартный индексатор, который захватывает запрошенный элемент с помощью математического указателя на Int Ptr, который представляет заголовок неуправляемых данных, которые выделяются при построении. Я также реализовал условный двумерный индексатор, где вы можете передать массив целых чисел, представляющих измерения, в массив, а затем выполнить над ним A [x, y]. это всего лишь небольшой пример того, как это может работать.

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

  1. MemoryArrayEnumerator класс: я решил реализовать фактические перечислители вместо функций перечислителя. класс перечислителя в основном просто берет MemoryArray, а затем предоставляет функции-счетчики для перечисления фактических MemoryArrayItem объектов.

  2. MemoryArrayItem class: этот класс не делает ничего другого, кроме как содержит информацию о подходящем указателе, основанную на начале и позиции в массиве. это конкретные классы, которые реализуются поверх (в стороне от?) этого объекта, которые фактически выполняют указатель для вывода данных.

затем есть еще несколько классов поддержки, MemoryStringArray - это резервный блок памяти переменного размера, который в настоящее время создает только строки, а также есть класс автоматического удаления (AutoDisposer) и универсальный класс для обработки. прикрепляет и отсоединяет. (AutoReference<>).

Теперь на вершине этого фундамента находятся специальные классы. каждый из трех типов (массив / перечислитель / элемент) реализован специально для объектов, которые вы просматриваете. в более масштабной версии этого проекта, от которой я отказался и для которой это стало шипом, у меня есть более общая обработка смещений и тому подобное, так что вы не так привязаны к конкретным классам, но даже если они есть, они довольно полезно; моей первоначальной реализацией были все конкретные классы без реальной базы.

В настоящее время у меня все они реализованы как отдельные классы, на которые вы передаете ссылки; Итак, класс TestArray передается классу MemoryArray в своем конструкторе. то же самое с перечислителем и товаром. я знаю, что там будет сохранена некоторая вычислительная мощность, и я думаю, что есть хорошая возможность и для экономии места, если я найду хороший способ реализовать их как потомков, а не просто иметь копию базовых классов. хотя сначала я хотел получить базовое представление об этом, и это, казалось, было самым простым способом. проблема в том, что это еще один слой косвенности.

TestArray и TestArrayEnumerator оказались не слишком многим, кроме как просто пройти через функции MemoryArray и MemoryArrayEnumerator. главная проблема в этих классах - просто вырезать указатели и распределить их по элементам, которые его используют.

но, таким образом, TestArrayItem - это место, где указатели фактически переключаются на реальные данные; вот файл я отрубил большой раздел комментариев, в котором рассматриваются некоторые варианты, чтобы лучше обращаться с резервным хранилищем переменной длины (все еще в фактическом файле по приведенной выше ссылке), и, пожалуйста, извините за множество комментариев, где я оставляю свои заметки о о чем я думаю, работая над этим:)

TestArrayItem.cs

// ----------------------------------------------
//      rights lavished upon all with love
//         see 'license/unlicense.txt'
//   ♥ 2011, shelley butterfly - public domain
// ----------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MemStorageDemo
{
    public unsafe class TestArrayItem
    {
        public static MemoryStringArray s_MemoryArrayStore = new MemoryStringArray();

        public static int TestArrayItemSize_bytes =
            sizeof(double) * 2
            + sizeof(int)
            + sizeof(int);

        // hard-coding here; this is another place that things could be a little more generic if you wanted and if 
        // performance permitted; for instance, creating a dictionary of offsets based on index.  also, perhaps
        // a dictionary with key strings to allow indexing using field names of the object.
        private enum EFieldOffset
        {
            DoubleTheFirstOffset       =  0,
            DoubleTheSecondOffset      =  8,
            IntTheFirstOffset          = 16,
            StringTheFirstHandleOffset = 20
        }

        private MemoryArrayItem myMemoryArrayItem;
        private MemoryStringArray myStringStore;

        // constructor that uses the static string array store
        public TestArrayItem(MemoryArrayItem parMemoryArrayItem) :
            this(parMemoryArrayItem, s_MemoryArrayStore)
        {
        }

        // constructor for getting the item at its memory block without any initialization (e.g. existing item)
        public TestArrayItem(MemoryArrayItem parMemoryArrayItem, MemoryStringArray parStringStore)
        {
            myMemoryArrayItem = parMemoryArrayItem;
            myStringStore = parStringStore;
        }

        // constructor for geting the item at its memory block and initializing it (e.g. adding new items)
        public TestArrayItem(MemoryArrayItem parMemoryArrayItem, double parDoubleTheFirst, double parDoubleTheSecond, int parIntTheFirst, string parStringTheFirst)
        {
            myMemoryArrayItem = parMemoryArrayItem;

            DoubleTheFirst = parDoubleTheFirst;
            DoubleTheSecond = parDoubleTheSecond;
            IntTheFirst = parIntTheFirst;
            StringTheFirst = parStringTheFirst;
        }

        // if you end up in a situation where the compiler isn't giving you equivalent performance to just doing
        // the array math directly in the properties, you could always just do the math directly in the properties.
        //
        // it reads much cleaner the way i have it set up, and there's a lot less code duplication, so without 
        // actually determining empirically that i needed to do so, i would stick with the function calls.
        private IntPtr GetPointerAtOffset(EFieldOffset parFieldOffset)
            { return myMemoryArrayItem.ObjectPointer + (int)parFieldOffset; }

        private double* DoubleTheFirstPtr 
            { get { return (double*)GetPointerAtOffset(EFieldOffset.DoubleTheFirstOffset); } }
        public double DoubleTheFirst
        {
            get
            {
                return *DoubleTheFirstPtr;
            }

            set
            {
                *DoubleTheFirstPtr = value;
            }
        }

        private double* DoubleTheSecondPtr
            { get { return (double*)GetPointerAtOffset(EFieldOffset.DoubleTheSecondOffset); } }
        public double DoubleTheSecond
        {
            get
            {
                return *DoubleTheSecondPtr;
            }
            set
            {
                *DoubleTheSecondPtr = value;
            }
        }

        // ahh wishing for a preprocessor about now
        private int* IntTheFirstPtr
            { get { return (int*)GetPointerAtOffset(EFieldOffset.IntTheFirstOffset); } }
        public int IntTheFirst
        {
            get
            {
                return *IntTheFirstPtr;
            }
            set
            {
                *IntTheFirstPtr = value;
            }
        }

        // okay since we're using the StringArray backing store in the example, we just need to get the
        // pointer stored in our blocks, and then copy the data from that address 
        private int* StringTheFirstHandlePtr 
            { get { return (int*)GetPointerAtOffset(EFieldOffset.StringTheFirstHandleOffset); } }
        public string StringTheFirst
        {
            get
            {
                return myStringStore.GetString(*StringTheFirstHandlePtr);
            }
            set
            {
                myStringStore.ModifyString(*StringTheFirstHandlePtr, value);
            }
        }

        public void CreateStringTheFirst(string WithValue)
        {
            *StringTheFirstHandlePtr = myStringStore.AddString(WithValue);
        }

        public override string ToString()
        {
            return string.Format("{0:X8}: {{ {1:0.000}, {2:0.000}, {3}, {4} }} {5:X8}", (int)DoubleTheFirstPtr, DoubleTheFirst, DoubleTheSecond, IntTheFirst, StringTheFirst, (int)myMemoryArrayItem.ObjectPointer);
        }
    }
}

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

в любом случае, вот и все, надеюсь, я вернусь к Интернету этим вечером или завтра и сделаю все возможное, чтобы зарегистрироваться. К сожалению, собираюсь вернуть кабельный модем обратно в кабельную компанию, поэтому не буду быть в бизнесе, если мы не пойдем в Макдональдс или что-то еще :) надеюсь, что это была какая-то помощь; я буду отлаживать проблемы с ним, чтобы была хотя бы функциональная база; я знаю, что это не первый раз, когда я думаю о написании такой библиотеки, и я думаю, что другие тоже.


предыдущий контент

Я использовал нечто подобное для использования библиотеки COM, которую я создал, для взаимодействия с предварительно скомпилированной Win32 / 64 FFTW dll. для этого вопроса нам действительно нужно что-то более общее, чем то, что у меня было, поэтому я начал работать над чем-то на прошлой неделе, что было бы достаточно в качестве приличной универсальной библиотеки для этих типов использования, с расширяемым управлением памятью, многомерностью, нарезкой, и т. д.

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

Я собираюсь продолжить работу над примером на этой неделе, и я обновлю здесь новый код, и я постараюсь извлечь из него достаточно информации, чтобы сделать пост, объясняющий это, но если вам все равно интересно Последнее, что я, вероятно, смогу опубликовать до позже на этой неделе:

http://www.mediafire.com/file/a7yq53ls18q7bvf/EfficientStorage-6639584.zip

это просто решение VS2010 с базовыми потребностями для выполнения того, что, по моему мнению, отвечает вашим потребностям.есть много комментариев, но не стесняйтесь задавать вопросы, и я проверю, как только у меня будет Интернет ... в любом случае, это был забавный проект.

после завершения примера, который я собираюсь закончитьотключить полную библиотеку первой итерации и выпустить ее куда-нибудь;я обновлю здесь ссылку, когда она у меня будет.

обязательное предупреждение: оно компилируется, но не было проверено;Я уверен, что есть проблемы, это довольно большая тема.

1 голос
/ 17 июля 2011

Я сделал такую ​​вещь для проекта rapidSTORM, где необходимо кэшировать несколько миллионов малонаселенных объектов (микроскопия локализации). Хотя я не могу дать вам хорошие фрагменты кода (слишком много зависимостей), я обнаружил, что реализация с Boost Fusion была очень быстрой и понятной. Слияние структуры, построение вектора для каждого типа элемента, а затем написать довольно простой метод доступа для этого вектора, который реконструировал каждый элемент.

(О, я только что заметил, что вы пометили вопрос, но, возможно, мне поможет и мой ответ на C ++)

1 голос
/ 10 июля 2011

К сожалению, OO не может абстрагироваться от проблем с производительностью (насыщение полосы пропускания равно одному).Это удобная парадигма, но она имеет ограничения.

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

Могу ли я отослать вас к сообществу J?См .:

http://www.JSoftware.com.

Это не группа C # (или Java).Они хорошая группа.Как правило, массив должен рассматриваться как объект первого класса.В C # это не так гибко.Это может быть разочаровывающей структурой для работы с C #.

Существуют различные модели ОО для проблем с большими наборами данных ... но если вы задаете такой вопрос, возможно, пришло время перейти к более функциональным.Или, по крайней мере, функционально для решения проблем / прототипирования.

1 голос
/ 10 июля 2011

Ваш вопрос подразумевает, что есть проблема. Использование памяти оказалось проблемой?

Если 100 байт на элемент, то это звучит как 1 ГБ. Так что мне интересно о приложении и если это проблема. Работает ли приложение на выделенном 64-битном компьютере, скажем, с 8 ГБ или оперативной памятью?

Если есть страх, вы можете проверить его с помощью интеграционного теста. Приведите 20 миллионов таких предметов и проведите несколько тестов производительности.

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

Увидимся

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...