Как освободить память после конвертации в base64 - PullRequest
1 голос
/ 25 марта 2011

Я пытаюсь передать содержимое файла. Код работает для файлов меньшего размера, но для файлов большего размера возникает ошибка «Недостаточно памяти».

public void StreamEncode(FileStream inputStream, TextWriter tw)
{
    byte[] base64Block = new byte[BLOCK_SIZE];
    int bytesRead = 0;

    try
    {
        do
        {
            // read one block from the input stream
            bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

            // encode the base64 string
            string base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

            // write the string
            tw.Write(base64String);

        } while (bytesRead == base64Block.Length);
    }
    catch (OutOfMemoryException)
    {
        MessageBox.Show("Error -- Memory used: " + GC.GetTotalMemory(false) + " bytes");
    }
}

Я могу выделить проблему и наблюдать, как используемая память растет по мере зацикливания.
Кажется, проблема в вызове Convert.ToBase64String().

Как освободить память для преобразованной строки?


Отредактировано отсюда вниз ... Вот обновление. Я также создал новую ветку об этом - извините, я думаю, это было неправильно.

Спасибо за ваши замечательные предложения. Из предложений я сократил размер буфера, используемого для чтения из файла, и похоже, что потребление памяти лучше, но я все еще вижу проблему с OOM, и я вижу эту проблему с файлами размером до 5 МБ. Я потенциально хочу иметь дело с файлами в десять раз больше.

Кажется, моя проблема сейчас связана с использованием TextWriter.

Я создаю запрос следующим образом [с несколькими правками, чтобы уменьшить код]:

HttpWebRequest oRequest = (HttpWebRequest)WebRequest.Create(new Uri(strURL));
oRequest.Method = httpMethod;
oRequest.ContentType = "application/atom+xml";
oRequest.Headers["Authorization"] = getAuthHeader();
oRequest.ContentLength = strHead.Length + strTail.Length + longContentSize;
oRequest.SendChunked = true;

using (TextWriter tw = new StreamWriter(oRequest.GetRequestStream()))
{
    tw.Write(strHead);
    using (FileStream fileStream = new FileStream(strPath, FileMode.Open, 
           FileAccess.Read, System.IO.FileShare.ReadWrite))
    {
        StreamEncode(fileStream, tw);
    }
    tw.Write(strTail);
}
.....

Что вызывает в рутине:

public void StreamEncode(FileStream inputStream, TextWriter tw)
{
    // For Base64 there are 4 bytes output for every 3 bytes of input
    byte[] base64Block = new byte[9000];
    int bytesRead = 0;
    string base64String = null;

    do
    {
        // read one block from the input stream
        bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

        // encode the base64 string
        base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

        // write the string
        tw.Write(base64String);


    } while (bytesRead !=0 );

}

Должен ли я использовать что-то кроме TextWriter из-за потенциально большого контента? Кажется, очень удобно создавать всю полезную нагрузку запроса.

Это совершенно неправильный подход? Я хочу иметь возможность поддерживать очень большие файлы.

Ответы [ 7 ]

4 голосов
/ 25 марта 2011

Если вы используете BLOCK_SIZE, который составляет 32 КБ или более, вы будете создавать строки размером 85 КБ или более, которые размещаются в куче больших объектов.Краткосрочные объекты должны жить в обычных кучах, а не в куче больших объектов, так что это может быть причиной проблем с памятью.

Кроме того, я вижу две возможные проблемы с кодом:

  • В кодировке base64 используется заполнение в конце строки, поэтому, если вы разбиваете поток на биты и конвертируете в строки base64, а затем записываете строки в поток, вы не получитеодин поток base64.

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

1 голос
/ 25 марта 2011

Дикая догадка ... HttpWebRequest.AllowWriteStreamBuffering по умолчанию имеет значение true, и в соответствии с MSDN «установка AllowWriteStreamBuffering в значение true может вызвать проблемы производительности при загрузке больших наборов данных, поскольку буфер данных может использовать всю доступную память». Попробуйте установить oRequest.AllowWriteStreamBuffering = false и посмотрим, что получится.

1 голос
/ 25 марта 2011

Имейте в виду, что при преобразовании данных в base64 результирующая строка будет на 33% длиннее (при условии, что входной размер кратен 3, что, вероятно, является хорошей идеей в вашем случае). Если BLOCK_SIZE слишком велик, может быть недостаточно непрерывной памяти для хранения результирующей строки base-64.

Попробуйте уменьшить BLOCK_SIZE, чтобы каждый элемент base-64 был меньше, чтобы было легче выделить для него память.

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

0 голосов
/ 25 марта 2011

Я бы сначала записал результат во временный файл.

using (TextWriter tw = new StreamWriter(oRequest.GetRequestStream()))
{
    tw.Write(strHead);
    var tempPath = Path.GetTempFileName();
    try
    {
        using (var input = File.OpenRead(strPath))
        using (var output = File.Open(
            tempPath, FileMode.Open, FileAccess.ReadWrite))
        {
            StreamEncode(fileStream, output);
            output.Seek(0, SeekOrigin.Begin);
            CopyTo(output, ((StreamWriter)tw).BaseStream);
        }
    }
    finally
    {
        File.Delete(tempPath);
    }
    tw.Write(strTail);
}

public void StreamEncode(Stream inputStream, Stream output)
{
    // For Base64 there are 4 bytes output for every 3 bytes of input
    byte[] base64Block = new byte[9000];
    int bytesRead = 0;
    string base64String = null;

    using (var tw = new StreamWriter(output))
    {
        do
        {
            // read one block from the input stream
            bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

            // encode the base64 string
            base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

            // write the string
            tw.Write(base64String);

        } while (bytesRead !=0 );
    }

}


static void CopyTo(Stream input, Stream output)
{
    const int length = 10240;
    byte[] buffer = new byte[length];
    int count = 0;

    while ((count = input.Read(buffer, 0, length)) > 0)
        output.Write(buffer, 0, count);
}
0 голосов
/ 25 марта 2011

Код выглядит нормально с точки зрения использования памяти, но я думаю, что вы передаете средство записи для потока, основанного на памяти (например, MemoryStream), и сохранение данных там вызывает исключение OOM.

Если значение BLOCK_SIZE превышает 86 Кб, выделения будут происходить в куче больших объектов (LOH), это изменит поведение выделений, но само по себе не должно вызывать OOM.

Примечание: ваше конечное условие неверно - должно быть bytesRead! = 0, в общем случае Read может вернуть меньше байтов, чем запрашивается, даже если осталось больше данных. Также FileStream никогда не делает это, насколько мне известно.

0 голосов
/ 25 марта 2011

Попробуйте уменьшить размер блока или не присваивайте результат вызова Convert переменной:

bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);
tw.Write(Convert.ToBase64String(base64Block, 0, bytesRead));
0 голосов
/ 25 марта 2011

Попробуйте вытащить объявление base64String из цикла.Если это по-прежнему не помогает, попробуйте вызвать сборщик мусора после стольких итераций.

GC.Collect ();GC.WaitForPendingFinalizers ();

...