Spinach Forest

March, 2025

/ TIL: Scheduled(ThreadPool)Executor has no thread size upper bound   / Peak PL?   / Links of Feburary   / TIL: WITH RECURSIVE   / ... 

TIL: Scheduled(ThreadPool)Executor has no thread size upper bound

(2020 年代に Java の話ですまぬ仕事なので・・・)

表題通り。ScheduledThreadPoolExecutor はスレッドプールのスレッド数の上限を指定することができない。そしてこれが Java 標準で入っている唯一のスレッドプール対応 ScheduledExecutorService である。"Scheduled" というのは「何ミリ秒待ってから実行してね」みたいな API があるということです。

これ何が困るかというと、たとえばアプリの初期化とかで重いタスクをポイポイっと投入すると、投入した数だけスレッドができて並列に動いてしまうわけです。やめてくれ!クリティカルパスの CPU サイクルが奪われちゃうじゃないか!

なおスレッド数の上限を指定できる ThreadPoolExecutor という実装もあるが、これは "scheduled" な API が存在しない。困る。みんなどうしてるのかな・・・とおもって検索したら社内では欲しいものが誰かによって実装されていた。が、残念ながら Guava には入っていなかった。

"Scheduled" とかいらなくね?という気もするが、残念なことに Kotlin interop においては必須である。なぜなら Executor.asCoroutineDispatcher() で作った CoroutineDispatcher は、その Executor が scheduled でないと schedule の必要な操作 (delay など) をしたときサラっと内部の executor に fallback するという恐しい実装になっているからである。やめろ!!! delay() のあとはもとの executor に戻ってくるのかもしれないが(というか常識的に考えてそうなっているはずだがコードをパッと見た感じでは怪しいような)・・・

まあ適当になんとかしますが、みなそれぞれに出来が悪い。


なおいちおう挙動を確認してみたところ じっさいに fallback 先の DefaultExecutor でタスクが実行されることは確認できたが思わぬ行動もあり、自分が Kotlin coroutine continuation の実装を理解していないことがわかりました。しらね・・・

Peak PL?

プログラミング言語の進化は、このところ減速気味ではなかろうか。

あ、前提として自分は PL の専門家ではないどころか割と保守的なプログラマなので、そうでないという主張があれば喜んで聞き入れたいです。はい。

最近のメインストリームな言語は、どれも割とよくできている。クライアントサイドだと TypeScript に Swift, Kotlin. サーバサイドだと Go。あと TypeScript はサーバサイドでも使われてますね。システムレベルには Rust がある。

こうした言語はどれもモダンである。Go は意図的にモダン感、というのはつまり、様々な文法機能を拒否しているが、それはそれで受け入れられている。少なくとも作りがショボいせいで機能を増やせないわけではない。エコシステム、すなわちパッケージ管理とかも、割と不満なく使われている。TS の裏にいる Node まわりは不満な人もいるかもしれないが、それでも YARN とかでそれなりに納得してるのではないか。

一世代前の言語、すなわち Java や Python、あと Ruby や vanilla JS しても、色々と不満はあれど「完全に理解したが難しくてわけわかんない」感じはない。これは、更に一世代前の言語、すなわち C(++), Perl, Emacs Lisp などとは対照的である。

一方、将来のメインストリームとなりそうな新進気鋭の言語は、さほど多く見られない。しいていえば Zig なんかは Hashimoto 氏も応援していたりで見どころはありそうだが、ちょうクール!スタートアップは Zig 使いを雇えあいつらはハッカーだから!、みたいな雰囲気はないし、機能的にもさほどとんがった感はない(ある?)。Julia はトンガリ指数は高いが、一方で本来なら対象となるはずのトンガリエーアイ人材に使われている様子はない。同じポジションで鳴り物入りデビューを果たした Mojo も buzz を聞かない。トンガリエーアイの皆さんは Python とか使ってる。なんでやねん。

というわけでプログラミング言語界隈、外からみると一時期ほどの勢いは感じない。

ただ「停滞している」というのも抵抗がある。なぜならモダン言語はきちんと開発が続いており、バージョンアップを通じて恙無く改善が進んでいるからである。Java や Python のような「レガシー言語」ですら、じりじりと進歩がある。

この「出来の良いメインストリーム言語がインクリメンタルに進化していく」状態は、長期的な均衡点 (aka: peak, endgame) なのだろうか。


まず、そうだと言える理由を考えてみる:

モダンメインストリーム言語は出来がいい。大きな不満がない。具体的には:

  • 言語仕様だけでなく、言語処理系自体が柔軟である。マクロやコンパイラ・プラグインなどを通じ、レガシー言語では実現できなかったメタプログラミングが実現されている。(vs. たとえば C/C++)
  • 速い。JVM, JSVM, LLVM, そのほか、資金力とガチ勢の書いたコンパイラに支えられている。(vs. たとえば Ruby, Python)
  • きちんとオープンソースである (vs. Java - GPL だが開発元がガメついので嫌われがち)

言語仕様がそこそこマトモでかつ処理系に柔軟性があると、たとえばニッチでとんがった関数型言語 (eg. Haskell, ML) に影響を受けた人も落とし所をみつけやすい: メインストリームに入りそうな機能はバージョンアップで受け入れられ、実験的な機能はコンパイラプラグインやマクロ、実行時 AST アクセスなどでカバーされる。たとえば Jetpack Compose, Triton みたいなやつね。

速度も、特にオープンソースとの組み合わせでメインストリームに分がある。CPU 業者が自分からパッチを書いてくれる LLVM を相手に新興言語が戦うのは難しい、気がする。

きちんとオープンソースしていて、コミュニティの作ったエコシステムが強いのもモダンメインストリーム言語の強みである。先に列挙したモダン言語だと Swift が相対的にはいちばんクローズドだろうが、そうはいっても先代の言語と比べたら圧倒的にオープンといえる。そしてクローズドな部分は、主にクローズドなターゲット環境(高級電話機、ラップトップ)に隣接する部分である。そこのクローズさは、人々はさほど気にしていない(気にする人は代替品を使っているので。) モダンメインストリーム言語には反権力を煽る悪役感がない。Go の開発もわりかし一社に閉じていてオープンさでは Java 以下にも見えるが、ライセンス含め今のところうまくバランスを取っている。

更に現代的な理由として、

  • プログラミング言語をありがたがるのが前時代的で古い。

と見なされている可能性もある。なぜならエーアイで vibe coding するのが時代の流れだからである。レビューとデバッグするだけなら JS と Python でもいいじゃん、みたいな。

こうした理由から、現状を打破する次世代言語が登場するようには思えない。


いや、現状はせいぜい一時的な小休止であって、新しい言語はあわれる。と主張できる理由を考える。

  • モダンメインストリーム言語はまだ若い (10 歳前後?) そのうちボロがでるだろう。
  • 今は要素技術すなわち LLM などエーアイの進歩が早く関心と資金を集めているが、そのうち一段落して落ち着いたら、エーアイスタックの成熟の一環として新しいプログラミング言語も生まれるだろう。
  • おまえのようなおっさんが知らないだけで次世代プログラミング言語は着々と歩みを進めてるんだよ。

若さ。Java も Python も昔はクール言語だったんだよ。ほんとだよ。

エーアイ。あんたらほんとは Python イヤなんでしょ? GPU 予算が余ったらプログラマ雇って言語作らせてもいいんだよ?

おっさん。特に申し上げることはありませんが。おっしゃることがある場合はお知らせください。


言語化できる根拠の薄さに反し、個人的にはそのうちまたクール言語の時代が来るんじゃないかな、来るといいなと思ってます。ただいまこの瞬間にはその匂いを嗅ぎ取れないだけで。

むかし Pragmatic Programmer という本が "Learn at least one new language every year." といった頃、それはつまり Paul Graham が "Frankly, the fact that good hackers prefer Python" と煽ったのと大体同じ時代だが、プログラミング言語はどんどん新しいのがでてクールになっていく前提があったと思う。

今そういうクールファクターをプログラミング言語に感じられないのは自分個人の老化なのか、時代のフェーズなのか、どちらなのだろうね。少なくとも誰かが何らかの形で Python を倒す必要はあると思うんだけど。

Links of Feburary

エーアイ

その他テック

どうでもいい


TIL: WITH RECURSIVE

いくつか並列実行する部分区間をもつ E2E のレイテンシを分析する際、実行フローのクリティカルパスを特定したい。レイテンシのデータは field trace から取り出す。手元での計測ではなく、世の中(社内ベータテスト人口)の遅いケースではどんなクリティカルパスが支配的なのか。

・・・というような分析をするにあたり、データを Python に持っていくのではなく、できれば SQL だけで済ませたい。そこで SQL で shortest path を計算できないかチャットに聞いたら 「WITH RECURSIVE を使え」" という返事が来た。(実際は Claude でなく社内のチャットに聞いた。だいたい同じ返答だった。)

なんだそれ・・・ということで実際に試してみる。 (動かしてみたいひとはこの notebook をどうぞ):

WITH RECURSIVE paths AS (
  SELECT
    weight AS distance,
    dst AS tail,
    src || '.' || dst AS name
  FROM edges
  WHERE src = 'A'
  
  UNION ALL
  
  SELECT
    paths.distance + edges.weight AS distance,
    edges.dst AS tail,
    paths.name || '.' || edges.dst AS name
  FROM paths
  INNER JOIN edges ON edges.src = paths.tail
)
SELECT distance, name
FROM paths
WHERE tail = 'I'
ORDER BY distance

ここで "UNION ALL" を使うのは、わかるようなわからないようなかんじ。パーサは UNION ALL を使ったこの構造を特別に検知して再起(ループ)実行を plan するらしい。SQL なのに限りなく手続き的。

このアプローチはすべての経路を列挙するので効率的とはいえない。ダイクストラとかできないかな・・・とおもったが自分の SQL 力ではわからなかった。ただ自分の扱うフローは小さなグラフなので、このくらいの非効率は問題なさそう。

なおチャットはダイクストラもスルッと書いてよこしたが、正しいのかいまいちよくわからなかった・・・

-- Written by Claude. 
-- Not sure whether this is correct or a slop.
WITH RECURSIVE Dijkstra AS (
    -- Base case: starting node with cost 0
    SELECT 
        source_node AS node,
        0 AS cost,
        ARRAY[source_node] AS path
    FROM (SELECT 'A' AS source_node) s  -- Replace with your start node
    
    UNION ALL
    
    -- For each iteration, select the lowest-cost unvisited node
    SELECT 
        e.destination_node,
        d.cost + e.edge_weight,
        d.path || e.destination_node
    FROM Dijkstra d
    JOIN graph_edges e ON d.node = e.source_node
    WHERE NOT e.destination_node = ANY(d.path)
    -- This is critical - only process the lowest-cost node in each iteration
    AND NOT EXISTS (
        SELECT 1 FROM Dijkstra d2
        WHERE d2.cost < d.cost AND NOT EXISTS (
            SELECT 1 FROM Dijkstra d3
            WHERE d3.node = d2.node
        )
    )
)
SELECT node, path, cost
FROM Dijkstra
WHERE node = 'Z'  -- Replace with your destination node
ORDER BY cost
LIMIT 1;