Los conjuntos de bits, también llamados mapas de bits, se utilizan habitualmente como estructuras de datos rápidas. Desafortunadamente, pueden utilizar demasiada memoria. Para compensar, utilizamos a menudo mapas de bits comprimidos.
Los mapas de bits rugientes son mapas de bits comprimidos que tienden a superar a los mapas de bits comprimidos convencionales como WAH, EWAH o Concise. En algunos casos, los mapas de bits rugientes pueden ser cientos de veces más rápidos y, a menudo, ofrecen una compresión significativamente mejor. Incluso pueden ser más rápidos que los mapas de bits sin comprimir.
Se ha descubierto que los mapas de bits rugientes funcionan bien en muchas aplicaciones importantes:
Utilice Roaring para la compresión de mapas de bits siempre que sea posible. No utilice otros métodos de compresión de mapas de bits (Wang et al., SIGMOD 2017)
Felicitaciones por crear algo que hace que mi software se ejecute 5 veces más rápido (Charles Parker de BigML)
Esta biblioteca es utilizada por
La biblioteca está madura y se ha utilizado en producción durante muchos años.
El motor SQL de YouTube, Google Procella, utiliza mapas de bits Roaring para la indexación. Apache Lucene utiliza mapas de bits Roaring, aunque tienen su propia implementación independiente. Los derivados de Lucene como Solr y Elastic también utilizan mapas de bits Roaring. Otras plataformas como Whoosh, Microsoft Visual Studio Team Services (VSTS) y Pilosa también utilizan mapas de bits Roaring con sus propias implementaciones. Encontrará mapas de bits Roaring en InfluxDB, Bleve, Cloud Torrent, Redpanda, etc.
Existe una especificación de formato serializado para la interoperabilidad entre implementaciones. Contamos con implementaciones interoperables de C/C++, Java y Go.
(c) 2013-... los autores de RoaringBitmap
Este código tiene la licencia Apache, versión 2.0 (AL2.0).
Los conjuntos son una abstracción fundamental en el software. Se pueden implementar de varias maneras, como conjuntos de hash, como árboles, etc. En bases de datos y motores de búsqueda, los conjuntos suelen ser una parte integral de los índices. Por ejemplo, es posible que necesitemos mantener un conjunto de todos los documentos o filas (representados por un identificador numérico) que cumplan alguna propiedad. Además de agregar o eliminar elementos del conjunto, necesitamos funciones rápidas para calcular la intersección, la unión, la diferencia entre conjuntos, etc.
Para implementar un conjunto de números enteros, una estrategia particularmente atractiva es el mapa de bits (también llamado conjunto de bits o vector de bits). Usando n bits, podemos representar cualquier conjunto formado por números enteros del rango [0,n): el iésimo bit se establece en uno si el entero i está presente en el conjunto. Los procesadores de productos básicos utilizan palabras de W=32 o W=64 bits. Combinando muchas de estas palabras, podemos admitir valores grandes de n. Las intersecciones, uniones y diferencias se pueden implementar como operaciones bit a bit AND, OR y ANDNOT. También se pueden implementar funciones de conjunto más complicadas como operaciones bit a bit.
Cuando el enfoque de conjunto de bits es aplicable, puede ser órdenes de magnitud más rápido que otras posibles implementaciones de un conjunto (por ejemplo, como un conjunto hash) y al mismo tiempo utilizar varias veces menos memoria.
Sin embargo, un conjunto de bits, incluso uno comprimido, no siempre es aplicable. Por ejemplo, si tiene 1000 números enteros de apariencia aleatoria, entonces una matriz simple podría ser la mejor representación. Nos referimos a este caso como el escenario "escaso".
Un BitSet sin comprimir puede utilizar mucha memoria. Por ejemplo, si toma un BitSet y establece el bit en la posición 1.000.000 en verdadero y tiene poco más de 100 kB. Eso es más de 100 kB para almacenar la posición de un bit. Esto es un desperdicio incluso si no te importa la memoria: supongamos que necesitas calcular la intersección entre este BitSet y otro que tiene un bit en la posición 1.000.001 en verdadero, entonces necesitas pasar por todos estos ceros, si lo deseas. O no. Eso puede resultar un gran desperdicio.
Dicho esto, definitivamente hay casos en los que intentar utilizar mapas de bits comprimidos es un desperdicio. Por ejemplo, si tienes un tamaño de universo pequeño. Por ejemplo, sus mapas de bits representan conjuntos de números enteros de [0,n) donde n es pequeño (por ejemplo, n=64 o n=128). Si puede utilizar un BitSet sin comprimir y no aumenta su uso de memoria, entonces los mapas de bits comprimidos probablemente no le sean útiles. De hecho, si no necesita compresión, BitSet ofrece una velocidad notable.
El escenario disperso es otro caso de uso en el que no se deben utilizar mapas de bits comprimidos. Tenga en cuenta que los datos que parecen aleatorios no suelen ser comprimibles. Por ejemplo, si tiene un pequeño conjunto de enteros aleatorios de 32 bits, no es matemáticamente posible utilizar mucho menos de 32 bits por entero, y los intentos de compresión pueden ser contraproducentes.
La mayoría de las alternativas a Roaring son parte de una familia más grande de mapas de bits comprimidos que son mapas de bits codificados por longitud de ejecución. Identifican tiradas largas de 1 o 0 y las representan con una palabra marcadora. Si tiene una combinación local de 1 y 0, utiliza una palabra sin comprimir.
Hay muchos formatos en esta familia:
Sin embargo, existe un gran problema con estos formatos que puede perjudicar gravemente en algunos casos: no hay acceso aleatorio. Si desea comprobar si un valor determinado está presente en el conjunto, debe empezar desde el principio y "descomprimir" todo. Esto significa que si quieres intersectar un conjunto grande con un conjunto grande, aún tienes que descomprimir todo el conjunto grande en el peor de los casos...
El rugido resuelve este problema. Funciona de la siguiente manera. Divide los datos en fragmentos de 2 16 números enteros (por ejemplo, [0, 2 16 ), [2 16 , 2 x 2 16 ), ...). Dentro de un fragmento, puede utilizar un mapa de bits sin comprimir, una lista simple de números enteros o una lista de ejecuciones. Cualquiera que sea el formato que utilice, todos le permiten comprobar rápidamente la presencia de cualquier valor (por ejemplo, con una búsqueda binaria). El resultado neto es que Roaring puede calcular muchas operaciones mucho más rápido que los formatos codificados por longitud de ejecución como WAH, EWAH, Concise... Quizás sea sorprendente que Roaring también ofrezca generalmente mejores relaciones de compresión.
import org . roaringbitmap . RoaringBitmap ;
public class Basic {
public static void main ( String [] args ) {
RoaringBitmap rr = RoaringBitmap . bitmapOf ( 1 , 2 , 3 , 1000 );
RoaringBitmap rr2 = new RoaringBitmap ();
rr2 . add ( 4000L , 4255L );
rr . select ( 3 ); // would return the third value or 1000
rr . rank ( 2 ); // would return the rank of 2, which is index 1
rr . contains ( 1000 ); // will return true
rr . contains ( 7 ); // will return false
RoaringBitmap rror = RoaringBitmap . or ( rr , rr2 ); // new bitmap
rr . or ( rr2 ); //in-place computation
boolean equals = rror . equals ( rr ); // true
if (! equals ) throw new RuntimeException ( "bug" );
// number of values stored?
long cardinality = rr . getLongCardinality ();
System . out . println ( cardinality );
// a "forEach" is faster than this loop, but a loop is possible:
for ( int i : rr ) {
System . out . println ( i );
}
}
}
Consulte la carpeta de ejemplos para obtener más ejemplos, que puede ejecutar con ./gradlew :examples:runAll
, o ejecutar uno específico con ./gradlew :examples:runExampleBitmap64
, etc.
http://www.javadoc.io/doc/org.roaringbitmap/RoaringBitmap/
Puede descargar versiones desde github: https://github.com/RoaringBitmap/RoaringBitmap/releases
Agregue la siguiente dependencia a su archivo pom.xml...
< dependency >
< groupId >com.github.RoaringBitmap.RoaringBitmap</ groupId >
< artifactId >roaringbitmap</ artifactId >
< version >1.3.16</ version >
</ dependency >
Puede ajustar el número de versión.
Luego agregue el repositorio a su archivo pom.xml:
< repositories >
< repository >
< id >jitpack.io</ id >
< url >https://jitpack.io</ url >
</ repository >
</ repositories >
Consulte https://github.com/RoaringBitmap/JitPackRoaringBitmapProject para ver un ejemplo completo.
Agregue la siguiente dependencia a su archivo pom.xml
dentro del elemento <dependencies>
...
< dependency >
< groupId >org.roaringbitmap</ groupId >
< artifactId >roaringbitmap</ artifactId >
< version >1.3.16</ version >
</ dependency >
Agregue el repositorio de GitHub dentro del elemento <repositories>
(archivo pom.xml
)...
< repositories >
< repository >
< id >github</ id >
< name >Roaring Maven Packages</ name >
< url >https://maven.pkg.github.com/RoaringBitmap/RoaringBitmap</ url >
< releases >< enabled >true</ enabled ></ releases >
< snapshots >< enabled >true</ enabled ></ snapshots >
</ repository >
</ repositories >
Consulte https://github.com/RoaringBitmap/MavenRoaringBitmapProject para ver un ejemplo completo.
El acceso al registro está protegido por una autorización. Por lo tanto, debe agregar sus credenciales de GitHub a su configuración global.xml: $HOME.m2settings.xml
.
Necesitará un token que podrá generar en GitHub.
GitHub > Settings > Developer Settings > Personal access tokens > Generate new token
El token necesita el permiso de lectura: paquetes. El identificador del token es una cadena larga como ghp_ieOkN
.
Coloque lo siguiente en su archivo settings.xml
, dentro del elemento <servers>
.
< server >
< id >github</ id >
< username >lemire</ username >
< password >ghp_ieOkN</ password >
</ server >
Reemplace lemire
por su nombre de usuario de GitHub y ghp_ieOkN
por el identificador del token que acaba de generar.
Entonces todo lo que necesitas es editar tu archivo build.gradle
de esta manera:
plugins {
id ' java '
}
group ' org.roaringbitmap ' // name of your project
version ' 1.0-SNAPSHOT ' // version of your project
repositories {
mavenCentral()
maven {
url ' https://jitpack.io '
}
}
dependencies {
implementation ' com.github.RoaringBitmap.RoaringBitmap:roaringbitmap:1.3.16 '
testImplementation ' junit:junit:3.8.1 '
}
Consulte https://github.com/RoaringBitmap/JitPackRoaringBitmapProject para ver un ejemplo completo.
Primero necesitas tus credenciales de GitHub. Ir a
GitHub > Settings > Developer Settings > Personal access tokens > Generate new token
Y cree un token con permiso de lectura: paquetes.
Si su nombre de usuario de GitHub es lemire
y su token personal de GitHub ghp_ieOkN
, puede configurarlos usando variables del sistema. En bash, puedes hacerlo así:
export GITHUB_USER=lemire
export GITHUB_PASSWORD=ghp_ieOkN
Si lo prefieres, puedes escribir tus credenciales de GitHub en tu archivo gradle.properties.
# gradle.properties
githubUser=lemire
githubPassword=ghp_ieOkN
Entonces todo lo que necesitas es editar tu archivo build.gradle
de esta manera:
plugins {
id ' java '
}
group ' org.roaringbitmap ' // name of your project
version ' 1.0-SNAPSHOT ' // version of your project
repositories {
mavenCentral()
maven {
url ' https://maven.pkg.github.com/RoaringBitmap/RoaringBitmap '
credentials {
username = System . properties[ ' githubUser ' ] ?: System . env . GITHUB_USER
password = System . properties[ ' githubPassword ' ] ?: System . env . GITHUB_PASSWORD
}
}
}
dependencies {
implementation ' org.roaringbitmap:roaringbitmap:1.3.16 '
testImplementation ' junit:junit:3.8.1 '
}
Consulte https://github.com/RoaringBitmap/MavenRoaringBitmapProject para ver un ejemplo completo.
Java carece de enteros nativos sin signo, pero los enteros todavía se consideran sin signo en Roaring y se ordenan según Integer.compareUnsigned
. Esto significa que Java ordenará los números así 0, 1, ..., 2147483647, -2147483648, -2147483647,..., -1. Para interpretar correctamente, puede utilizar Integer.toUnsignedLong
y Integer.toUnsignedString
.
Si desea que sus mapas de bits se encuentren en archivos mapeados en memoria, puede usar el paquete org.roaringbitmap.buffer en su lugar. Contiene dos clases importantes, ImmutableRoaringBitmap y MutableRoaringBitmap. MutableRoaringBitmaps se derivan de ImmutableRoaringBitmap, por lo que puede convertir (transmitir) un MutableRoaringBitmap en un ImmutableRoaringBitmap en tiempo constante.
Un ImmutableRoaringBitmap que no es una instancia de MutableRoaringBitmap está respaldado por un ByteBuffer que viene con cierta sobrecarga de rendimiento, pero con la flexibilidad adicional de que los datos pueden residir en cualquier lugar (incluso fuera del montón de Java).
En ocasiones, es posible que necesite trabajar con mapas de bits que residen en el disco (instancias de ImmutableRoaringBitmap) y mapas de bits que residen en la memoria de Java. Si sabes que los mapas de bits residirán en la memoria de Java, lo mejor es utilizar instancias de MutableRoaringBitmap, no sólo se podrán modificar, sino que también serán más rápidas. Además, debido a que las instancias de MutableRoaringBitmap también son instancias de ImmutableRoaringBitmap, puede escribir gran parte de su código esperando ImmutableRoaringBitmap.
Si escribe su código esperando instancias de ImmutableRoaringBitmap, sin intentar convertir las instancias, entonces sus objetos serán verdaderamente inmutables. MutableRoaringBitmap tiene un método conveniente (toImmutableRoaringBitmap) que es una conversión simple a una instancia de ImmutableRoaringBitmap. Desde el punto de vista del diseño del lenguaje, las instancias de la clase ImmutableRoaringBitmap son inmutables solo cuando se usan según la interfaz de la clase ImmutableRoaringBitmap. Dado que la clase no es definitiva, es posible modificar instancias, a través de otras interfaces. Por lo tanto, no tomamos el término "inmutable" de manera purista, sino más bien práctica.
Una de nuestras motivaciones para este diseño en el que las instancias de MutableRoaringBitmap se pueden convertir en instancias de ImmutableRoaringBitmap es que los mapas de bits suelen ser grandes o se usan en un contexto donde se deben evitar las asignaciones de memoria, por lo que evitamos forzar copias. Se podrían esperar copias si es necesario mezclar y combinar instancias de ImmutableRoaringBitmap y MutableRoaringBitmap.
El siguiente ejemplo de código ilustra cómo crear un ImmutableRoaringBitmap a partir de un ByteBuffer. En tales casos, el constructor solo carga los metadatos en la RAM mientras se accede a los datos reales desde ByteBuffer a pedido.
import org . roaringbitmap . buffer .*;
//...
MutableRoaringBitmap rr1 = MutableRoaringBitmap . bitmapOf ( 1 , 2 , 3 , 1000 );
MutableRoaringBitmap rr2 = MutableRoaringBitmap . bitmapOf ( 2 , 3 , 1010 );
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
DataOutputStream dos = new DataOutputStream ( bos );
// If there were runs of consecutive values, you could
// call rr1.runOptimize(); or rr2.runOptimize(); to improve compression
rr1 . serialize ( dos );
rr2 . serialize ( dos );
dos . close ();
ByteBuffer bb = ByteBuffer . wrap ( bos . toByteArray ());
ImmutableRoaringBitmap rrback1 = new ImmutableRoaringBitmap ( bb );
bb . position ( bb . position () + rrback1 . serializedSizeInBytes ());
ImmutableRoaringBitmap rrback2 = new ImmutableRoaringBitmap ( bb );
Alternativamente, podemos serializar directamente a un ByteBuffer
con el método serialize(ByteBuffer)
.
Las operaciones en un ImmutableRoaringBitmap como y, o, xor, flip, generarán un RoaringBitmap que se encuentra en la RAM. Como sugiere el nombre, el ImmutableRoaringBitmap en sí no se puede modificar.
Este diseño se inspiró en Apache Druid.
Se puede encontrar un ejemplo funcional completo en el archivo de prueba TestMemoryMapping.java.
Tenga en cuenta que no debe mezclar las clases del paquete org.roaringbitmap con las clases del paquete org.roaringbitmap.buffer. Son incompatibles. Sin embargo, se serializan con la misma salida. El rendimiento del código en el paquete org.roaringbitmap es generalmente superior porque no hay gastos generales debido al uso de instancias de ByteBuffer.
En general, no es seguro acceder a los mismos mapas de bits utilizando subprocesos diferentes: los mapas de bits no están sincronizados para mejorar el rendimiento. Si desea acceder a un mapa de bits desde más de un hilo, debe proporcionar sincronización. Sin embargo, puede acceder a un mapa de bits inmutable desde varios subprocesos, siempre que respete la interfaz ImmutableBitmapDataProvider
.
Muchas aplicaciones utilizan Kryo para la serialización/deserialización. Se pueden utilizar mapas de bits Roaring con Kryo de manera eficiente gracias a un serializador personalizado (Kryo 5):
public class RoaringSerializer extends Serializer < RoaringBitmap > {
@ Override
public void write ( Kryo kryo , Output output , RoaringBitmap bitmap ) {
try {
bitmap . serialize ( new KryoDataOutput ( output ));
} catch ( IOException e ) {
e . printStackTrace ();
throw new RuntimeException ();
}
}
@ Override
public RoaringBitmap read ( Kryo kryo , Input input , Class <? extends RoaringBitmap > type ) {
RoaringBitmap bitmap = new RoaringBitmap ();
try {
bitmap . deserialize ( new KryoDataInput ( input ));
} catch ( IOException e ) {
e . printStackTrace ();
throw new RuntimeException ();
}
return bitmap ;
}
}
Aunque Roaring Bitmaps se diseñó pensando en el caso de 32 bits, tenemos extensiones para enteros de 64 bits. Ofrecemos dos clases para este propósito: Roaring64NavigableMap
y Roaring64Bitmap
.
El Roaring64NavigableMap
se basa en un árbol rojo-negro convencional. Las claves son números enteros de 32 bits que representan los elementos de 32 bits más significativos, mientras que los valores del árbol son mapas de bits Roaring de 32 bits. Los mapas de bits Roaring de 32 bits representan los bits menos significativos de un conjunto de elementos.
El enfoque más nuevo Roaring64Bitmap
se basa en la estructura de datos ART para contener el par clave/valor. La clave está formada por los elementos más importantes de 48 bits, mientras que los valores son contenedores Roaring de 16 bits. Está inspirado en The Adaptive Radix Tree: ARTful Indexing for Main-Memory Databases de Leis et al. (ICDE '13).
import org . roaringbitmap . longlong .*;
// first Roaring64NavigableMap
LongBitmapDataProvider r = Roaring64NavigableMap . bitmapOf ( 1 , 2 , 100 , 1000 );
r . addLong ( 1234 );
System . out . println ( r . contains ( 1 )); // true
System . out . println ( r . contains ( 3 )); // false
LongIterator i = r . getLongIterator ();
while ( i . hasNext ()) System . out . println ( i . next ());
// second Roaring64Bitmap
bitmap1 = new Roaring64Bitmap ();
bitmap2 = new Roaring64Bitmap ();
int k = 1 << 16 ;
long i = Long . MAX_VALUE / 2 ;
long base = i ;
for (; i < base + 10000 ; ++ i ) {
bitmap1 . add ( i * k );
bitmap2 . add ( i * k );
}
b1 . and ( bitmap2 );
Se especifica la serialización de mapas de bits Roaring de 64 bits: consulte https://github.com/RoaringBitmap/RoaringFormatSpec#extention-for-64-bit-implementations
Sin embargo, solo lo implementa Roaring64NavigableMap
, cambiando:
Roaring64NavigableMap.SERIALIZATION_MODE = Roaring64NavigableMap.SERIALIZATION_MODE_PORTABLE
RangeBitmap
es una estructura de datos concisa que admite consultas de rango. Cada valor agregado al mapa de bits está asociado con un identificador incremental y las consultas producen un RoaringBitmap
de los identificadores asociados con los valores que satisfacen la consulta. Cada valor agregado al mapa de bits se almacena por separado, de modo que si un valor se agrega dos veces, se almacenará dos veces, y si ese valor es menor que algún umbral, habrá al menos dos números enteros en el RoaringBitmap
resultante.
Es más eficiente, tanto en términos de tiempo como de espacio, proporcionar un valor máximo. Si no conoce el valor máximo, proporcione Long.MAX_VALUE
. El orden sin firmar se utiliza como en cualquier otra parte de la biblioteca.
var appender = RangeBitmap . appender ( 1_000_000 );
appender . add ( 1L );
appender . add ( 1L );
appender . add ( 100_000L );
RangeBitmap bitmap = appender . build ();
RoaringBitmap lessThan5 = bitmap . lt ( 5 ); // {0,1}
RoaringBitmap greaterThanOrEqualTo1 = bitmap . gte ( 1 ); // {0, 1, 2}
RoaringBitmap greaterThan1 = bitmap . gt ( 1 ); // {2}
RoaringBitmap equalTo1 = bitmap . eq ( 1 ); // {0, 1}
RoaringBitmap notEqualTo1 = bitmap . neq ( 1 ); // {2}
RangeBitmap
se puede escribir en el disco y mapear en memoria:
var appender = RangeBitmap . appender ( 1_000_000 );
appender . add ( 1L );
appender . add ( 1L );
appender . add ( 100_000L );
ByteBuffer buffer = mapBuffer ( appender . serializedSizeInBytes ());
appender . serialize ( buffer );
RangeBitmap bitmap = RangeBitmap . map ( buffer );
El formato de serialización utiliza el orden de bytes little endian.
obtener java
./gradlew assemble
se compilará
./gradlew build
compilará y ejecutará las pruebas unitarias
./gradlew test
ejecutará las pruebas
./gradlew :roaringbitmap:test --tests TestIterators.testIndexIterator4
ejecuta solo la prueba TestIterators.testIndexIterator4
; ./gradlew -i :roaringbitmap:test --tests TestRoaringBitmap.issue623
ejecuta solo el problema de issue623
en la clase TestRoaringBitmap
mientras imprime en la consola.
./gradlew bsi:test --tests BufferBSITest.testEQ
ejecuta solo la prueba BufferBSITest.testEQ
en el submódulo bsi
Si planeas contribuir a RoaringBitmap, puedes cargarlo en tu IDE favorito.
Se invitan contribuciones. Usamos el estilo Google Java (ver roaring_google_checks.xml
). Se puede aplicar automáticamente a su código con ./gradlew spotlessApply
No vuelva a formatear el código innecesariamente (especialmente en comentarios/javadoc).
En los archivos serializados, parte de los primeros 4 bytes están dedicados a una "cookie" que sirve para indicar el formato del archivo.
Si intenta deserializar o mapear un mapa de bits a partir de datos que tienen una "cookie" no reconocida, el código cancelará el proceso e informará un error.
Este problema les ocurrirá a todos los usuarios que serializaron mapas de bits de Roaring usando versiones anteriores a la 0.4.x cuando actualicen a la versión 0.4.x o superior. Estos usuarios necesitan actualizar sus mapas de bits serializados.
Dados N enteros en [0,x), entonces el tamaño serializado en bytes de un mapa de bits Roaring nunca debe exceder este límite:
8 + 9 * ((long)x+65535)/65536 + 2 * N
Es decir, dada una sobrecarga fija para el tamaño del universo (x), los mapas de bits de Roaring nunca usan más de 2 bytes por número entero. Puede llamar RoaringBitmap.maximumSerializedSize
para obtener una estimación más precisa.
No existe una estructura de datos que sea siempre ideal. Debe asegurarse de que los mapas de bits de Roaring se ajusten al perfil de su aplicación. Hay al menos dos casos en los que los mapas de bits Roaring se pueden reemplazar fácilmente por alternativas superiores en cuanto a compresión:
Tiene pocos valores aleatorios que abarcan un intervalo grande (es decir, tiene un conjunto muy escaso). Por ejemplo, tome el conjunto 0, 65536, 131072, 196608, 262144... Si esto es típico de su aplicación, podría considerar usar un HashSet o una matriz ordenada simple.
Tiene un conjunto denso de valores aleatorios que nunca forman series de valores continuos. Por ejemplo, considere el conjunto 0,2,4,...,10000. Si esto es típico de su aplicación, es posible que le sirva mejor un conjunto de bits convencional (por ejemplo, la clase BitSet de Java).
¿Cómo selecciono un elemento al azar?
Random random = new Random();
bitmap.select(random.nextInt(bitmap.getCardinality()));
Para ejecutar pruebas comparativas de JMH, utilice los siguientes comandos:
$ ./gradlew jmh::shadowJar
$ java -jar jmh/build/libs/benchmarks.jar
También puede ejecutar un punto de referencia específico:
$ java -jar jmh/build/libs/benchmarks.jar 'org.roaringbitmap.aggregation.and.identical.*'
Si tiene un shell bash, también puede ejecutar nuestro script que compila y ejecuta automáticamente pruebas específicas...
$ ./jmh/run.sh 'org.roaringbitmap.aggregation.and.identical.*'
https://groups.google.com/forum/#!forum/roaringbitmaps
Este trabajo fue apoyado por la subvención número 26143 del NSERC.