Rendimiento de mutlitheading en RX vs Theads vs Executors

8

Estoy escribiendo una aplicación de fondo en Kotlin.

Para acelerar las cosas, actualmente confío en RxKotlin en el servidor para realizar la ejecución paralela de tareas de E / S, como llamadas a bases de datos y llamadas a API. El código generalmente se ve así.

val singleResult1 = Single.fromCallable{
  database.get(....)
}.io()

val singleResult2 = Single.fromCallable{
  database.update(....)
}.io()

Single.zip(singleResult1, singleResult2){ result1: Result1, result2: Result2 ->
    ....
}
.flatMap{
  //other RX calls
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.blockingGet()

Sin embargo, dado que no funciona realmente funciona con múltiples eventos (solo singles), Rx se siente un poco desordenado y solo agrega un montón de repeticiones (también causa complicaciones si quiero devolver un valor nulo y a veces podría estropear el seguimiento de la pila )

Estoy pensando en eliminar Rx y usar Executors(o hilos) para el paralelismo insteada. ¿Hay alguna consideración de rendimiento a considerar aquí?

Ejemplo en lo que estoy pensando:

fun <T> waitAll(tasks: List<Callable<T>>, threadCount: Int = -1): List<T> {
    val threads = if (threadCount == -1) tasks.size else threadCount
    val executor = Executors.newFixedThreadPool(threads)
    val results = executor.invokeAll(tasks).map {
        it.get()
    }
    executor.shutdown()
    return results
}

Y usándolo así:

waitAll(listOf(callable1, callable2))

¿O tal vez usando hilos regulares y unirlos?

threads.forEach{
   it.start()
}
threads.forEach{
   it.join()
}

¿O por qué no corrientes?

listOf(callable1,callable2)
.parallelStream()
.map{it.call()}
.collect(Collectors.toList())
Ricardo
fuente
1
¿Qué quiere decir con: Rx se siente un poco desordenado y solo agrega un montón de repeticiones (también causa complicaciones si quiero devolver un valor nulo y, a veces, podría estropear el seguimiento de la pila) ?
burbujas
1
Por el amor de Dios, no sigas la ruta del hilo. Desea tener una abstracción de tarea adecuada y los hilos se vuelven realmente desordenados muy rápidamente. La ruta de transmisión se ve más prometedora si no alcanza ninguna limitación. Probablemente reemplazaría RxKotlin con corutinas si las cosas se complican más: tiene el mismo alcance que RxKotlin y no se limita a la interfaz de usuario como ha escrito en otro lugar, pero se siente más idiomático.
Arvid Heise

Respuestas:

4

KotlinRx en sí mismo usa ejecutores, excepto que tiene dos grupos de subprocesos creados previamente Schedulers.io()(sin límites) y Schedulers.computation()(delimitados por el número de núcleos) y no gira uno nuevo cada vez como lo hace su código sugerido. Lo que obviamente también puedes hacer manualmente:

private val executor = Executors.newCachedThreadPool() // equivalent to io()

fun <T> waitAll(tasks: List<Callable<T>>): List<T> {
    return executor.invokeAll(tasks).map {
        it.get()
    }
}

Esto debería ser mejor que crear un hilo para cada tarea, generalmente hablando, permitiendo reutilizar hilos existentes, pero depende de su uso.

coroutines es (IMO) más adecuado para tratar con comunicación de fondo / ui-thread (es decir, Android, etc.)

Si las corutinas son útiles para esto depende mucho de lo que tenga dentro de sus tareas. ¿Una API de bloqueo (por ejemplo, JDBC)? No lo son ¿Una asíncrona (por ejemplo, Retrofit)? Son.

Alexey Romanov
fuente
5

Los servicios Java Executor usan Threads y RxKotlin usa ExecutorServices. Entonces, todos estos son iguales en el fondo. La diferencia es la arquitectura de software. Entonces, si elige que la mejor arquitectura se integre con su código, funcionará mejor y hará el trabajo correctamente. Simple es lo mejor.

Si tiene una arquitectura basada en eventos u observable y está tratando de implementar una nueva biblioteca para trabajar con operaciones o trabajos basados ​​en eventos, podría escribir pasos incorrectos sobre la separación o el tiempo del trabajo. Usa RxKotlin y no vuelvas a inventar la rueda.

Si su trabajo no se trata de eventos o patrones observables y solo necesita hacer trabajos paralelos, simplemente use los servicios de Ejecutor. El RxKotlin habrá terminado la ingeniería. Cuando usa RxKotlin en esta situación, necesita hacer más cosas de las que necesita.

Así que creo que la pregunta no es la velocidad en esta situación, sino la arquitectura.

Burak Akyıldız
fuente