Проблемы с производительностью в FileStream.Write при записи байтов, декодированных с помощью AsciiEncoding.GetBytes и Convert.FromBase64String

Я столкнулся с проблемой производительности при использовании функции FileStream.Write.

У меня есть консольное приложение, которое я использую для чтения строки Base64 из файла (размер ~ 400 КБ) с помощью объекта StreamReader. Я конвертирую эту строку в массив байтов с помощью Convert.FromBase64String. Затем я записываю этот байтовый массив в файл с помощью объекта FileStream. Полученная длина массива байтов составила 334991.

Я измерил время, затраченное на запись массива байтов, и оказалось, что оно составляет примерно 0,116 секунды.

Просто для удовольствия я получил массив байтов из той же закодированной в Base64 строки с помощью функции ASCIIEncoding.GetBytes (хотя я знал, что это не даст правильного вывода DECODED - я просто хотел попробовать). Я записал этот байтовый массив в файл с помощью объекта FileStream. Полученная длина массива байтов составила 458414.

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

Вот пример кода:

class Program
{
    static void Main(string[] args)
    {
        Stopwatch stopWatch = new Stopwatch();
        TimeSpan executionTime;

        StreamReader sr = new StreamReader("foo.txt");
        string sampleString = sr.ReadToEnd();
        sr.Close();

        ////1. Convert to bytes using Base64 Decoder (The real output!)
        //byte[] binaryData = Convert.FromBase64String(sampleString);

        //2. Convert to bytes using AsciiEncoding (Just for Fun!)
        byte[] binaryData = new System.Text.ASCIIEncoding().GetBytes(sampleString);
        Console.WriteLine("Byte Length: " + binaryData.Length);

        stopWatch.Start();
        FileStream fs = new FileStream("bar.txt", FileMode.Create, FileAccess.Write);
        fs.Write(binaryData, 0, binaryData.Length);
        fs.Flush();
        fs.Close();
        stopWatch.Stop();

        executionTime = stopWatch.Elapsed;
        Console.WriteLine("FileStream Write - Total Execution Time: " + executionTime.TotalSeconds.ToString());
        Console.Read();
    }
}

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

Длина массива байтов, полученного с помощью Convert.FromBase64String, меньше, чем длина, полученная с помощью функции ASCIIEncoding.GetBytes.

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

Или я что-то ужасно не так делаю? Пожалуйста, порекомендуйте.


person amit-agrawal    schedule 15.05.2009    source источник


Ответы (4)


Во-первых, DateTime имеет низкое разрешение (iirc 0,018 с). Так что лучше использовать класс секундомера.

Это не полностью объясняет разницу, но вы можете получить более точные цифры.

person Henk Holterman    schedule 15.05.2009
comment
Верно, Хенк, я тоже это заметил, но нервничал;) - person RandomNickName42; 15.05.2009
comment
Спасибо, Хенк. Я изменил тестовый код, чтобы использовать класс StopWatch, обновленная информация теперь отражена в исходном посте. - person amit-agrawal; 15.05.2009

Я дал аналогичный совет по другому вопросу, посмотрите эти инструменты и ссылки от MS Research.

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

Кроме того, вы должны внимательно следить за проблемами, связанными с CLR БОЛЬШАЯ куча объектов. Особенно при использовании массивов (все, что превышает ~ 80 КБ, имеет неоптимальные взаимодействия с управляемой кучей, если вы запускали это 5000 раз в одном и том же процессе).

Однако на самом деле, посмотрев еще раз, я не думаю, что они так тесно связаны с вашей леммой. Я запустил этот код в профилировщике, и он просто показывает, что Convert. Base64 использует все ваши циклы.

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

person RandomNickName42    schedule 15.05.2009

Я думаю, что основная проблема в вашем коде заключается в том, что вы пытаетесь сравнить капусту с морковью (французское выражение):

Convert.FromBase64String и ASCIIEncoding (). GetBytes вообще не делают то же самое.

Просто попробуйте использовать любой текстовый файл в качестве входных данных для вашей программы, и он не сработает на FromBase64 при нормальной работе с ASCIIEncoding.

Теперь объясним, почему производительность снижается:

  • ASCIIEncoding (). GetBytes просто берет один символ из вашего файла и конвертирует его в байт (что довольно просто: делать нечего). Например, он переведет 'A' в 0x41 и 'Z' в 0x5A ...

  • Для Convert.FromBase64String это отдельная история. Это действительно перевод «строки в кодировке base64» в массив байтов. Строка base64 - это, скажем, «печатное представление двоичных данных. Лучше, это« текстовое »представление двоичных данных, которое позволяет, например, пересылать их по Интернету. Изображения в сообщениях электронной почты кодируются в формате base64, потому что почтовые протоколы основаны на тексте, поэтому процесс преобразования назад / вперед base64 в байты совсем не прост, что снижает производительность.

Fyi, строка base64 должна выглядеть примерно так:

SABlAGwAbABvAHcAIABXAG8AcgBsAGQAIQA =

что переводится как "Hello World!" не сразу, не так ли?

Вот некоторая информация о формате base64: http://en.wikipedia.org/wiki/Base64

Надеюсь это поможет

person odalet    schedule 15.05.2009
comment
привет, если вы заметили выше, измерение времени не включает преобразование строки base64 в массив байтов, поэтому я не уверен, стоит ли ваше объяснение снижения производительности. а также я упомянул, что сравниваю капусту и морковь, просто чтобы выделить проблему, связанную с производительностью. - person amit-agrawal; 15.05.2009
comment
Сама моя ошибка: забудьте об этом, я действительно недостаточно хорошо прочитал ваш вопрос ... - person odalet; 15.05.2009

Возможно, вы захотите взглянуть на серию статей (и сопутствующий исходный проект), которые недавно написал Джон Скит по этой проблеме.

здесь и здесь

В частности, он сравнивал буферизацию и потоковую передачу, но также были получены интересные результаты с разными размерами файлов и количеством потоков.

person Steven Evers    schedule 15.05.2009