Olá pessoal! No meu trabalho, costumo usar Kotlin para automação. Minha atividade não está diretamente relacionada à programação, mas Kotlin simplifica muito as tarefas de trabalho.
Recentemente foi necessário coletar dados de um tamanho bastante grande para fazer a análise, então decidi escrever um pequeno script para obter os dados e salvá-los no Excel. Não houve problemas com o último ponto - li sobre o Apache POI, peguei alguns exemplos da documentação oficial, modificando-o por mim mesmo. O mesmo não pode ser dito sobre os pedidos da Internet.
A fonte retornou em lotes de json e foi necessário coletar esses "lotes" de alguma forma rapidamente, convertendo-os em texto e escrevendo uma tabela em um arquivo.
Método assíncrono
Decidi começar com uma simples assincronização. Depois de vasculhar um pouco o HttpUrlConnection, enviei-o para onde ele pertence, substituindo-o por HttpClient do Java.
Para testes, usei o serviço https://jsonplaceholder.typicode.com/ , que me foi sugerido por um desenvolvedor conhecido. Salvei um link que emite comentários ao Json para uma variável, de modo a não duplicar e iniciar os testes.
const val URL = "https://jsonplaceholder.typicode.com/comments"
A função estava pronta e até funcionando. Os dados chegaram.
fun getDataAsync(url: String): String? {
val httpClient = HttpClient.newBuilder()
.build()
val httpRequest = HttpRequest.newBuilder()
.uri(URI.create(link)).build()
return httpClient.sendAsync(httpRequest, BodyHandlers.ofString())
.join().body()
}
Agora era preciso verificar a velocidade de trabalho. Armado com o measureTimeMillis, executei o código.
val asyncTime = measureTimeMillis {
val res = (1..10)
.toList()
.map {getDataAsync("$URL/$it")}
res.forEach { println(it) }
}
println(" $asyncTime ")
Tudo funcionou como deveria, mas eu queria mais rápido. Depois de vasculhar um pouco a Internet, encontrei uma solução em que as tarefas são realizadas em paralelo.
Mapa Paralelo
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> =
coroutineScope {
map { async { f(it) } }.awaitAll()
}
, ( Iterable) pmap, . A. async , .awaitAll() . suspend, .
, , - .
val parmapTime = measureTimeMillis {
runBlocking {
val res = (1..10)
.toList()
.pmap { getDataAsync("$URL/$it") }
println(mapResult)
}
}
println(" pmap $parmapTime ")
- 1523, . map async, .
Parallel Map v 2.0
, , .
suspend fun <T, V> Iterable<T>.parMap(func: suspend (T) -> V): Iterable<V> =
coroutineScope {
map { element ->
async(Dispatchers.IO) { func(element) }
}.awaitAll()
}
val parMapTime = measureTimeMillis {
runBlocking {
val res = (1..10)
.toList()
.parMap { getDataAsync("$URL/$it") }
}
println(res)
}
println(" map $parMapTime ")
Dispatchers.IO 2 ~ 610 . ! ( , excel ..) . , - .
Java ParallelStream
, stackowerflow parallelStream. , IDEA.
val javaParallelTime = measureTimeMillis {
val res = (1..10).toList()
.parallelStream()
.map { getDataAsync("$URL/$it") }
res.forEach { println(it) }
}
println("Java parallelSrtream $javaParallelTime ")
, . , . stream . , , , "" , Json.
, - , async . .
Os resultados podem ser vistos na tabela a seguir. Para mim, definitivamente decidi deixar o modo assíncrono . Principalmente por causa do tratamento de erros mais simples, é claro. E não há necessidade de ir além das corrotinas aqui.
Método |
Tempo (ms) |
|---|---|
Método assíncrono |
1487 |
Implementação de Pmap da web |
1523 |
Minha opção é parallelMap |
610 |
Java.parallelStream |
578 |
No futuro, há ideias para organizar isso em uma pequena biblioteca e usá-lo para fins pessoais e, claro, reescrever tudo do "código hindu" para o humano, desde que haja possibilidades suficientes. E, em seguida, carregue tudo para o vds.
Espero que minha experiência seja útil para alguém. Eu ficaria feliz em receber críticas e conselhos construtivos! Obrigado a todos