🚀

Isolate じゃんじゃん使おうぜって話

3 min read

TL;DR

  • Dart 2.15 で Isolate の立ち上げが 100 倍高速化されました
  • 以前の Dart では「ちょっと重いくらいなら Isolate の立ち上げにかかる時間の方が長いから使わん方がええよ」が定石でしたが、今回の高速化でその心配がかなり薄れています
  • 元 Flutter Team の Fillip も、「今後は気にしなくてええんちゃう?」って言ってます

https://youtu.be/UKH9ulkribk?t=142

はじめに

Flutter でフェッチなど時間のかかる処理を行う際、何を使うべきでしょうか?答えは、 async/await でしょう。では、JSON のパースなど、重い処理を行う場合は?特に気にしてない?あるいは、同じように async/await で済ませている?そんな方は、ぜひお読みください。なお、本記事で Isolate の使い方には触れませんのでご容赦ください。使い方は、以下が詳しいです(ちょっと長いですが)

https://youtu.be/qrFTt1NZed8

非同期処理と並列処理の違い

async は asynchronous を指し、和訳すると「非同期的な」という意味です。従って、 await をつけて呼ぶ処理は「非同期的に」実行されることになります。

これを、仕事に喩えて考えてみましょう。社員 A がおり、あなたはその上司です。さらにあなたの上司 B から仕事が振られてくるので、その進め方を A に指示しなければいけないとします。ある日、 B から「100 件の顧客アンケートを取れ」と指示がありました。もちろん、いつも通りの細かい事務作業は継続しつつ、です。

なお、 Dart が原則シングルスレッドであることを踏まえ、あなた直属の部下は A しかいません。

https://medium.com/globant/single-thread-dart-what-ccbca2543ae9

同期処理の場合

あなたはいつもの事務作業と同じように、「順番にお願いね」と指示をします。A は指示通り、100 件の連絡先へ順番に電話をかけていきます。しかし、連絡先によってはすぐに答えてくれません。愚直な A は、アンケートの答えが得られるまで電話口で待っていたので、とんでもない時間がかかってしまいました。 その他にもたくさんの仕事が溜まっているのに…

非同期処理の場合

皆さんが現実世界で仕事をする場合、どんな進め方をするでしょうか? 呼び出し音を鳴らしている間、他の仕事をちまちまと進めるでしょうか。あるいは、そもそも電話を使わずにメールで一斉送信し、バラバラと返信が来るのを待つ間に他の仕事を進めるかもしれません。 これが、非同期処理です。この喩えを想像すると、「自分以外の誰かボール待ち」が発生する場合、非同期処理が有効なことがわかります。

非同期処理と並列処理の違い

前回のセクションで、非同期処理を使うと「待ち」が発生する場合に時間を節約できることがわかりました。同じ喩えを用いて、今後は重い作業を振られた場合を考えます。あなたの上司 B から「今日中にビール市場の調査資料をスライド100枚作ってくれ」と言われたとしましょう。前回同様、いつもの細かな事務作業は継続しなければならないとします。

非同期処理の場合

アンケートの件を教訓にしたあなたは、社員A に対して「非同期的に進めなさい」と指示をします。さあ、これで前回同様にスピードアップするでしょう!

…残念ながら、A のスライド作成は終わりませんでした。何よりもマズいのは、いつもの細かい事務作業が全く進んでいないことです。細かいけれど、毎日必ず実施しなければいけません。何とも絶望的な状況ですが、どうすればよかったのでしょうか?

並列処理の場合

あなたが今回見落としていた重大なポイントは、非同期処理が強みを発揮する「待ち」がどこにもないという点です。A はスライド作成が終わるまでつきっきりになってしまうので、細かい事務作業を進めるスキマ時間がありませんでした

スライド作成のような重い仕事と細かい事務作業を両立させる唯一の方法は、同僚を連れてきてあげることです。細かい事務作業を、新たに連れてきた社員 C にやらせることにしました。依然としてスライド作成は大変ですが、その横で C がいつもの事務作業を終わらせてくれました。これで、いつもの業務は滞りなく行われることになります。

たとえ話を Dart の世界へ

さて、ここまでのたとえ話を Dart の世界へ変換してみましょう。

  • 登場人物
    • 社員 A: Dart のメインスレッド
    • 社員 C: Dart のスレッド②
    • あなた: main 関数
    • 上司 B: プログラマー
  • 仕事
    • 普段の細かい事務作業: 画面描画
    • アンケート 100 件: ファイルのフェッチ
    • スライド作成 100 枚: JSON のパース
      2つ目のたとえ話で見た通り、JSON のパースのような重い処理を行っている間、画面描画はブロックされます。これが何をもたらすか?答えは「カクカクな画面」です。これが実際に起こることは、以下の記事で紹介されています。

https://zenn.dev/iwaku/articles/2021-01-03-iwaku

Dart 2.15 でのアップデート

ここまでの話を見ると、あらゆる処理を Isolate で処理すれば良いように思えます。しかしながら、これは誤りです。というより、誤りでした。なぜなら、 Isolate の立ち上げに時間がかかるため、軽い処理であれば同期処理で済ます方が高速だったからです

https://medium.com/flutter-jp/isolate-a3f6eab488b5

ところが、この状況を一転させたのが、2021/12/09 に発表された Dart 2.15 でした。派手なアップデートはありませんでしたが、その中でひときわ輝いていたのが Isolate の高速化です。

https://medium.com/dartlang/dart-2-15-7e7a598e508a

上掲の記事によれば、

  • 新しい Isolate の立ち上げにかかる時間が100倍高速化
  • 立ち上げた Isolate の使用に使うメモリが10〜100分の1に

It is now more than 100 times faster to start an additional isolate in an existing isolate group as we don’t need to initialize the program structures, and those spawned isolates consume between 10–100 times less memory.

ここまで劇的な変化であれば、 Isolate をジャンジャン使っても許される(?)のではないかと思います。重めの処理を Isolate で処理した場合のトータルがどのくらい高速化したのか、気が向いたら検証して追記します。

Discussion

ログインするとコメントできます