python - slow - speed up mongodb query
Base de datos de persistencia(MySQL/MongoDB/Cassandra/BigTable/BigData) Vs Non-Persistence Array(PHP/PYTHON) (3)
¿Qué tan beneficioso será utilizar la matriz no persistente de Python/PHP
para almacenar datos de más de 6GB con más de 800 millones de filas en la RAM, en lugar de usar la base de datos MySQL / MongoDB / Cassandra / BigTable / BigData (Persistence Database) cuando se trata de velocidad / latencia en forma simple? consulta de ejecucion?
Por ejemplo, encontrar un nombre en más de 800 millones de filas en 1 segundo: ¿es posible? ¿Alguien tiene experiencia en tratar con un conjunto de datos de más de 1 a 2 mil millones de filas y obtener el resultado en 1 segundo para una consulta de búsqueda simple?
¿Existe una metodología mejor y probada para tratar con miles de millones de filas?
Aún puede aprovechar la búsqueda basada en RAM, y aún tener funcionalidades adicionales que proporcionan las bases de datos especializadas en comparación con un hashmap / matriz en RAM.
Su objetivo con las búsquedas basadas en RAM es realizar búsquedas más rápidas y evitar los gastos generales de red . Sin embargo, ambos pueden lograrse alojando la base de datos localmente, o la red ni siquiera sería una sobrecarga para pequeñas cargas útiles de datos como los nombres.
Por el método de matriz de RAM, la resistencia de las aplicaciones disminuye a medida que tiene un solo punto de falla , no es fácil hacer una instantánea, es decir, tendría que hacer algo de calentamiento de los datos cada vez que su aplicación cambie o se reinicie , y siempre estará restringido a un solo patrón de consulta y Puede que no sea capaz de evolucionar en el futuro.
Alternativas igualmente buenas con un rendimiento razonablemente comparable se pueden volver a realizar en una configuración de clúster o maestro-esclavo, o en aerospike en máquinas SSD . Usted obtiene la ventaja de las instantáneas constantes, el alto rendimiento, la distribución y la resiliencia a través de la fragmentación / agrupación, es decir, 1/8 de los datos en 8 instancias, de modo que no existe un punto único de falla.
4 bytes (int) * 1_000_000_000 ~ 4 Gb 4 bytes (int) * 1_000_000_000 / 64 bytes = 62500000 veces (para caché L1) 4 bytes (int) * 1_000_000_000 / 64 bytes = 62500000 veces (para caché L2)
Tomada la latencia, que todos deben saber para la memoria principal 100 ns desde aquí obtenemos 100 s. Si toda la memoria caché L1 interna (línea de 64 bytes para Intel) está cerca de 31.25 ms. Pero antes de eso también hay cachés L2 / L3 (el mismo tamaño de línea) sería 218,75 ms. Puede ver que leer 1 Mb secuencialmente (en otras palabras es el mejor caso), por lo que para 4 Gb es 4024 * 250 µs = 1006000 µs ~ = 1 s. El disco SSD tiene menos latencia, pero no es tan simple. Hay investigaciones (quizás caducadas ahora) que la mayoría de los discos SSD, que están disponibles para que todos puedan comprarlos, no pueden tener tasas de carga realmente altas (razones: fallan y más interesantes) tienen su propio recolector de basura, que puede agregar grandes estado latente). Pero también hay soluciones adaptables al entorno de discos SSD como Aerospike y, en general, las unidades SSD son más rápidas que las HDD.
Sólo para entender. En una computadora portátil típica (mi: Intel Core i5, x64, 16Gb RAM) necesito cerca de 580 ms a 875 ms para calcular una suma larga para 1 billón de elementos int. También puedo ver la velocidad de Clickhouse desde 300 Mb / sa 354.66 Mb / s para calcular la suma en la columna Int32 en mi SSD. (tenga en cuenta que la suma en ambos casos no tiene sentido, debido al desbordamiento de tipo)
Por supuesto, también tenemos CUDA como variante o incluso para subprocesos simples (supongamos que varios subprocesos calcularán la suma, que podemos lograr fácilmente).
¿Entonces, qué podemos hacer?
Hay dos tipos de escala: vertical y horizontal. La mayoría de las bases de datos prefieren la escala horizontal, supongo que la razón es simple. La escala horizontal es más simple que la vertical. Para la escala vertical, necesita personas (o debería tener por su propia cuenta) muy buena experiencia en diferentes áreas. Por ejemplo, desde mi vida, debería saber mucho sobre Java / HotSpot / OS arquitectures / Many-many technologies / frameworks / DBs para escribir o comprender los beneficios de diferentes decisiones al crear aplicaciones / algoritmos de alto rendimiento. Y esto es solo el comienzo, hay expertos mucho más difíciles que yo.
Otras bases de datos utilizan la escala vertical, con mayor precisión utilizan optimizaciones especiales para situaciones / consultas particulares.
Todas las decisiones son comprometidas entre diferentes operaciones. Por ejemplo, para el problema Top N, Vertica y Druid tienen implementaciones específicas, que resuelven exactamente esta tarea. En Cassandra, para hacer que todas las selecciones sean rápidas, debe crear varias tablas o varias vistas para una tabla con una representación diferente y eficiente para una consulta en particular, por supuesto, gastar más espacio de almacenamiento, debido a la duplicación de datos.
Uno de los mayores problemas reales es que incluso usted puede leer 1 billón de filas en un segundo, probablemente no puede escribir al mismo tiempo en la misma tabla. En otras palabras, el problema principal: es difícil satisfacer todas las solicitudes de los usuarios para todas las tareas de los usuarios al mismo tiempo.
¿Existe una metodología mejor y probada para tratar con miles de millones de filas?
Algunos ejemplos:
- RAM con combinación de archivos asignados en memoria (para mantener la sobrecarga): cuando utiliza archivos asignados en memoria (o archivos de acceso aleatorio), puede almacenar más datos y, con buenos discos, recibir altas tasas de lectura / escritura.
- Segmentos de memoria indexados: la idea es dividir los datos por índice, que se asociarán con el segmento y consultar dentro de los segmentos, sin procesar todos los datos.
- Almacenamientos específicos de la tarea: cuando conoce sus datos y sus requisitos, puede idear un almacenamiento, que será muy eficiente para ellos, pero no para otros (en su caso, "encontrar un nombre" podría indexar y dividir los datos por letras, prefijo , etc.);
- Hacer cálculos complejos en C / C ++, a veces esto podría ser más rápido. :) Es un poco gracioso, pero las historias reales. A través de world of mouth C ++ es más complejo para la programación y el soporte, pero es más fácil escribir una aplicación rápida en C ++, si tiene suficiente experiencia;
- Dublicación de datos (almacenar datos de múltiples maneras para diferentes consultas);
- Generación de código (generar código sobre la marcha, que se optimizará para cada tarea en particular);
- Múltiples subprocesos, por supuesto: realice una tarea en varios subprocesos si pudiera compartir efectivamente los recursos de la CPU
- Caché, por supuesto: resultados de caché, basados en discos / RAM / red (me refiero a servidores de caché externos);
En algunos casos, el uso de una solución propia puede ser más costoso (y efectivo) y luego personalizado. En algunos casos no es ...
La comparación de cadenas es relativamente compleja, así que supongo que necesitas comenzar por calcular cuánto tiempo necesitas para comparar dos cadenas. Este sencillo ejemplo muestra cuánto tiempo necesitamos para comparar dos cadenas en Java.
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(1)
public class StringEquals {
@Param({"0", "5", "10"})
int prefix;
String theSamePart, theSamePartQuery;
@Setup(Level.Invocation)
public void setData() {
String value = String.valueOf(ThreadLocalRandom.current().nextInt());
theSamePart = prefix > 0 ? value.substring(Math.min(prefix, value.length())) : value;
value = String.valueOf(ThreadLocalRandom.current().nextInt());
theSamePartQuery = prefix > 0 ? theSamePart + value.substring(Math.min(prefix, value.length())) : value;
}
@Benchmark
public boolean equals(StringEquals stringEquals) {
return stringEquals.theSamePart.equals(stringEquals.theSamePartQuery);
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(StringEquals.class.getSimpleName())
.measurementIterations(10)
.warmupIterations(10)
.build()).run();
}
}
Resultados:
Benchmark (prefix) Mode Cnt Score Error Units
StringEquals.equals 0 sample 3482270 0,047 ± 0,011 us/op
StringEquals.equals:equals·p0.00 0 sample 0,022 us/op
StringEquals.equals:equals·p0.50 0 sample 0,035 us/op
StringEquals.equals:equals·p0.90 0 sample 0,049 us/op
StringEquals.equals:equals·p0.95 0 sample 0,058 us/op
StringEquals.equals:equals·p0.99 0 sample 0,076 us/op
StringEquals.equals:equals·p0.999 0 sample 0,198 us/op
StringEquals.equals:equals·p0.9999 0 sample 8,636 us/op
StringEquals.equals:equals·p1.00 0 sample 9519,104 us/op
StringEquals.equals 5 sample 2686616 0,037 ± 0,003 us/op
StringEquals.equals:equals·p0.00 5 sample 0,021 us/op
StringEquals.equals:equals·p0.50 5 sample 0,028 us/op
StringEquals.equals:equals·p0.90 5 sample 0,044 us/op
StringEquals.equals:equals·p0.95 5 sample 0,048 us/op
StringEquals.equals:equals·p0.99 5 sample 0,060 us/op
StringEquals.equals:equals·p0.999 5 sample 0,238 us/op
StringEquals.equals:equals·p0.9999 5 sample 8,677 us/op
StringEquals.equals:equals·p1.00 5 sample 1935,360 us/op
StringEquals.equals 10 sample 2989681 0,039 ± 0,001 us/op
StringEquals.equals:equals·p0.00 10 sample 0,021 us/op
StringEquals.equals:equals·p0.50 10 sample 0,030 us/op
StringEquals.equals:equals·p0.90 10 sample 0,049 us/op
StringEquals.equals:equals·p0.95 10 sample 0,056 us/op
StringEquals.equals:equals·p0.99 10 sample 0,074 us/op
StringEquals.equals:equals·p0.999 10 sample 0,222 us/op
StringEquals.equals:equals·p0.9999 10 sample 8,576 us/op
StringEquals.equals:equals·p1.00 10 sample 325,632 us/op
Entonces, suponga que necesita 1_000_000_000 cadenas, necesita aproximadamente 8_000_000_000 us = 8000 s para procesar 1 billón de cadenas en casos de 99.99%.
En contraste podríamos intentar hacerlo de forma paralela:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.*;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Threads(1)
public class SearchBillionForkJoin {
static final int availableProcessors = 4; // Runtime.getRuntime().availableProcessors()
static final int size = 10_000_000, bucketSize = size / availableProcessors;
static final int handlersCount = availableProcessors;
@Param({"50", "100"})
int spinner;
String[] a;
Callable<Integer>[] callables;
ForkJoinTask<Integer>[] tasks;
QueryHolder queryHolder;
@Setup(Level.Trial)
public void setup() {
callables = new Callable[handlersCount];
queryHolder = new QueryHolder();
a = new String[size];
for (int i = 0; i < callables.length; ++i) {
switch (i) {
case 0:
callables[i] = createForBucket(queryHolder, a, 0, bucketSize);
break;
case 1:
callables[i] = createForBucket(queryHolder, a, bucketSize, bucketSize * 2);
break;
case 2:
callables[i] = createForBucket(queryHolder, a, bucketSize * 2, bucketSize * 3);
break;
case 3:
callables[i] = createForBucket(queryHolder, a, bucketSize * 3, size);;
break;
}
}
tasks = new ForkJoinTask[handlersCount];
}
@Setup(Level.Invocation)
public void setData() {
for (int i = 0; i < a.length; ++i) {
a[i] = String.valueOf(ThreadLocalRandom.current().nextInt());
}
queryHolder.query = String.valueOf(ThreadLocalRandom.current().nextInt());
}
@Benchmark
public Integer forkJoinPoolWithoutCopy() {
try {
for (int i = 0; i < tasks.length; ++i) {
tasks[i] = ForkJoinPool.commonPool().submit(callables[i]);
}
Integer position = -1;
boolean findMore = true;
head:
while(position == -1 && findMore) {
findMore = false;
for (int i = 0; i < tasks.length; ++i) {
if (tasks[i].isDone() && !tasks[i].isCancelled()) {
final Integer value = tasks[i].get();
if (value > -1) {
position = value;
for (int j = 0; j < tasks.length; ++j) {
if (j != i && !tasks[j].isDone()) {
tasks[j].cancel(true);
}
}
break head;
}
} else {
findMore = true;
}
}
int counter = spinner;
while (counter > 0) --counter;
}
return position;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(SearchBillionForkJoin.class.getSimpleName())
.jvmArgs("-Xmx10G")
.measurementIterations(10)
.warmupIterations(10)
.build()).run();
}
static boolean isDone(ForkJoinTask[] tasks) {
for (int i = 0; i < tasks.length; ++i) {
if (!tasks[i].isDone()) {
return false;
}
}
return true;
}
static Callable<Integer> createForBucket(QueryHolder queryHolder, String[] a, int start, int end) {
return new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for (int j = start; j < end; ++j) {
if (queryHolder.query.equals(a[j])) {
return j;
}
}
return -1;
}
};
}
static class QueryHolder {
String query = null;
}
}
Utilizo 10_000_000 y 4 subprocesos (para 4 cpu cores), porque no tengo suficiente memoria para ello. Los resultados aún no parecen apropiados.
Benchmark (spinner) Mode Cnt Score Error Units
SearchBillionForkJoin.forkJoinPoolWithoutCopy 50 sample 166 47,136 ± 1,989 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.00 50 sample 5,521 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.50 50 sample 47,055 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.90 50 sample 54,788 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.95 50 sample 56,653 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.99 50 sample 61,352 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.999 50 sample 63,635 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.9999 50 sample 63,635 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p1.00 50 sample 63,635 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy 100 sample 162 51,288 ± 4,031 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.00 100 sample 5,448 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.50 100 sample 49,840 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.90 100 sample 67,030 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.95 100 sample 90,505 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.99 100 sample 110,920 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.999 100 sample 121,242 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p0.9999 100 sample 121,242 ms/op
SearchBillionForkJoin.forkJoinPoolWithoutCopy:forkJoinPoolWithoutCopy·p1.00 100 sample 121,242 ms/op
En otras palabras 63,635 ms * 100 = 6363,5 ms = 6 s. Estos resultados podrían mejorarse, por ejemplo, si pudiera usar bloqueos de afinidad (una CPU completa por subproceso). Pero podría ser demasiado complejo.
Intentemos usar segmentos solo para mostrar la idea:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Threads(1)
public class SearchInMapBillionForkJoin {
static final int availableProcessors = 8; // Runtime.getRuntime().availableProcessors()
static final int size = 10_000_000, bucketSize = size / availableProcessors;
static final int handlersCount = availableProcessors;
Map<Integer, List<StringWithIndex>> strings;
QueryHolder queryHolder;
ForkJoinTask<Integer>[] tasks;
Callable<Integer>[] callables;
@Param({"50", "100"})
int spinner;
@Setup(Level.Trial)
public void setup() throws Exception {
queryHolder = new QueryHolder();
strings = new ConcurrentHashMap<>();
tasks = new ForkJoinTask[handlersCount];
callables = new Callable[handlersCount];
setData();
}
public void setData() throws Exception {
final int callableBucket = size / handlersCount;
for (int i = 0; i < handlersCount; ++i) {
callables[i] = createGenerateForBucket(strings, callableBucket);
tasks[i] = ForkJoinPool.commonPool().submit(callables[i]);
}
while(!isDone(tasks)) {
int counter = spinner;
while (counter > 0) --counter;
}
Map<Integer, Integer> distribution = new HashMap<>();
for (List<StringWithIndex> stringWithIndices : strings.values()) {
distribution.compute(stringWithIndices.size(), (key, value) -> value == null ? 1 : value + 1);
}
int maxListSize = 0;
for (int i = 0; i < handlersCount; ++i) {
Integer max = tasks[i].get();
if (max > maxListSize) {
maxListSize = max;
}
}
System.out.println("maxListSize = " + maxListSize);
System.out.println("list size distribution = " + distribution);
System.out.println("map size = " + strings.size());
distribution = null;
queryHolder.query = String.valueOf(ThreadLocalRandom.current().nextInt());
}
@Benchmark
public Integer findInSegment() {
final String query = this.queryHolder.query;
final Integer hashCode = query.hashCode();
final Map<Integer, List<StringWithIndex>> strings = this.strings;
if (strings.containsKey(hashCode)) {
List<StringWithIndex> values = strings.get(hashCode);
if (!values.isEmpty()) {
final int valuesSize = values.size();
if (valuesSize > 100_000) {
final int bucketSize = valuesSize / handlersCount;
callables[0] = createSearchForBucket(query, values, 0, bucketSize);
callables[1] = createSearchForBucket(query, values, bucketSize, bucketSize * 2);
callables[2] = createSearchForBucket(query, values, bucketSize * 2, bucketSize * 3);
callables[3] = createSearchForBucket(query, values, bucketSize * 3, values.size());
try {
for (int i = 0; i < callables.length; ++i) {
tasks[i] = ForkJoinPool.commonPool().submit(callables[i]);
}
Integer position = -1;
boolean findMore = true;
head:
while (position == -1 && findMore) {
findMore = false;
for (int i = 0; i < tasks.length; ++i) {
if (tasks[i].isDone() && !tasks[i].isCancelled()) {
final Integer value = tasks[i].get();
if (value > -1) {
position = value;
for (int j = 0; j < tasks.length; ++j) {
if (j != i && !tasks[j].isDone()) {
tasks[j].cancel(true);
}
}
break head;
}
} else {
findMore = true;
}
}
int counter = spinner;
while (counter > 0) --counter;
}
return position;
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
for (StringWithIndex stringWithIndex : values) {
if (query.equals(stringWithIndex.value)) {
return stringWithIndex.index;
}
}
}
}
}
return -1;
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(SearchInMapBillionForkJoin.class.getSimpleName())
.jvmArgs("-Xmx6G")
.measurementIterations(10)
.warmupIterations(10)
.build()).run();
}
static class StringWithIndex implements Comparable<StringWithIndex> {
final int index;
final String value;
public StringWithIndex(int index, String value) {
this.index = index;
this.value = value;
}
@Override
public int compareTo(StringWithIndex o) {
int a = this.value.compareTo(o.value);
if (a == 0) {
return Integer.compare(this.index, o.index);
}
return a;
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StringWithIndex) {
return this.value.equals(((StringWithIndex) obj).value);
}
return false;
}
}
static class QueryHolder {
String query = null;
}
static Callable<Integer> createSearchForBucket(String query, List<StringWithIndex> values, int start, int end) {
return new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for (int j = start; j < end; ++j) {
StringWithIndex stringWithIndex = values.get(j);
if (query.equals(stringWithIndex.value)) {
return stringWithIndex.index;
}
}
return -1;
}
};
}
static Callable<Integer> createGenerateForBucket(Map<Integer, List<StringWithIndex>> strings,
int count) {
return new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int maxListSize = 0;
for (int i = 0; i < count; ++i) {
String value = String.valueOf(ThreadLocalRandom.current().nextInt());
List<StringWithIndex> values = strings.computeIfAbsent(value.hashCode(), k -> new ArrayList<>());
values.add(new StringWithIndex(i, value));
if (values.size() > maxListSize) {
maxListSize = values.size();
}
}
return maxListSize;
}
};
}
static boolean isDone(ForkJoinTask[] tasks) {
for (int i = 0; i < tasks.length; ++i) {
if (!tasks[i].isDone()) {
return false;
}
}
return true;
}
}
Resultados:
Benchmark (spinner) Mode Cnt Score Error Units
SearchInMapBillionForkJoin.findInSegment 50 sample 5164328 ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.00 50 sample ≈ 10⁻⁵ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.50 50 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.90 50 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.95 50 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.99 50 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.999 50 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.9999 50 sample 0.009 ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p1.00 50 sample 18.973 ms/op
SearchInMapBillionForkJoin.findInSegment 100 sample 4642775 ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.00 100 sample ≈ 10⁻⁵ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.50 100 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.90 100 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.95 100 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.99 100 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.999 100 sample ≈ 10⁻⁴ ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p0.9999 100 sample 0.005 ms/op
SearchInMapBillionForkJoin.findInSegment:findInSegment·p1.00 100 sample 0.038 ms/op
Antes de hacer cualquier conclusión global, es bueno conocer algunas críticas para este ejemplo:
- debido a que los datos de referencia tienen una muy buena distribución entre los tamaños de lista: un ejemplo: maxListSize = 3, distribución de tamaño de lista = {1 = 9954167, 2 = 22843, 3 = 49}, tamaño de mapa = 9977059. maxListSize para todas las iteraciones solo 4.
- como resultado, nunca vamos a la rama "if (valuesSize> 100_000)";
- Además, en la mayoría de los casos, probablemente no vayamos dentro de "} else {for (StringWithIndex stringWithIndex: valores) {", debido a la condición "if (strings.containsKey (hashCode))";
- esta prueba, a diferencia de las pruebas anteriores, se ejecutaba en diferentes PC (8 cpus, 32 Gb RAM, amd64);
Aquí puede tener una idea, que verifique si hay una clave en el mapa (o segmento de memoria), obviamente, es mejor que revisar todos los datos. Este tema es muy amplio. Hay muchas personas que trabajan con el rendimiento y pueden decir que "la optimización del rendimiento es un proceso infinito". :) También debo recordar que la "Pre-optimización es mala", y para mí agregar que no significa que debas diseñar tu sistema sin pensar, irracionalmente.
Descargo de responsabilidad: Toda esta información podría estar equivocada. Está destinado únicamente a fines informativos y no puede incorporarse a ningún contrato. Antes de usarlo para escenarios de producción, debes verificarlo por tu cuenta. Y no debes usar esta información en código de producción me refiero. No soy responsable de la posible pérdida de dinero. Toda esta información no se refiere a ninguna empresa en la que haya trabajado. No estoy afiliado a ninguno de MySQL / MongoDB / Cassandra / BigTable / BigData y también a Apache Ignite / Hazelcast / Vertica / Clickhouse / Aerospike o cualquier otra base de datos.
Debería ser muy grande diferente, alrededor de 4-5 órdenes de magnitud más rápido. La base de datos almacena registros en bloques de 4KB (generalmente), y tiene que traer cada uno de esos bloques a la memoria, necesita algunos milisegundos. Divide el tamaño de tu mesa con 4KB y obtén la imagen. En contraste, los tiempos correspondientes a los datos en memoria son generalmente nanosegundos. No hay duda de que la memoria es más rápida, la pregunta real es si tiene suficiente memoria y cuánto tiempo puede mantener sus datos allí.
Sin embargo, lo anterior es válido para una consulta de select * from table
. Si desea una select * from table where name=something
, puede crear un índice en el nombre, para que la base de datos no tenga que analizar todo el archivo, y los resultados deberían ser mucho, mucho mejores, probablemente muy satisfactorios para la práctica utilizar.