Este artigo comparará o desempenho de vários algoritmos de compressão comumente usados. Os resultados mostram que alguns algoritmos ainda funcionam corretamente sob restrições extremamente exigentes da CPU.
As comparações no artigo incluem:
JDK GZIP - Este é um algoritmo lento com alta taxa de compressão, e os dados comprimidos são adequados para uso a longo prazo. java.util.zip.gzipinputStream / gzipOutputStream no JDK é a implementação desse algoritmo.
JDK Deflate - Este é outro algoritmo no JDK (esse algoritmo é usado em arquivos ZIP). O que o torna diferente do GZIP é que você pode especificar o nível de compactação do algoritmo, para que você possa equilibrar o tempo de compressão e o tamanho do arquivo de saída. Os níveis opcionais são 0 (não compactados) e 1 (comprimido rápido) a 9 (comprimido lento). Sua implementação é java.util.zip.deflateroututputStream/inflaterInputStream.
A implementação Java do algoritmo de compressão LZ4 - essa é a velocidade de compressão mais rápida entre os algoritmos introduzidos neste artigo. Comparado com o desviar mais rápido, seus resultados de compressão são um pouco piores.
Snappy - Este é um algoritmo de compactação muito popular desenvolvido pelo Google. O objetivo é fornecer algoritmos de compressão com velocidade relativamente boa e taxa de compressão.
Teste de compressão
Também demorei muito tempo para descobrir quais arquivos são adequados para testes de compressão de dados e existem nos computadores da maioria dos desenvolvedores de Java (não quero que você tenha algumas centenas de megabytes de arquivos para executar este teste). Por fim, pensei que a maioria das pessoas deveria ter a documentação para o JDK instalado localmente. Portanto, decidi mesclar todo o diretório Javadoc em um arquivo - emperando todos os arquivos. Isso pode ser feito facilmente com o comando tar, mas nem todo mundo é um usuário do Linux, então escrevi um programa para gerar este arquivo:
classe pública inputGenerator {private estático final string javadoc_path = "your_path_to_jdk/docs"; public static final file file_path = new File ("your_output_file_path"); estático {tente {if (! file_path.exists ()) makejavadocfile (); } catch (ioexception e) {e.printStackTrace (); }} private estático void makejavadocfile () lança IoException {try (outputStream OS = new BufferoudOutputStream (new FileOutputStream (FILE_PATH), 65536)) {AppendDir (OS, novo arquivo (Javadoc_Phath)); } System.out.println ("arquivo javadoc criado"); } private estático void apênddir (sistema operacional final de saída, root de arquivo final) lança ioexception {for (file f: root.listfiles ()) {if (f.isdirectory ()) appenddir (os, f); else files.copy (f.topath (), OS); }}}O tamanho de todo o arquivo na minha máquina é de 354.509.602 bytes (338 MB).
teste
No começo, eu queria ler o arquivo inteiro na memória e compactá -lo. No entanto, os resultados mostram que mesmo as máquinas 4G podem ficar facilmente sem espaço para a memória.
Por isso, decidi usar o cache do arquivo do sistema operacional. A estrutura de teste que usamos aqui é JMH. Este arquivo será carregado no cache pelo sistema operacional durante a fase de aquecimento (será comprimido duas vezes na fase de aquecimento). Vou compactar o conteúdo no fluxo de bytearrayoutputStream (sei que essa não é a maneira mais rápida, mas é relativamente estável para cada teste e não leva tempo para escrever os dados compactados no disco), portanto, é necessário algum espaço de memória para armazenar essa saída.
Abaixo está a classe base da classe de teste. Todos os testes são diferentes apenas nas diferentes implementações do fluxo de saída compactado, para que você possa reutilizar essa classe base de teste e apenas gerar um fluxo a partir da implementação do StreamFactory:
@OutputTimeUnit (timeUnit.millisEconds) @state (scope.thread) @fork (1) @warmup (iterations = 2) @Measurement (iterações = 3) @benchmarkmode (mode.singleshottime) public class) TestParent {protegido M_IninFile; @Setup public void setup () {m_inputFile = inputGenerator.file_path.topath (); } interface streamfactory {public outputStream getStream (Final OutputStream UnderingingStream) lança IoException; } public int BaseBenchmark (Final StreamFactory Factory) lança IoException {Try (ByteArrayOutputStream Bos = novo byteArrayOutputStream ((int) m_inputfile.tofile (). Length (); O outputStream OS = FATORY.GETSTREAM (BOS)) {files.copy (M_Inputes); os.flush (); return bos.size (); }}}Esses casos de teste são muito semelhantes (seu código -fonte está disponível no final do artigo) e apenas um exemplo está listado aqui - a classe de teste do JDK deflate;
classe pública JDKDeflatEtest estende o testParent {@param ({"1", "2", "3", "4", "5", "6", "7", "8", "9"}) public int m_lvl; @Benchmark public int Deflate () lança IoException {return BaseBenchmark (new StreamFactory () {@Override public OutputStream getStream (outputStream UnderingStream) lança ioException {final Deflater DFLater = newflater (m_lvl, verdadeiro); }}Resultados do teste
O tamanho do arquivo de saída
Primeiro, vejamos o tamanho do arquivo de saída:
|| implementação || tamanho do arquivo (bytes) |||| gzip || 64.200.201 |||| snappy (normal) || 138.250.196 |||| snappy (emoldurado) || 101.470,113 ||||| lz4 (rápido) || 98.316.501 |||| lz4 (alto) || 82.076.909 ||| deflatar (lvl = 1) || 78,369,711 |||| (lvl = 4) || 68.090.059 || || deflate (lvl = 5) || 65.699.810 |||| deflate (lvl = 6) || 64.200,191 ||||| || 63.839.200 |||
Pode -se observar que o tamanho do arquivo varia bastante (de 60 MB a 131MB). Vamos dar uma olhada em quanto tempo leva para diferentes métodos de compressão.
Tempo de compressão
|| Implementação || Compressão Time (ms) |||| snappy.framedOutput || 2264.700 |||| snappy.NormalOutput || 2201.120 |||| lz4.testFastnative || 1056.326 |||| lz4.testfastunsa Fe || 1346.835 |||| lz4.testfastSafe || 1917.929 |||| lz4.testhighnative || 7489.958 |||| lz4.testhighunsafe || 10306.973 |||| || 14413.622 |||| deflate (lvl = 1) || 4522.644 |||| deflate (lvl = 2) || 4726.477 ||| || 7896.572 |||| deflate (lvl = 6) || 9783.701 |||| deflate (lvl = 7) || 10731.761 ||||| || 10351.887 ||
Em seguida, mesclamos o tempo de compressão e o tamanho do arquivo em uma tabela para contar a taxa de transferência do algoritmo e ver quais conclusões podem ser tiradas.
Taxa de transferência e eficiência
|| implementação || tempo (ms) || tamanho do arquivo não compactado || taxa de transferência (mb/s) ||| tamanho do arquivo compactado (mb) |||| snappy.NormalOutput || 2201.12 || 338 || || 149.2471409017 || 96.7693328857 |||| lz4.testfastnative || 1056.326 || 338 || 319.9769768045 || 93.757220459 | || 176.2317583185 || 93.7557220459 ||||| lz4.TestFastUNSAVE || 1346.835 || 338 || 250.h. || 45.1270888301 || 78.2680511475 |||| lz4.testhighsafe || 14413.622 || 338 || 23.4500391366 || 78.268011475 | || 32.7933332124 || 78.2680511475 |||| deflatar (lvl = 1) || 4522.644 || 338 || 74.7350443679 || 74.73961768 | || 71.5120374012 || 71.7735290527 |||| deflatar (lvl = 3) || 5081.934 || 338 || 66.5101120951 || 69.8477336 | || 50.1524605124 || 64.9452209473 |||| deflate (lvl = 5) || 7896.572 || 338 || 42.8033835442 || 62.6564025879 ||| || 34.5472536415 || 61.2258911133 |||| || 22.8991689295 || 60.8825683594 |||| deflate (lvl = 9) || 14878.364 || 338 || 22.7175514727 || 60.8730316162 | || 61.2258911133 ||
Como pode ser visto, a maioria das implementações é muito ineficiente: nos processadores Xeon E5-2650, o deflate de alto nível é de cerca de 23 MB/s, e até o GZIP é de apenas 33 MB/s, o que provavelmente é difícil de satisfazer. Ao mesmo tempo, o algoritmo de defalte mais rápido pode atingir cerca de 75 MB/s, o Snappy é 150 MB/s e LZ4 (Fast, implementação do JNI) pode atingir um incrível 320 MB/s!
Pode ser visto claramente na tabela que atualmente existem duas implementações que estão em desvantagem: Snappy é mais lento que o LZ4 (compactação rápida) e o arquivo compactado é maior. Pelo contrário, o LZ4 (alta taxa de compressão) é mais lento que o deflate dos níveis 1 para 4, e o tamanho do arquivo de saída é muito maior que o de esvaziar do nível 1.
Portanto, se você precisar executar "compressão em tempo real", eu definitivamente escolherei na implementação do JNI LZ4 (FAST) ou do nível 1. Obviamente, se sua empresa não permitir bibliotecas de terceiros, você só poderá usar o deflate. Você também precisa considerar de maneira abrangente quantos recursos gratuitos da CPU existem e onde os dados compactados são armazenados. Por exemplo, se você deseja armazenar dados compactados em disputa de disputa, o desempenho de 100 MB/s acima não é útil para você (assumindo que seu arquivo seja grande o suficiente) - a velocidade do HDD se tornará um gargalo. Se o mesmo arquivo for emitido para o disco rígido SSD - até o LZ4 parecerá muito lento na frente dele. Se você deseja comprimir os dados primeiro e depois enviá -los para a rede, é melhor escolher LZ4, porque o desempenho de compressão do Deflate75MB/S é realmente pequeno em comparação com a taxa de transferência de 125 MB/
Resumir
Se você acha que a compactação de dados é muito lenta, pode considerar a implementação LZ4 (rápida), que pode atingir velocidades de compressão de texto de cerca de 320 MB/s - essa velocidade de compressão não deve ser percebida para a maioria dos aplicativos.
Se você está limitado a não poder usar bibliotecas de terceiros ou apenas deseja ter uma solução de compressão um pouco melhor, considere o uso do JDK esflatar (LVL = 1) para codificação e decodificação - o mesmo arquivo pode comprimir o mesmo arquivo para 75Mb/s.
código -fonte
Código fonte do teste de compressão Java