C # OutOfMemory, сопоставленный файл памяти или временная база данных - PullRequest
2 голосов
/ 26 февраля 2012

Нужен совет, лучшая практика и т.д ...

Технология: C # .NET4.0, Winforms, 32 бит

Я ищу несколько советов о том, как лучше всего справиться с обработкой больших данных в моем приложении C # Winforms, которое испытывает высокое использование памяти (рабочий набор) и случайное исключение OutOfMemory.

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

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

  2. Для каждого товара, для каждого дня с момента его появления на складе рассчитывают различные метрики, которые строят исторический профиль для каждого товара в корзине.

В результате мы можем выполнить сотни, тысячи и / или миллионы вычислений в зависимости от количества товаров в «корзине покупок». Если в корзине слишком много товаров, мы рискуем попасть в исключение «OutOfMemory».

Несколько предостережений ;

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

  2. Несмотря на то, что мы выполняем шаги 1 и 2 в фоновом потоке, скорость важна, так как количество товаров в «корзине покупок» может значительно повлиять на общую скорость расчета.

  3. Память извлекается сборщиком мусора .NET при закрытой «корзине покупок». Мы профилировали нашу заявку и гарантируем, что все ссылки правильно расположены и закрыты, когда корзина закрыта.

  4. После завершения всех вычислений результирующие данные сохраняются в IDictionary. «CalculatedData - это объект класса, свойства которого представляют собой отдельные метрики, рассчитанные с помощью вышеуказанного процесса.

Некоторые идеи, о которых я думал;

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

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

  • Файлы с отображением в памяти
    Одна идея состояла в том, чтобы использовать отображенные в памяти файлы, которые будут хранить словарь данных. Будет ли это возможно / выполнимо, и как мы можем поставить это на место?

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

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

Любой совет приветствуется ....

Ответы [ 3 ]

0 голосов
/ 26 февраля 2012

Если вы заинтересованы в использовании метода отображения файлов в памяти, вы можете попробовать его сейчас. Я написал небольшой собственный пакет .NET с именем MemMapCache, который по сути создает базу данных ключ / val, поддерживаемую MemMappedFiles. Это немного хакерская концепция, но программа MemMapCache.exe сохраняет все ссылки на файлы, отображенные в памяти, чтобы в случае сбоя вашего приложения вам не приходилось беспокоиться о потере состояния вашего кэша.

Его очень просто использовать, и вы должны иметь возможность добавить его в свой код без особых изменений. Вот пример использования его: https://github.com/jprichardson/MemMapCache/blob/master/TestMemMapCache/MemMapCacheTest.cs

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

Пожалуйста, дайте мне знать, если вы в конечном итоге используете его. Я был бы заинтересован в ваших результатах.

Однако в долгосрочной перспективе я бы порекомендовал Redis.

0 голосов
/ 23 сентября 2012

Как обновление для тех, кто наткнулся на эту тему ...

В итоге мы использовали SQLite в качестве решения для кэширования. Используемая нами база данных SQLite существует отдельно от основного хранилища данных, используемого приложением. Мы сохраняем вычисленные данные в SQLite (diskCache) по мере необходимости и имеем код, контролирующий аннулирование кэша и т. Д. Это было для нас подходящим решением, поскольку мы смогли достичь скорости записи и около 100 000 записей в секунду.

Для тех, кто заинтересован, это код, который управляет вставками в diskCache. Полный балл за этот код принадлежит JP Richardson (показано здесь, отвечая на вопрос) за его превосходное сообщение в блоге.

internal class SQLiteBulkInsert
{
#region Class Declarations

private SQLiteCommand m_cmd;
private SQLiteTransaction m_trans;
private readonly SQLiteConnection m_dbCon;

private readonly Dictionary<string, SQLiteParameter> m_parameters = new Dictionary<string, SQLiteParameter>();

private uint m_counter;

private readonly string m_beginInsertText;

#endregion

#region Constructor

public SQLiteBulkInsert(SQLiteConnection dbConnection, string tableName)
{
    m_dbCon = dbConnection;
    m_tableName = tableName;

    var query = new StringBuilder(255);
    query.Append("INSERT INTO ["); query.Append(tableName); query.Append("] (");
    m_beginInsertText = query.ToString();
}

#endregion

#region Allow Bulk Insert

private bool m_allowBulkInsert = true;
public bool AllowBulkInsert { get { return m_allowBulkInsert; } set { m_allowBulkInsert = value; } }

#endregion

#region CommandText

public string CommandText
{
    get
    {
        if(m_parameters.Count < 1) throw new SQLiteException("You must add at least one parameter.");

        var sb = new StringBuilder(255);
        sb.Append(m_beginInsertText);

        foreach(var param in m_parameters.Keys)
        {
            sb.Append('[');
            sb.Append(param);
            sb.Append(']');
            sb.Append(", ");
        }
        sb.Remove(sb.Length - 2, 2);

        sb.Append(") VALUES (");

        foreach(var param in m_parameters.Keys)
        {
            sb.Append(m_paramDelim);
            sb.Append(param);
            sb.Append(", ");
        }
        sb.Remove(sb.Length - 2, 2);

        sb.Append(")");

        return sb.ToString();
    }
}

#endregion

#region Commit Max

private uint m_commitMax = 25000;
public uint CommitMax { get { return m_commitMax; } set { m_commitMax = value; } }

#endregion

#region Table Name

private readonly string m_tableName;
public string TableName { get { return m_tableName; } }

#endregion

#region Parameter Delimiter

private const string m_paramDelim = ":";
public string ParamDelimiter { get { return m_paramDelim; } }

#endregion

#region AddParameter

public void AddParameter(string name, DbType dbType)
{
    var param = new SQLiteParameter(m_paramDelim + name, dbType);
    m_parameters.Add(name, param);
}

#endregion

#region Flush

public void Flush()
{
    try
    {
        if (m_trans != null) m_trans.Commit();
    }
    catch (Exception ex)
    {
        throw new Exception("Could not commit transaction. See InnerException for more details", ex);
    }
    finally
    {
        if (m_trans != null) m_trans.Dispose();

        m_trans = null;
        m_counter = 0;
    }
}

#endregion

#region Insert

public void Insert(object[] paramValues)
{
    if (paramValues.Length != m_parameters.Count) 
        throw new Exception("The values array count must be equal to the count of the number of parameters.");

    m_counter++;

    if (m_counter == 1)
    {
        if (m_allowBulkInsert) m_trans = m_dbCon.BeginTransaction();
        m_cmd = m_dbCon.CreateCommand();

        foreach (var par in m_parameters.Values)
            m_cmd.Parameters.Add(par);

        m_cmd.CommandText = CommandText;
    }

    var i = 0;
    foreach (var par in m_parameters.Values)
    {
        par.Value = paramValues[i];
        i++;
    }

    m_cmd.ExecuteNonQuery();

    if(m_counter != m_commitMax)
    {
        // Do nothing
    }
    else
    {
        try
        {
            if(m_trans != null) m_trans.Commit();
        }
        catch(Exception)
        { }
        finally
        {
            if(m_trans != null)
            {
                m_trans.Dispose();
                m_trans = null;
            }

            m_counter = 0;
        }
    }
}

#endregion

}

0 голосов
/ 26 февраля 2012

Самое простое решение - это база данных, возможно, SQLite.Файлы, отображаемые в память, автоматически не становятся словарями, вам придется самостоятельно кодировать все управление памятью и, таким образом, бороться с самой системой .net GC за владение данными.

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