Desempenho de compressão GZip

Em meu projeto atual, muitos arquivos de texto são lidos, gravados e atualizados na unidade local. Alguns desses arquivos podem se tornar muito grandes e reterão dados redundantes. Esses dois aspectos o tornam o candidato perfeito para um algoritmo de compactação de arquivo. GZip é um algoritmo de compactação de esvaziamento comumente usado na web para diminuir o tamanho da carga útil e compactar arquivos individuais.

Para testar as diferenças de desempenho, executei 100.000 ciclos compactando e lendo um documento HTML de amostra, a página inicial do Echovoice . Durante a execução das operações, um thread separado de baixa prioridade monitorou o uso de RAM e CPU do sistema por meio de PerformanceCounters em c #. Os contadores de desempenho monitoraram todo o sistema, que estava em um estado ocioso (exceto para o processo atual), que capturou o arquivo lido e gravado externamente ao processo de teste. Para este projeto, isso é representativo do caso de uso real.

Com o documento de amostra carregado na memória, o arquivo foi primeiro gravado na unidade local 100.000 vezes e, em seguida, cada documento foi lido usando texto padrão e GZip compactado. Um cronômetro monitorava o tempo. O código completo pode ser visto abaixo.

Os resultados para os 100.000 ciclos estão abaixo (estes são os totais):

type    length [B]  write [ms]  read [ms]
GZip 3572 112708 42474
TXT
17758 57396 28166

O arquivo GZip é aproximadamente 80% menor em tamanho do que o arquivo de texto, mas os tempos de gravação e leitura quase dobraram. Ainda assim, o GZip está gravando no arquivo a uma taxa de pouco mais de um milissegundo por arquivo, o que inclui o tempo de compactação.

Os desempenhos de CPU e RAM não foram tão distantes quanto eu esperava. Novamente, isso ocorre em todo o sistema, portanto, os valores não são tão confidenciais quanto poderiam ser, se apenas inspecionassem o processo do projeto de teste. Mas, no entanto, houve muito pouco aumento visto na CPU para as operações de leitura e gravação para GZip. No entanto, houve um aumento significativo na RAM.

Os resultados para os 100.000 ciclos estão abaixo (são médias):

type    write RAM [MB]  write CPU [%]   read RAM [MB]   read CPU [%]
GZip 9758 29.79 9429 26.19
TXT
8331 26.47 7312 26.22

O uso da compressão depende do seu aplicativo. Para mim, o pequeno impacto no desempenho da RAM compensou a troca por tamanhos de arquivo muito menores. Mas, este é um assistente de desenvolvimento que será executado em computadores com grande quantidade de memória. Além disso, este projeto não exigirá que esta operação seja executada continuamente, portanto, o tamanho da memória persistente é um fator muito maior. Sua milhagem pode variar. O GZip pode não ser a resposta se seu projeto estiver sendo executado em um pequeno servidor em nuvem ou se houver um grande volume de arquivos de tamanho menor sendo gravados.

Os gráficos a seguir mostram o desempenho lado a lado, conforme foram coletados durante as operações.

Cenário

Cenário

Cenário

Cenário

Cenário

Cenário

Cenário

Cenário

E o código completo:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sandbox
{
class Program
{
static void Main(string[] args)
{
//number of iterations
int x = 100000;

//create the test directories
Directory.CreateDirectory(@"C:\EVLogs\GZ vs Text Peformance\testgzs");
Directory.CreateDirectory(@"C:\EVLogs\GZ vs Text Peformance\testtxt");

//store the diagnostics
List<IODiagnostics> diagnostics = new List<IODiagnostics>();
diagnostics
.Add(new IODiagnostics("GZip"));
diagnostics
.Add(new IODiagnostics("TXT"));

//get a string to write (https://echovoice.com) at the time of this post
string html = getEVHTML();

//validate the html is not null or empty
if (!string.IsNullOrWhiteSpace(html))
{
//write GZ to file x times
Performance performance = new Performance();
performance
.start();
Stopwatch timer = Stopwatch.StartNew();
for (int i = 0; i < x; i++)
WriteGZipStream(html, i);
timer
.Stop();
diagnostics
[0].write_elapsed_milliseconds = timer.ElapsedMilliseconds;
performance
.stop();
while (performance.isAlive)
Thread.Sleep(10);
diagnostics
[0].write_performance = performance;

//reset the stopwatch
performance
= new Performance();
performance
.start();
timer
.Reset();
timer
.Start();

//read all GZ files
for (int i = 0; i < x; i++)
ReadGZipStream(i);
timer
.Stop();
diagnostics
[0].read_elapsed_milliseconds = timer.ElapsedMilliseconds;
performance
.stop();
while (performance.isAlive)
Thread.Sleep(10);
diagnostics
[0].read_performance = performance;

//check file length
diagnostics
[0].file_length = new FileInfo(@"C:\EVLogs\GZ vs Text Peformance\testgzs\gz1.gz").Length;

//write TXT to file x times
performance
= new Performance();
performance
.start();
timer
.Reset();
timer
.Start();
for (int i = 0; i < x; i++)
writeTextFile
(html, i);
timer
.Stop();
diagnostics
[1].write_elapsed_milliseconds = timer.ElapsedMilliseconds;

performance
.stop();
while (performance.isAlive)
Thread.Sleep(10);
diagnostics
[1].write_performance = performance;

//reset
performance
= new Performance();
performance
.start();
timer
.Reset();
timer
.Start();

//read all TXT files
for (int i = 0; i < x; i++)
readTextFile
(i);
timer
.Stop();
diagnostics
[1].read_elapsed_milliseconds = timer.ElapsedMilliseconds;
performance
.stop();
while (performance.isAlive)
Thread.Sleep(10);
diagnostics
[1].read_performance = performance;

//check file length
diagnostics
[1].file_length = new FileInfo(@"C:\EVLogs\GZ vs Text Peformance\testtxt\txt1.txt").Length;

//get performance averages
diagnostics
[0].avg_write_RAM_performance = diagnostics[0].write_performance.getRAMperformance();
diagnostics
[0].avg_write_CPU_performance = diagnostics[0].write_performance.getCPUperformance();
diagnostics
[0].avg_read_RAM_performance = diagnostics[0].read_performance.getRAMperformance();
diagnostics
[0].avg_read_CPU_performance = diagnostics[0].read_performance.getCPUperformance();
diagnostics
[1].avg_write_RAM_performance = diagnostics[1].write_performance.getRAMperformance();
diagnostics
[1].avg_write_CPU_performance = diagnostics[1].write_performance.getCPUperformance();
diagnostics
[1].avg_read_RAM_performance = diagnostics[1].read_performance.getRAMperformance();
diagnostics
[1].avg_read_CPU_performance = diagnostics[1].read_performance.getCPUperformance();

//write the results
using (StreamWriter sw = new StreamWriter(string.Format("results_x{0}.txt", x)))
{
sw
.WriteLine(string.Format("File Read/Write Operation Times for {0} Cycles", x));
sw
.WriteLine();
sw
.WriteLine("type\tlength [B]\twrite [ms]\tread [ms]\tavg write RAM\tavg write CPU\tavg read RAM\tavg read CPU");
sw
.WriteLine("---------------------------------------------------------------------------------------------------------------------------------");
sw
.WriteLine(diagnostics[0].ToString());
sw
.WriteLine(diagnostics[1].ToString());
}

//write the points for graphical display
writePerformancePoints
(string.Format("GZ_Write_{0}.csv", x), diagnostics[0].write_performance);
writePerformancePoints
(string.Format("GZ_Read_{0}.csv", x), diagnostics[0].read_performance);
writePerformancePoints
(string.Format("TXT_Write_{0}.csv", x), diagnostics[1].write_performance);
writePerformancePoints
(string.Format("TXT_Read_{0}.csv", x), diagnostics[1].read_performance);

//clean-up
cleanup
(@"C:\EVLogs\GZ vs Text Peformance\testgzs");
cleanup
(@"C:\EVLogs\GZ vs Text Peformance\testtxt");
}
}

private static void WriteGZipStream(string text, int index)
{
//create and open GZStream
using (GZipStream stream = new GZipStream(File.OpenWrite(string.Format(@"C:\EVLogs\GZ vs Text Peformance\testgzs\gz{0}.gz", index)), CompressionMode.Compress))
{
//write to the stream using streamwriter
using (StreamWriter sw = new StreamWriter(stream))
sw
.Write(text);
}
}

private static void ReadGZipStream(int index)
{
//open the file and decrompress
using (GZipStream stream = new GZipStream(File.OpenRead(string.Format(@"C:\EVLogs\GZ vs Text Peformance\testgzs\gz{0}.gz", index)), CompressionMode.Decompress))
{
//use streamreader to read from file
using (StreamReader sr = new StreamReader(stream))
{
string line;
while ((line = sr.ReadLine()) != null)
{
//don't do anything with the lines
}
}
}
}

private static void writeTextFile(string text, int index)
{
//create file open stream and write using streamwriter
using (StreamWriter sw = new StreamWriter(string.Format(@"C:\EVLogs\GZ vs Text Peformance\testtxt\txt{0}.txt", index), true))
sw
.WriteLine(text);
}

private static void readTextFile(int index)
{
//open the file and read using streamreader
using (StreamReader sr = new StreamReader(string.Format(@"C:\EVLogs\GZ vs Text Peformance\testtxt\txt{0}.txt", index)))
{
string line;
while ((line = sr.ReadLine()) != null)
{
//don't do anything with the lines
}
}
}

private static string getEVHTML()
{
//make sure the file exists
if (File.Exists("index.html"))
{
//use stringbuilder to build the HTML string
StringBuilder sb = new StringBuilder();

//read from the html file
using (StreamReader r = new StreamReader("index.html"))
{
//store the line and append to the stringbuilder
string line;
while ((line = r.ReadLine()) != null)
sb
.Append(line);
}
return sb.ToString();
}
//file doesn't exist, return null
return null;
}

private static void cleanup(string path)
{
//get the files, delete the files, remove the directory
string[] files = Directory.GetFiles(path);
for (int i = 0; i < files.Length; i++)
File.Delete(files[i]);
Directory.Delete(path);
}

private static void convertToGZip()
{
//convert files in this directory to GZips
string[] files = Directory.GetFiles(Directory.GetCurrentDirectory());

//verify there are files
if (files != null && files.Length > 0)
{
//iterate over the files
for (int i = 0; i < files.Length; i++)
{
//create a temporary file to dump compressed data into
string temp_file = Path.GetTempFileName();

//open a regular streamreader for the current text file
using (StreamReader reader = new StreamReader(files[i]))
{
//create stream for new file (currently in temp dir)
using (GZipStream stream = new GZipStream(File.OpenWrite(temp_file), CompressionMode.Compress))
{
//write to the temporary file
using (StreamWriter writer = new StreamWriter(stream))
{
//read from the original and copy into the temporary file
string line;
while ((line = reader.ReadLine()) != null)
{
writer
.WriteLine(line);
}
}
}
}

//delete the original file
File.Delete(files[i]);

//move the temp file to the original file location (note: this renames the file as well, will retain same extension as previous)
File.Move(temp_file, files[i]);
}
}
}

private static void writePerformancePoints(string file_name, Performance performance)
{
//validate the input
if (!string.IsNullOrWhiteSpace(file_name) && performance != null && performance.performance != null && performance.performance.Count > 0)
{
//create a stream writer
using (StreamWriter writer = new StreamWriter(file_name))
{
writer
.WriteLine("Seconds,RAM [MB],CPU [%]");

//iterate and add to the file
for (int i = 0; i < performance.performance.Count; i++)
{
writer
.WriteLine(i +