Este projeto não é mais mantido !!!
A OHC deve fornecer um bom desempenho tanto no hardware de commodities quanto nos grandes sistemas usando arquiteturas não uniformes de memória.
Ainda não há resultados de teste de desempenho disponíveis - você pode experimentar a ferramenta OHC -Benchmark. Veja as instruções abaixo. Uma impressão muito básica na velocidade está na seção _benchmarking_.
Java 8 vm que suporta 64 bits e tem sun.misc.Unsafe
.
O OHC é direcionado para Linux e OSX. Deve funcionar no Windows e em outros OSS Unix.
O OHC fornece duas implementações para diferentes características de entrada de cache: - A implementação _linked_ aloca a memória fora do heap para cada entrada individualmente e funciona melhor para entradas médias e grandes. - A implementação _Chunked_ aloca a memória fora da heap para cada segmento de hash como um todo e é destinado a pequenas entradas.
O número de segmentos é configurado via org.caffinitas.ohc.OHCacheBuilder
, padrão para # of cpus * 2
e deve ser uma potência de 2. Os acessos em cada segmento são sincronizados.
Cada entrada de mapa de hash é alocada individualmente. As entradas são gratuitas (desalocadas), quando não são mais referenciadas pelo próprio mapa fora do heap ou por qualquer referência externa como org.caffinitas.ohc.DirectValueAccess
ou org.caffinitas.ohc.CacheSerializer
.
O design desta implementação reduz o tempo bloqueado de um segmento para um tempo muito curto. Coloque/substitua as operações alocadas primeiro, ligue para o org.caffinitas.ohc.CacheSerializer
para serializar a chave e o valor e, em seguida, coloque a entrada totalmente preparada no segmento.
O despejo é realizado usando um algoritmo LRU. Uma lista vinculada através de todos os elementos em cache por segmento é usada para acompanhar as entradas mais velhas.
Implementação fora da alocação de memória em grama.
O objetivo desta implementação é reduzir a sobrecarga para entradas de cache relativamente pequenas em comparação com a implementação vinculada, uma vez que a memória para todo o segmento é pré-alocada. Esta implementação é adequada para pequenas entradas com implementações de serialização rápida (DE) de org.caffinitas.ohc.CacheSerializer
.
A segmentação é a mesma da implementação vinculada. O número de segmentos é configurado via org.caffinitas.ohc.OHCacheBuilder
, padrão para # of cpus * 2
e deve ser uma potência de 2. Os acessos em cada segmento são sincronizados.
Cada segmento é dividido em vários pedaços. Cada segmento é responsável por uma parte da capacidade total (capacity / segmentCount)
. Essa quantidade de memória é alocada uma vez adiante durante a inicialização e dividida logicamente em um número configurável de pedaços. O tamanho de cada pedaço é configurado usando a opção chunkSize
em org.caffinitas.ohc.OHCacheBuilder
.
Como a implementação vinculada, as entradas de hash são serializadas primeiro em um buffer temporário, antes que o put real em um segmento ocorra (as operações de segmento são sincronizadas).
Novas entradas são colocadas no pedaço de gravação atual. Quando esse pedaço estiver cheio, o próximo pedaço vazio se tornará o novo pedaço de gravação. Quando todos os pedaços estão cheios, o pedaço menos recentemente usado, incluindo todas as entradas que contém, é despejado.
A especificação das propriedades fixedKeyLength
e fixedValueLength
reduz a pegada de memória em 8 bytes por entrada.
As funções de serialização, acesso direto e get-with-carregador não são suportadas nesta implementação.
Para ativar a implementação em chunked, especifique o chunkSize
em org.caffinitas.ohc.OHCacheBuilder
.
NOTA: A implementação em chunked ainda deve ser considerada experimental.
O OHC suporta três algoritmos de despejo:
Use a classe OHCacheBuilder
para configurar todos os parâmetros necessários como
Geralmente você deve trabalhar com uma grande tabela de hash. Quanto maior a tabela de hash, mais curta a lista vinculada em cada partição de hash-isso significa menos caminhadas de ligação ligada e maior desempenho.
A quantidade total de memória de heap exigida é a tabela total de hash de capacidade total . Cada bucket de hash (atualmente) requer 8 bytes - então a fórmula é capacity + segment_count * hash_table_size * 8
.
O OHC aloca a memória fora do heap, ignorando diretamente a limitação de memória fora de Java. Isso significa que toda a memória alocada pelo OHC não é contada para -XX:maxDirectMemorySize
.
Como especialmente a implementação vinculada executa operações aloc/gratuitas para cada entrada individual, considere que a fragmentação da memória pode acontecer.
Deixe também algum espaço para a cabeça, pois algumas alocações ainda podem estar em voo e também "as outras coisas" (sistema operacional, JVM etc.) precisam de memória. Depende do padrão de uso quanto espaço de cabeça é necessário. Observe que a implementação vinculada aloca a memória durante as operações de gravação _Before_ ela é contada para os segmentos, que despejarão entradas mais antigas. Isso significa: não dedique toda a memória disponível ao OHC.
Recomendamos o uso do Jemalloc para manter a fragmentação baixa. Em sistemas operacionais UNIX, pré -carga Jemalloc.
O OSX geralmente não requer Jemalloc por motivos de desempenho. Também verifique se você está usando uma versão recente do Jemalloc - algumas distribuições Linux ainda fornecem versões bastante antigas.
Para pré-carregar o jemalloc no Linux, use export LD_PRELOAD=<path-to-libjemalloc.so
, para pré-carregar jemalloc no OSX, use export DYLD_INSERT_LIBRARIES=<path-to-libjemalloc.so
. Um modelo de script para pré -carregamento pode ser encontrado no projeto Apache Cassandra.
QuickStart:
Ohcache ohcache = ohcachebuilder.newbuilder () .KeySerializer (YourKeySerializer) .ValueSerializer (YourValueSerializer) .construir();
Este Quickstart usa a menos configuração padrão:
Consulte Javadoc do CacheBuilder
para obter uma lista completa de opções.
Os serializadores de chave e valor precisam implementar a interface CacheSerializer
. Esta interface tem três métodos:
int serializedSize(T t)
para retornar o tamanho serializado do objeto especificadovoid serialize(Object obj, DataOutput out)
para serializar o objeto fornecido à saída de dadosT deserialize(DataInput in)
para desserializar um objeto da entrada de dados Clone o repositório Git para sua máquina local. Use a filial mestre estável ou uma tag de liberação.
git clone https://github.com/snazy/ohc.git
Você precisa do OpenJDK 11 ou mais recente para construir a partir da fonte. Apenas execute
mvn clean install
Você precisa construir o OHC a partir da fonte, porque os grandes artefatos de referência não são carregados para o Maven Central.
Execute java -jar ohc-benchmark/target/ohc-benchmark-0.7.1-SNAPSHOT.jar -h
(ao construir a partir da fonte) para obter algumas informações de ajuda.
Geralmente, a ferramenta de benchmark inicia vários threads e executa operações _Get_ e _put_ usando as distribuições de chave configuráveis para _Get_ e _put_ operações. A distribuição do tamanho do valor também precisa ser configurada.
Opções de linha de comando disponíveis:
-cap <gg> tamanho do cache -d <gg> Duração de referência em segundos -h ajuda, imprima este comando -lf <gg> Hash Table Load Factor -r <gg> Ração de leitura-escrava (como um duplo 0..1 representando a chance de uma leitura) -rkd <gg> Hot Key Use Distribuição - Padrão: Uniforme (1..10000) -Sc <gg> Número de segmentos (número de mapas individuais fora de heap) -t <gg> threads para execução -vs <gg> Tamanhos de valor - Padrão: corrigido (512) -wkd <gg> Hot Key Use Distribuição - Padrão: Uniforme (1..10000) -wu <gg> Aquecer-<Secs>, <Secs> -z <gg> Tamanho da tabela de hash -CS <gg> Tamanho do bloco - se especificado, ele usará a implementação "rastreada" -fks <gg> corrigiu o tamanho da chave em bytes -fvs <gg> Tamanho do valor fixo em bytes -mes <gg> tamanho de entrada máxima em bytes -unl não use bloqueio -apenas apropada para o modo de thread único -hm <gg> algoritmo de hash para usar - murmur3, xx, crc32 -BH Show Bucket Historgram em estatísticas -kl <gg> Ative o histograma da balde. Padrão: false
Distribuições para teclas de leitura, teclas de gravação e tamanhos de valor podem ser configuradas usando as seguintes funções:
Exp (min..max) uma distribuição exponencial ao longo do intervalo [min..max] Extremo (min..max, forma) uma distribuição extrema (Weibull) sobre o intervalo [min..max] Qextreme (min..max, forma, quantas) um valor extremo, dividido em quantas, dentro do qual a chance de seleção é uniforme Gaussian (min..max, stdvrng) uma distribuição gaussiana/normal, onde média = (min+max)/2 e stdev é (média-min)/stdvrng Gaussian (min..max, média, stdev) Uma distribuição gaussiana/normal, com média explicitamente definida e stdev Uniforme (min..max) uma distribuição uniforme ao longo da faixa [min, max] Fixado (val) uma distribuição fixa, sempre retornando o mesmo valor Precedindo o nome com ~ inverterá a distribuição, por exemplo Aliases: extr, qextr, gauss, normal, norma, weibull
(Nota: estes são semelhantes à ferramenta de estresse Apache Cassandra - se você conhece um, conhece os dois;)
Exemplo rápido com uma taxa de leitura/gravação de .9
, aproximadamente 1,5 GB de capacidade máxima, 16 threads que dão 30 segundos:
Java -Jar OHC-benchmark/Target/OHC-benchmark-0.5.1-snapshot.jar
(Observe que a versão no nome do arquivo JAR pode ser diferente.)
Em um sistema Core i7 de 2,6 GHz (OSX), os seguintes números são típicos executando a referência acima (.9 Razão de leitura/gravação):
Ao usar um número muito grande de objetos em uma pilha muito grande, as máquinas virtuais sofrem com aumento da pressão do GC, pois basicamente precisa inspecionar todos os objetos, seja ele pode ser coletado e precisa acessar todas as páginas de memória. Um cache deve manter um conjunto quente de objetos acessíveis para acesso rápido (por exemplo, omitir disco ou tiras de redondos de rede). A única solução é usar a memória nativa - e você acabará com a opção de usar algum código nativo (C/C ++) via JNI ou usará o acesso direto à memória.
O código nativo usando C/C ++ via JNI tem a desvantagem que você precisa escrever naturalmente C/C ++ para cada plataforma. Embora a maioria dos UNIX OS (Linux, OSX, BSD, Solaris) seja bastante semelhante ao lidar com coisas como bibliotecas de comparação e troca ou POSIX, você geralmente também deseja suportar a outra plataforma (Windows).
Tanto o código nativo quanto o acesso direto na memória têm a desvantagem que eles precisam "deixar" o contexto da JVM " - quer dizer que o acesso à memória fora da pilha é mais lento do que o acesso a dados na pilha Java e que cada chamada JNI tem alguns" escapar do contexto da JVM "Custo.
Mas a memória fora da pilha é ótima quando você precisa lidar com uma quantidade enorme de vários/muitos GB de memória de cache, pois esse dose não pressiona o coletor de lixo Java. Deixe o Java GC fazer seu trabalho para o aplicativo, onde esta biblioteca faz seu trabalho para os dados em cache.
Tl; dr alocando a memória fora da heap direta e ignorando ByteBuffer.allocateDirect
é muito suave com o GC e temos controle explícito sobre a alocação de memória e, mais importante, livre. A implementação de ações em Java libera a memória fora do pêlo durante uma coleção de lixo-também: se não houver mais memória fora de heap, provavelmente desencadeia um GC completo, o que é problemático se vários threads executarem essa situação simultaneamente, pois significa lotes de GCS completo sequencialmente. Além disso, a implementação de ações usa uma lista vinculada global e sincronizada para rastrear alocações de memória fora do heap.
É por isso que o OHC aloca diretamente a memória fora do heap e recomenda a pré-carga do Jemalloc nos sistemas Linux para melhorar o desempenho do gerenciamento de memória.
O OHC foi desenvolvido em 2014/15 para o Apache Cassandra 2.2 e 3.0 para serem usados como o novo back-end da linha de linha.
Como não havia implementações de cache totalmente fora de fora, foi decidido construir um completamente novo - e esse é o OHC. Mas o OHC por si só também pode ser utilizável para outros projetos - é por isso que o OHC é uma biblioteca separada.
Um grande 'obrigado' tem que ir a Benedict Elliott Smith e Ariel Weisberg da DataStax por sua entrada muito útil para o OHC!
Ben Manes, o autor de Caffeine, o cache altamente configurável no heap usando LFU minúsculo.
Desenvolvedor: Robert Stupp
Copyright (C) 2014 Robert Stupp, Koeln, Alemanha, Robert-Stupp.De
Licenciado sob a licença Apache, versão 2.0 (a "licença"); Você não pode usar esse arquivo, exceto em conformidade com a licença. Você pode obter uma cópia da licença em
http://www.apache.org/license/license-2.0
A menos que exigido pela lei aplicável ou acordada por escrito, o software distribuído sob a licença é distribuído "como está", sem garantias ou condições de qualquer tipo, expressa ou implícita. Consulte a licença para o idioma específico que rege as permissões e limitações sob a licença.