¡Este proyecto ya no se mantiene!
OHC proporcionará un buen rendimiento tanto en el hardware de productos básicos como en los sistemas grandes utilizando arquitecturas no uniformes de memoria.
Todavía no hay resultados de prueba de rendimiento disponibles: puede probar la herramienta OHC -Benchmark. Vea las instrucciones a continuación. Una impresión muy básica en la velocidad está en la sección _benchmarking_.
Java 8 VM que admite 64 bits y tiene sun.misc.Unsafe (Oracle JVMS en X64 Intel CPU).
OHC está dirigido a Linux y OSX. Debería funcionar en Windows y otros OSS Unix.
OHC proporciona dos implementaciones para diferentes características de entrada de caché: - La implementación _linked_ asigna la memoria fuera del montón para cada entrada individualmente y funciona mejor para entradas medianas y grandes. - La implementación _Chunked_ asigna la memoria fuera del montón para cada segmento hash en su conjunto y está destinado a entradas pequeñas.
El número de segmentos se configura a través de org.caffinitas.ohc.OHCacheBuilder , predeterminado a # of cpus * 2 y debe ser una potencia de 2. Las entradas se distribuyen sobre los segmentos utilizando los bits más significativos del código de hash de 64 bits. Los accesos en cada segmento están sincronizados.
Cada entrada del mapa hash se asigna individualmente. Las entradas son gratuitas (desanimadas), cuando ya no son referenciadas por el mapa fuera de la altura o cualquier referencia externa como org.caffinitas.ohc.DirectValueAccess o un org.caffinitas.ohc.CacheSerializer .
El diseño de esta implementación reduce el tiempo bloqueado de un segmento a muy poco tiempo. Ponga/reemplace las operaciones de asignación de memoria primero, llame a los org.caffinitas.ohc.CacheSerializer para serializar la clave y el valor y luego colocar la entrada completamente preparada en el segmento.
El desalojo se realiza utilizando un algoritmo LRU. Se utiliza una lista vinculada a través de todos los elementos en caché por segmento para realizar un seguimiento de las entradas mayores.
Implementación de asignación de memoria fragmentada.
El propósito de esta implementación es reducir la sobrecarga para entradas de caché relativamente pequeñas en comparación con la implementación vinculada, ya que la memoria para todo el segmento está previamente alocada. Esta implementación es adecuada para entradas pequeñas con implementaciones de serialización rápida (DE) de org.caffinitas.ohc.CacheSerializer .
La segmentación es la misma que en la implementación vinculada. El número de segmentos se configura a través de org.caffinitas.ohc.OHCacheBuilder , predeterminado a # of cpus * 2 y debe ser una potencia de 2. Las entradas se distribuyen sobre los segmentos utilizando los bits más significativos del código de hash de 64 bits. Los accesos en cada segmento están sincronizados.
Cada segmento se divide en múltiples trozos. Cada segmento es responsable de una porción de la capacidad total (capacity / segmentCount) . Esta cantidad de memoria se asigna una vez por adelantado durante la inicialización y se divide lógicamente en un número configurable de fragmentos. El tamaño de cada fragmento está configurado utilizando la opción chunkSize en org.caffinitas.ohc.OHCacheBuilder .
Al igual que la implementación vinculada, las entradas hash se serializan en un búfer temporal primero, antes de que ocurra el puesto real en un segmento (las operaciones de segmento se sincronizan).
Se colocan nuevas entradas en el fragmento de escritura actual. Cuando ese fragmento esté lleno, el próximo fragmento vacío se convertirá en el nuevo fragmento de escritura. Cuando todos los trozos están llenos, el fragmento menos utilizado, incluidas todas las entradas que contiene, se desalija.
La especificación de las propiedades de constructor fixedKeyLength y fixedValueLength reduce la huella de la memoria en 8 bytes por entrada.
La serialización, el acceso directo y las funciones Get-With-Loader no son compatibles con esta implementación.
Para habilitar la implementación fragmentada, especifique el chunkSize en org.caffinitas.ohc.OHCacheBuilder .
Nota: La implementación fragmentada aún debe considerarse experimental.
OHC admite tres algoritmos de desalojo:
Use la clase OHCacheBuilder para configurar todos los parámetros necesarios como
En general, debe trabajar con una gran mesa hash. Cuanto más grande sea la tabla hash, más corta es la lista vinculada en cada partición hash, eso significa caminatas de enlace menos vinculadas y mayor rendimiento.
La cantidad total de la memoria de montón requerida es la capacidad total más la tabla hash . Cada cubo hash (actualmente) requiere 8 bytes, por lo que la fórmula es capacity + segment_count * hash_table_size * 8 .
OHC asigna la memoria fuera del montón sin pasar por alto la limitación de memoria fuera del montón de Java. Esto significa que toda la memoria asignada por OHC no se cuenta para -XX:maxDirectMemorySize .
Dado que especialmente la implementación vinculada realiza operaciones Alloc/gratuitas para cada entrada individual, considere que la fragmentación de memoria puede ocurrir.
También deje un espacio para la cabeza ya que algunas asignaciones aún pueden estar en vuelo y también "las otras cosas" (sistema operativo, JVM, etc.) necesitan memoria. Depende del patrón de uso de la cantidad de cabecera necesaria. Tenga en cuenta que la implementación vinculada asigna la memoria durante las operaciones de escritura _before_ Se cuenta para los segmentos, lo que desalojará las entradas anteriores. Esto significa: no dedique toda la memoria disponible a OHC.
Recomendamos usar Jemalloc para mantener baja la fragmentación. En los sistemas operativos UNIX, Preload Jemalloc.
OSX generalmente no requiere Jemalloc por razones de rendimiento. También asegúrese de estar utilizando una versión reciente de Jemalloc: algunas distribuciones de Linux todavía proporcionan versiones bastante antiguas.
Para precargar jemalloc en Linux, use export LD_PRELOAD=<path-to-libjemalloc.so , para precargar jemalloc en OSX, use export DYLD_INSERT_LIBRARIES=<path-to-libjemalloc.so . Se puede encontrar una plantilla de script para la precarga en el proyecto Apache Cassandra.
Rápido:
Ohcache Ohcache = OhcacheBuilder.newBuilder ()
. Keyserializer (YourKeySerializer)
. Valorializador (Your ValuueSerializer)
.construir();
Este rato rápido utiliza la configuración menos predeterminada:
Consulte Javadoc de CacheBuilder para obtener una lista completa de opciones.
Los serializadores de clave y valor deben implementar la interfaz CacheSerializer . Esta interfaz tiene tres métodos:
int serializedSize(T t) para devolver el tamaño serializado del objeto dadovoid serialize(Object obj, DataOutput out) para serializar el objeto dado a la salida de datosT deserialize(DataInput in) para deserializar un objeto de la entrada de datos Clonar el repositorio de GIT a su máquina local. Use la rama maestra estable o una etiqueta de lanzamiento.
git clone https://github.com/snazy/ohc.git
Necesita OpenJDK 11 o más nuevo para construir desde la fuente. Solo ejecutar
mvn clean install
Debe construir OHC desde la fuente porque los grandes artefactos de referencia no están cargados para Maven Central.
Ejecute java -jar ohc-benchmark/target/ohc-benchmark-0.7.1-SNAPSHOT.jar -h (al construir desde la fuente) para obtener información de ayuda.
En general, la herramienta de referencia inicia un montón de subprocesos y realiza operaciones _get_ y _put_ simultáneamente utilizando distribuciones de clave configurables para operaciones _get_ y _put_. La distribución del tamaño del valor también debe configurarse.
Opciones de línea de comandos disponibles:
-cap <gg> tamaño del caché -d <gg> Duración de referencia en segundos -H ayuda, imprima este comando -lf <gg> factor de carga de tabla hash -r <gg> Ración de lectura-escritura (como un doble 0..1 que representa la posibilidad de una lectura) -rkd <gg> Hot Key Use Distribution - Predeterminado: Uniforme (1..10000) -Sc <GR> Número de segmentos (número de mapas individuales fuera de tiempo) -t <gg> hilos para la ejecución -vs <gh> tamaños de valor - predeterminado: fijo (512) -wkd <gg> Hot Key Use Distribution - Predeterminado: Uniforme (1..10000) -wu <gg> Calentamiento-<Work-Secs>, <Secs-Secs> -Z <gg> Tamaño de la tabla de hash -Cs <Gr> Tamaño del fragmento: si se especifica, utilizará la implementación "fragmentada" -fks <gg> Tamaño clave fijo en bytes -fvs <gg> Tamaño de valor fijo en bytes -mes <gg> tamaño de entrada máximo en bytes -UNL no usa el bloqueo: solo apropiado para el modo de subproceso único -hm <gg> algoritmo hash para usar - Murmur3, xx, CRC32 -bh show bucket histram en estadísticas -Kl <GR> Habilitar histograma de cubo. Valor predeterminado: Falso
Las distribuciones para claves de lectura, claves de escritura y tamaños de valor se pueden configurar utilizando las siguientes funciones:
Exp (min..max) Una distribución exponencial en el rango [min..max] Extremo (min..max, forma) una distribución de valor extremo (Weibull) sobre el rango [min..max] Qextreme (min..max, forma, cuantas) un valor extremo, dividido en cuantas, dentro de la cual la posibilidad de selección es uniforme Gaussian (min..max, stdvrng) una distribución gaussiana/normal, donde media = (min+max)/2, y stdev es (media-min)/stdvrng Gaussian (min..max, medio, stdev) una distribución gaussiana/normal, con media explícitamente definida y stdev Uniforme (min..max) Una distribución uniforme sobre el rango [min, max] Fijo (val) una distribución fija, siempre devolviendo el mismo valor Antes del nombre con ~ invertirá la distribución, por ejemplo, exp (1..10) producirá 10 más, en lugar de menos, a menudo Alias: extr, qextr, gauss, normal, norma, weibull
(Nota: estos son similares a la herramienta de estrés Apache Cassandra: si conoce una, sabe ambos;)
Ejemplo rápido con una relación de lectura/escritura de .9 , aproximadamente 1.5GB de capacidad máxima, 16 hilos que se ejecuta durante 30 segundos:
Java -jar OHC-Benchmark/Target/OHC-Benchmark-0.5.1-Snapshot.jar
(Tenga en cuenta que la versión en el nombre del archivo JAR podría diferir).
En un sistema Core i7 de 2.6GHz (OSX), los siguientes números son típicos que ejecutan el punto de referencia anterior (.9 Relación de lectura/escritura):
Al usar una cantidad muy grande de objetos en un montón muy grande, las máquinas virtuales sufrirán una mayor presión de GC, ya que básicamente tiene que inspeccionar cada objeto si se puede recolectar y tiene que acceder a todas las páginas de memoria. Un caché debe mantener un conjunto de objetos accesibles para el acceso rápido (por ejemplo, omitir disco o tripas redondas de red). La única solución es usar la memoria nativa, y allí terminará con la opción de usar algún código nativo (c/c ++) a través de JNI o usar acceso de memoria directa.
El código nativo que usa C/C ++ a través de JNI tiene el inconveniente que debe escribir naturalmente el código C/C ++ para cada plataforma. Aunque la mayoría de los unix OS (Linux, OSX, BSD, Solaris) son bastante similares cuando se trata de cosas como las bibliotecas de comparación y intercambio o POSIX, generalmente también desea admitir la otra plataforma (Windows).
Tanto el código nativo como el acceso a la memoria directa tienen el inconveniente de que tienen que "dejar" el "contexto JVM": quieren decir que el acceso a la memoria de montón de apagado es más lento que el acceso a los datos en el montón de Java y que cada llamada JNI tiene algunos " Escape del contexto JVM "Costo.
Pero la memoria de montón es excelente cuando tienes que lidiar con una gran cantidad de varios/muchos GB de memoria de caché, ya que eso no ejerce ninguna presión sobre el recolector de basura Java. Deje que el Java GC haga su trabajo para la solicitud donde esta biblioteca hace su trabajo para los datos en caché.
TL; DR asigna la memoria fuera del montón directamente y omitiendo ByteBuffer.allocateDirect es muy gentil para el GC y tenemos un control explícito sobre la asignación de memoria y, lo que es más importante, gratuito. La implementación de stock en Java libera la memoria fuera del montón durante una recolección de basura, también: si no hay más memoria fuera del montaje disponible, es probable que desencadene un GC completo, lo cual es problemático si varios hilos se encuentran con esa situación simultáneamente, ya que significa mucho de Full-GC secuencialmente. Además, la implementación de acciones utiliza una lista vinculada global y sincronizada para rastrear las asignaciones de memoria fuera del montón.
Esta es la razón por la cual OHC asigna la memoria fuera de la altura directamente y recomienda precargar Jemalloc en los sistemas Linux para mejorar el rendimiento del gestión de la memoria.
OHC se desarrolló en 2014/15 para Apache Cassandra 2.2 y 3.0 para ser utilizado como el nuevo backend de la hilera.
Dado que no había implementaciones de caché totalmente fuera de la altura disponibles, se ha decidido construir una completamente nueva, y eso es OHC. Pero resultó que OHC solo también podría ser utilizable para otros proyectos, por eso OHC es una biblioteca separada.
¡Un gran 'gracias' tiene que ir a Benedict Elliott Smith y Ariel Weisberg de DataStax por su entrada muy útil a OHC!
Ben Moless, el autor de Caffeine, el caché altamente configurable en el condado que usa W-pequeño LFU.
Desarrollador: Robert Stupp
Copyright (c) 2014 Robert Stupp, Koeln, Alemania, Robert-Stupp.de
Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); No puede usar este archivo, excepto de conformidad con la licencia. Puede obtener una copia de la licencia en
http://www.apache.org/licenses/license-2.0
A menos que la ley aplicable sea requerida o acordado por escrito, el software distribuido bajo la licencia se distribuye de manera "como es", sin garantías o condiciones de ningún tipo, ya sea expresas o implícitas. Consulte la licencia para los permisos y limitaciones de rigor de idioma específico bajo la licencia.