[Программирование, Kotlin] Параллельные запросы в Kotlin для автоматизации сборки данных
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! В своей работе я часто использую Kotlin для автоматизации. Деятельность моя не связана напрямую с программированием, но Котлин здорово упрощает рабочие задачи.Недавно нужно было собрать данные немаленького размера, дабы сделать анализ, поэтому решил написать небольшой скрипт, для получения данных и сохранения их в Excel. С последним пунктом проблем не возникло - почитал про Apache POI, взял пару примеров из официальной документации, доработав под себя. Чего не скажешь про запросы в Сеть.Источник отдавал пачками json и надо было как-то быстро эти "пачки" собирать, преобразовывая в текст и записывая в файл таблицу. Асинхронный методНачать решил с простой асинхронщины. Немного поковыряв HttpUrlConnection, отправил туда, где ему и место, заменив на HttpClient из Java.Для тестов взял сервис https://jsonplaceholder.typicode.com/, который мне подсказал один знакомый разработчик. Сохранил ссылку, которая выдает Json с комментариями в переменную, дабы не дублировать и начал тесты.
const val URL = "https://jsonplaceholder.typicode.com/comments"
Функция была готова и даже работала. Данные приходили.
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()
}
Теперь надо было проверить скорость работы. Вооружившись measureTimeMillis я запустил код.
val asyncTime = measureTimeMillis {
val res = (1..10)
.toList()
.map {getDataAsync("$URL/$it")}
res.forEach { println(it) }
}
println("Асинхронный запрос время $asyncTime мс")
Все работало как надо, но хотелось быстрее. Немного покопавшись в Интернете, я набрел на решение, в котором задачи выполняются параллельно.Parallel Map Автор в своем блоге пишет, что код ниже выполняется параллельно с использованием корутин. Ну что, я давно хотел их попробовать, а тут представилась возможность.
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 таких проблем не возникло. Там можно контролировать данные на каждом шаге итерации и удобно обрабатывать ошибки.ВыводыРезультаты можно посмотреть в таблице ниже. Для себя я однозначно решил оставить async await. В основном конечно из-за более простой обработки ошибок. Да и за пределы корутин тут выходить не надо.Метод Время (ms)Асинхронный метод1487Реализация pmap из Сети1523Мой вариант - parallelMap610Java.parallelStream578В дальнейшем, есть мысли оформить это в небольшую библиотеку и использовать в личных целях, и конечно переписать все это с "индусского кода" на человеческий, на сколько хватит возможностей. А потом залить все это на vds.Надеюсь мой опыт кому-нибудь пригодится. Буду рад конструктивной критике и советам! Всем спасибо
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Совершенный код, Проектирование и рефакторинг, Тестирование веб-сервисов, TypeScript] Инверсия контроля на голом TypeScript без боли
- [Программирование, Совершенный код, Терминология IT, Управление разработкой] Культ лучших практик (перевод)
- [Программирование, Управление разработкой, Управление продуктом, Микросервисы] Суровая правда о разработчиках и разработке
- [Open source, Программирование, Dart, Flutter] Как создать кастомный плагин для Dart-анализатора
- [Программирование, Разработка мобильных приложений, Разработка под Android, Kotlin] Как заблокировать приложение с помощью runBlocking
- [Программирование] Заметки о codestyle
- [Assembler, Программирование микроконтроллеров, Разработка под Arduino, Электроника для начинающих] Управление LCD и OLED дисплеями на AVR-ассемблере
- [Алгоритмы, Программирование микроконтроллеров, Производство и разработка электроники] Бинарный поиск в микроконтроллере
- [Разработка веб-сайтов, Программирование, Сетевые технологии, Программирование микроконтроллеров] Разрабатываем web-site для микроконтроллера
- [Программирование, DevOps, Kubernetes] Круглый стол «Нужно ли разработчику знать Kubernetes» 11 февраля
Теги для поиска: #_programmirovanie (Программирование), #_kotlin, #_korutiny (корутины), #_coroutines, #_kotlin, #_parallel, #_concurrency, #_programmirovanie (
Программирование
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:42
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! В своей работе я часто использую Kotlin для автоматизации. Деятельность моя не связана напрямую с программированием, но Котлин здорово упрощает рабочие задачи.Недавно нужно было собрать данные немаленького размера, дабы сделать анализ, поэтому решил написать небольшой скрипт, для получения данных и сохранения их в Excel. С последним пунктом проблем не возникло - почитал про Apache POI, взял пару примеров из официальной документации, доработав под себя. Чего не скажешь про запросы в Сеть.Источник отдавал пачками json и надо было как-то быстро эти "пачки" собирать, преобразовывая в текст и записывая в файл таблицу. Асинхронный методНачать решил с простой асинхронщины. Немного поковыряв HttpUrlConnection, отправил туда, где ему и место, заменив на HttpClient из Java.Для тестов взял сервис https://jsonplaceholder.typicode.com/, который мне подсказал один знакомый разработчик. Сохранил ссылку, которая выдает Json с комментариями в переменную, дабы не дублировать и начал тесты. const val URL = "https://jsonplaceholder.typicode.com/comments"
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() } val asyncTime = measureTimeMillis {
val res = (1..10) .toList() .map {getDataAsync("$URL/$it")} res.forEach { println(it) } } println("Асинхронный запрос время $asyncTime мс") suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> =
coroutineScope { map { async { f(it) } }.awaitAll() } val parmapTime = measureTimeMillis {
runBlocking { val res = (1..10) .toList() .pmap { getDataAsync("$URL/$it") } println(mapResult) } } println("Время pmap $parmapTime мс") 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 мс") val javaParallelTime = measureTimeMillis {
val res = (1..10).toList() .parallelStream() .map { getDataAsync("$URL/$it") } res.forEach { println(it) } } println("Java parallelSrtream время $javaParallelTime мс") =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:42
Часовой пояс: UTC + 5