25.11.2021 Евгений Галкин
Это тоже можно стримить
Streams
Евгений Галкин
• Разработчик в Тинькофф
• Java 10 лет
• Scala 1 год
• Делаю телефонного секретаря Олега
Задача – Преобразовать аудио файл в текст
3
Аудио Файл
@OlegOtvetBot
Задача – Преобразовать аудио файл в текст
4
Хранилище записей
Имя файла
Record.ogg Конвертация
в Wav Распознавание
в текст Файл
Record.ogg Файл
Record.wav Текст
разговора
Задача – Преобразовать аудио файл в текст
5
Хранилище записей
Имя файла
Record.ogg Конвертация
в Wav Распознавание
в текст Файл
Record.ogg Файл
Record.wav Текст
разговора
Задача – Преобразовать аудио файл в текст
6
Хранилище записей
Имя файла
Record.ogg Конвертация
в Wav Распознавание
в текст Файл
Record.ogg Файл
Record.wav Текст
разговора
Задача – Преобразовать аудио файл в текст
7
Хранилище записей
Имя файла
Record.ogg Конвертация
в Wav Распознавание
в текст Файл
Record.ogg Файл
Record.wav Текст
разговора
Extract Transform Load
Извлечение
данных Трансформация Загрузка
ETL
8
10s 10s 10s ???
Загрузить
файл Преобразовать
формат
Распознать в текст
Затрачено времени?
Время выполнения
9
10Mb >10Mb
Занимаемая память
10
10Mb >10Mb
Занимаемая память
11
10Mb >10Mb
10Mb >10Mb
>30Mb
100Mb >100Mb
Занимаемая память
12
10Mb >10Mb
1000Mb >1000Mb
>1110Mb
Классический подход
13
§ Легкий, простой
императивный код
§ Долго
§ Много и не предсказуемо по памяти
Streams
§ Простой функциональный код
§ Быстро
§ Мало и предсказуемо по
памяти
Загрузить
файл Преобразовать
формат
Распознать в текст
Время выполнения
14
Файл Результат
Загрузить
файл Преобразовать
формат
Распознать в текст
Время выполнения
15
Файл Результат
1S
Загрузить
файл Преобразовать
формат
Распознать в текст
Время выполнения
16
Файл Результат
1S 1S
Загрузить
файл Преобразовать
формат
Распознать в текст
Время выполнения
17
Файл Результат
1S 1S 1S
Загрузить
файл Преобразовать
формат
Распознать в текст
Занимаемая память
18
Файл Результат
4Kb 4Kb 4Kb
Инструменты
19
§ ZIO Streams
§ FS2 Streams
§ Akka Streams
Библиотеки
20
§ Doobie ( Database )
§ Tapir ( Http server ) (Akka, Http4s, ZIO Http )
§ Sttp ( Http client ) (Akka, AsyncHttpClient, Armeria, Http4s, HttpClient )
Создание
21
Stream.empty
Stream.succeed(22)
Stream.fromEffect(ZIO.succeed(22)) Stream.apply(1, 2, 3)
Stream.fromChunk(Chunk(1, 2, 3)) Stream.fromIterable(List(1, 2, 3)) Stream.range(1, 10)
Stream.iterate(1)(_ + 1)
Преобразование
22
Stream(1, 2, 3) .map(_ + 1)
.mapM(a => ZIO.succeed(a + 1))
.flatMap(a => Stream.succeed((a + 1))) .mapChunks(chunk => chunk)
.mapMPar(3)(a => ZIO.succeed(a + 1))
.flatMapPar(3)(a => Stream.succeed(a + 1)) .take(2)
.drop(1)
Разделение и объединение
23
Stream(1, 2, 3).partition(_ % 2 == 0) Stream(1, 2, 3).merge(Stream(4, 5, 6))
Выполнение
24
Stream(1, 2, 3).foreach(a => console.putStrLn(s"$a")) Stream(1, 2, 3).runHead
Stream(1, 2, 3).runDrain
Stream(1, 2, 3).fold(0)(_ + _)
Queue
25
for {
queue <- Queue.unbounded[Int]
stream = Stream.fromQueue(queue)
fiber <- stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain.fork _ <- queue.offer(22)
_ <- fiber.join } yield ()
Doobie
26
import zio.interop.catz._
import zio.stream.interop.fs2z._
val transactor: Transactor[Task] = ???
val stream = sql"select name from country"
.query[String]
.stream
.transact(transactor) .toZStream()
stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain
Doobie
27
import zio.interop.catz._
import zio.stream.interop.fs2z._
val transactor: Transactor[Task] = ???
val stream = sql"select name from country"
.query[String]
.stream
.transact(transactor) .toZStream()
stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain
Sttp отправка
28
val stream: Stream[Throwable, Byte] = Stream.succeed(22)
val request = basicRequest
.streamBody(ZioStreams)(stream) .post(uri"http://...")
request.send(backend)
Sttp получение
29
val request = basicRequest
.post(uri"http://...")
.response(asStreamAlwaysUnsafe(ZioStreams)) .readTimeout(Duration.Inf)
for {
response <- request.send(backend).orDie stream = response.body
_ <- stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain } yield ()
Java IO Interop
Java IO Interop
31
§ InputStream ( Читаем )
§ OutputStream ( Пишем )
int read() throws IOException;
void write(int b) throws IOException;
Streams в InputStream
32
def read(inputStream: InputStream): Unit = ???
val managed = for {
inputStream <- Stream.succeed[Byte](22).toInputStream
_ <- blocking.effectBlockingIO(send(inputStream)).toManaged_
} yield ()
managed.useNow
Managed
33
val managedStream: Managed[Any, InputStream] = Stream.succeed[Byte](22).toInputStream
def make[R, R1 <: R, E, A](acquire: ZIO[R, E, A])(release: A => ZIO[R1, Nothing, Any]): ZManaged[R1, E, A]
Managed.make(blocking.effectBlockingIO(new FileInputStream("file.txt")))(stream =>
blocking.effectBlockingIO(stream.close).ignore)
InputStream в Streams
34
def read(): InputStream = ???
val managed = ZManaged.fromAutoCloseable(blocking.effectBlockingIO(load())) val stream = Stream.fromInputStreamManaged(managed)
stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain
Streams из OutputStream
35
def write(outputStream: OutputStream): Unit = ???
val stream = ZStream.fromOutputStreamWriter(outputStream => write(outputStream)) stream.tap(value => console.putStrLn(s"$value")).take(1).runDrain
File
36
Stream.fromFile(Paths.get("file.txt"))
Stream.fromFile(Paths.get("file.txt"), chunkSize = 4096) Stream.fromResource("file.txt")
Stream.fromReader(new FileReader("file.txt"))
gRPC Bidirectional
Streaming over HTTP/2
Streams gRPC
38
service SpeechToText {
rpc StreamingRecognize(stream StreamingRecognizeRequest) returns (stream StreamingRecognizeResponse);
}
Распознавайте и синтезируйте речь с VoiceKit https://voicekit.tinkoff.ru/
Streams gRPC
39
val client: SpeechToTextClient.ZService[Any, Any] = ???
val stream = Stream.succeed[Byte](22)
val config = Stream.succeed(
StreamingRecognizeRequest(
StreamingRequest.StreamingConfig(
StreamingRecognitionConfig(Some(RecognitionConfig(AudioEncoding.LINEAR16, sampleRateHertz = 48000, numChannels = 1))) )
) )
val data =
stream.mapChunks(chunk => Chunk(StreamingRecognizeRequest(StreamingRequest.AudioContent(ByteString.copyFrom(chunk.toArray))))) val textStream = client.streamingRecognize(config ++ data)
val text = textStream.fold("")((acc, response) =>
acc + response.results.flatMap(result => result.recognitionResult.flatMap(_.alternatives)).mkString(" ") )
Streams gRPC
40
val client: SpeechToTextClient.ZService[Any, Any] = ???
val stream = Stream.succeed[Byte](22)
val config = Stream.succeed(
StreamingRecognizeRequest(
StreamingRequest.StreamingConfig(
StreamingRecognitionConfig(Some(RecognitionConfig(AudioEncoding.LINEAR16, sampleRateHertz = 48000, numChannels = 1))) )
) )
val data =
stream.mapChunks(chunk => Chunk(StreamingRecognizeRequest(StreamingRequest.AudioContent(ByteString.copyFrom(chunk.toArray))))) val textStream = client.streamingRecognize(config ++ data)
val text = textStream.fold("")((acc, response) =>
acc + response.results.flatMap(result => result.recognitionResult.flatMap(_.alternatives)).mkString(" ") )
Streams gRPC
41
val client: SpeechToTextClient.ZService[Any, Any] = ???
val stream = Stream.succeed[Byte](22)
val config = Stream.succeed(
StreamingRecognizeRequest(
StreamingRequest.StreamingConfig(
StreamingRecognitionConfig(Some(RecognitionConfig(AudioEncoding.LINEAR16, sampleRateHertz = 48000, numChannels = 1))) )
) )
val data =
stream.mapChunks(chunk => Chunk(StreamingRecognizeRequest(StreamingRequest.AudioContent(ByteString.copyFrom(chunk.toArray))))) val textStream = client.streamingRecognize(config ++ data)
val text = textStream.fold("")((acc, response) =>
acc + response.results.flatMap(result => result.recognitionResult.flatMap(_.alternatives)).mkString(" ") )
Streams gRPC
42
val client: SpeechToTextClient.ZService[Any, Any] = ???
val stream = Stream.succeed[Byte](22)
val config = Stream.succeed(
StreamingRecognizeRequest(
StreamingRequest.StreamingConfig(
StreamingRecognitionConfig(Some(RecognitionConfig(AudioEncoding.LINEAR16, sampleRateHertz = 48000, numChannels = 1))) )
) )
val data =
stream.mapChunks(chunk => Chunk(StreamingRecognizeRequest(StreamingRequest.AudioContent(ByteString.copyFrom(chunk.toArray))))) val textStream = client.streamingRecognize(config ++ data)
val text = textStream.fold("")((acc, response) =>
acc + response.results.flatMap(result => result.recognitionResult.flatMap(_.alternatives)).mkString(" ") )
Наша программа
43
val filenames = List("file1.ogg", "file2.ogg", "file3.ogg")
def loadFile(filename: String): UStream[Byte] = ???
def convert(inputStream: UStream[Byte]): UStream[Byte] = ???
def recognize(inputStream: UStream[Byte]): UStream[String] = ???
Stream
.fromIterable(filenames)
.flatMapPar(3)(filename => recognize(convert(loadFile(filename)))) .tap(line => console.putStrLn(line))
.runDrain
Наша программа
44
val filenames = List("file1.ogg", "file2.ogg", "file3.ogg")
def loadFile(filename: String): UStream[Byte] = ???
def convert(inputStream: UStream[Byte]): UStream[Byte] = ???
def recognize(inputStream: UStream[Byte]): UStream[String] = ???
Stream
.fromIterable(filenames)
.flatMapPar(3)(filename => recognize(convert(loadFile(filename)))) .tap(line => console.putStrLn(line))
.runDrain
Наша программа
45
val filenames = List("file1.ogg", "file2.ogg", "file3.ogg")
def loadFile(filename: String): UStream[Byte] = ???
def convert(inputStream: UStream[Byte]): UStream[Byte] = ???
def recognize(inputStream: UStream[Byte]): UStream[String] = ???
Stream
.fromIterable(filenames)
.flatMapPar(3)(filename => recognize(convert(loadFile(filename)))) .tap(line => console.putStrLn(line))
.runDrain
Наша программа
46
val filenames = List("file1.ogg", "file2.ogg", "file3.ogg")
def loadFile(filename: String): UStream[Byte] = ???
def convert(inputStream: UStream[Byte]): UStream[Byte] = ???
def recognize(inputStream: UStream[Byte]): UStream[String] = ???
Stream
.fromIterable(filenames)
.flatMapPar(3)(filename => recognize(convert(loadFile(filename)))) .tap(line => console.putStrLn(line))
.runDrain
Наша программа
47
val filenames = List("file1.ogg", "file2.ogg", "file3.ogg")
def loadFile(filename: String): UStream[Byte] = ???
def convert(inputStream: UStream[Byte]): UStream[Byte] = ???
def recognize(inputStream: UStream[Byte]): UStream[String] = ???
Stream
.fromIterable(filenames)
.flatMapPar(3)(filename => recognize(convert(loadFile(filename)))) .tap(line => console.putStrLn(line))
.runDrain
Классический подход
48
§ Легкий, простой код
§ Долго
§ Много и не предсказуемо по памяти
Streams
§ Легкий, простой код
§ Быстро
§ Мало и предсказуемо по памяти
§ Подходит для задач, где
данные можно обрабатывать
последовательно
tinkoff.ru
Евгений Галкин
tinkoff.ru
Полезные ссылки