2 ポイント 投稿者 GN⁺ 2024-05-13 | 1件のコメント | WhatsAppで共有
  • 言語ランタイムや JIT のような代替実装は、性能が優れていても、正本実装の変化とユーザーの期待に追随し続ける必要があるため、採用が限られることがある
  • PyPy、LuaJIT、TruffleRuby は高速な実行性能を示したが、互換性のギャップと新機能を追跡する負担が、実際のデプロイにおける障壁となった
  • YJIT は別の Ruby 実装ではなく、CRuby 内部に入る道を選び、最初から CRuby の機能と 100% 互換になるよう作られ、Shopify、Discourse、GitHub などにデプロイされている
  • Crystal のように既存言語と非常によく似ているが互換性はない選択は、ユーザーに「ほぼ Ruby だが Ruby ではない」違いを常に意識させる可能性がある
  • JSON パーサーや JavaScript のように、公開標準が実装と分離されている領域では代替実装の負担は小さくなるが、正本実装が事実上の標準であるエコシステムでは別の戦略が必要になる

繰り返される代替実装の落とし穴

  • ソフトウェアの世界では、既存システムのより良い代替実装として出発したプロジェクトが、正本実装の影に閉じ込められることが繰り返されている
  • 代替実装は、機能、性能、エコシステム、ユーザーの期待において、標準のように受け入れられている**正本実装(canonical implementation)**と比較される
  • 正本実装が変化し続けると、代替実装は自らの方向性を定めるよりも、その変化に追随することに多くのエネルギーを使わなければならない
  • 伝統的にインタープリターだった言語に JIT 実装を付ける場合、新機能はインタープリター側により早く入りやすく、JIT 側の追跡負担が大きくなる

PyPy、LuaJIT、TruffleRuby に見られたパターン

  • PyPy は Python 向けの高度な JIT コンパイラーで、CPython と比べて大きな速度向上を実現できたが、実際の利用は非常に少なかった
    • Python は定期的に新しい CPython バージョンと機能が出る動く標的である
    • PyPy はそれに追随するのに苦労し、常に Python の数バージョン分遅れた状態になった
    • Python ソフトウェアを PyPy と互換にするには、使える Python 機能が制限され、多くの Python プログラマーはそのことを気にしたがらない
  • LuaJIT は基本的なインタープリター方式の Lua 実装より大きな性能向上を提供し、高い評価と一定の実採用を得た
    • LuaJIT の作者 Mike Pall は、多くの人から優れたプログラマーだと評価されている
    • Lua 言語が新機能を追加し続ける中で、LuaJIT も複数バージョン遅れている
    • 一部の Lua ユーザーはそのため LuaJIT の使用をためらっている
    • Lua はミニマリズムで知られる言語だが、新機能の追加を遅らせたり Mike Pall と調整したりする努力は行われなかった
  • TruffleRuby は Ruby JIT の中でも最も印象的な性能値を掲げたが、デプロイは限定的だった
    • 実用上の理由のひとつは、TruffleRuby のウォームアップ時間が CRuby よりはるかに長い点である
    • CRuby が機能を追加し続ける中で、TruffleRuby のコントリビューターはそれに追随するため努力しなければならなかった
    • Ruby ユーザーは CRuby を正本実装と見ており、完全に互換ではない実装は検討する価値が低いと考えている

YJIT が選んだ別の道

  • YJIT はもうひとつの Ruby JIT として始まったが、別実装ではなくCRuby 自体の内部に構築された
  • この選択はいくつもの設計上のトレードオフを生んだが、YJIT が当初からすべての CRuby 機能と 100% 互換になれるようにした
  • 現在 YJIT は Ruby の「公式」JIT であり、Shopify、Discourse、GitHub などにデプロイされている
  • github.com や Shopify ストアを訪れたユーザーは、YJIT とやり取りしたことになる
  • これまでの Ruby JIT コンパイラーの中で YJIT が最も大きな成功を収めており、その成功には互換性が核心的な役割を果たした

「勝てないなら合流せよ」だけでは不十分

  • 代替実装として自らを位置づけると、正本実装の影の中で追いつくゲームを続けることになる可能性が高い
  • 正本プロジェクトが進化し続けると、代替実装は自分のプロジェクトの方向性について限られた意思決定権しか持たないまま後を追わなければならない
  • 正本実装に合流するほうがより良い結果を生むこともあるが、それだけですべての場合が解決するわけではない
  • Ruby エコシステムの Crystal は、Ruby に似た構文を持つ静的コンパイル言語であり、型推論を使う
    • Crystal は意図的に Ruby 互換を目指さず、Ruby から分岐した
    • Rubyist には「ほぼ Ruby だが完全に Ruby ではない」言語に見え、実際には多くの微妙な違いと非互換性がある
    • こうした類似性はユーザーの期待を揺さぶり、混乱を生む
    • Crystal は最初から Ruby に似ているとマーケティングしなければ、より良い結果を得られた可能性がある

競争を避け、自分の方向性を持つ選択

  • Peter Thiel の「competition is for losers」は、不必要に競争しなければならない位置に自ら入るな、という文脈で使われる
  • 新しいプログラミング言語を作るなら、Python のサブセットや既存言語と表面的に非常に近い何かを作ろうとしないほうがよい、という助言につながる
  • 自分だけのものを作れば、他の実装の性能、機能セット、ライブラリエコシステムに合わせなければならないという期待に縛られず、自分の速度と方向でシステムを発展させられる
  • この助言は、言語やシステムに正本実装が存在する状況に適用される
  • 公開標準がある領域は例外になり得る
    • JSON パーサーは比較的小さく、急速に変化しない明確な仕様があるため、独自実装が可能な対象である
    • JavaScript には複数のブラウザー基盤の実装が存在し、これは JS 仕様を管理する外部の標準化団体があるため可能になっている
    • JS 標準の作業者は、JIT コンパイル実装が性能に重要であることを理解し、言語の進化をそれに合わせて導いている
    • 彼らは、できるだけ速く多くの新機能を追加するゲームをしているわけではない

1件のコメント

 
GN⁺ 2024-05-13
Hacker Newsの意見
  • OPが見落としている重要な点がもう1つある。代替実装を作ると、たいてい基準実装とはアーキテクチャが異なり、基準実装では簡単なことが自分の実装では非常に難しい場合がある。
    たとえば、財務報告向けの独占的ソフトウェアが文書を奇妙なバイナリ形式で保存しているとする。無料の代替を作るにあたって、文書全体をメモリに読み込み、保存時にはファイル全体を書き直す構造を選んだが、元の実装はRAMが少なかった時代に作られていて、ユーザーが作業中のセクションだけを読み書きし、その場での更新まで可能だった、と考えられる。
    その後、元の実装が文書に添付ファイルを入れる機能を追加すると、投資家向け通話の録音や数百ページのスキャンPDFのような大きなファイルも、セクション単位の読み込みのおかげで問題なく動作する。一方、自分の実装は文書全体を逆シリアル化するので、文書がユーザーのRAMより大きくなった瞬間に問題になる。そして、元の実装では開発者1人が1週間で終えられる変更のために、こちらではソフトウェア全体を再設計しなければならないかもしれない。

    • すべての中核ソフトウェア構成要素に2つの独立実装が必要な業界で働き、複数の再実装に関わってきたが、元の実装が明らかにXをやっていて、自分たちのアーキテクチャにはうまく合わない機能を見たことはある。それでも、その機能のためにプログラムを再設計しなければならなかったことはなく、一方では些細な機能がもう一方では少し手間のかかる仕事になる、という程度だった。
      逆に、元の実装では実装が難しかったのに、2つ目の実装では些細になった機能もあった。大規模な再設計は一度だけで、それも指数的爆発を生む前提を持つ元の実装のほうで起きた。
      公開されている例としては、LinuxでWindowsアプリケーションを実行する問題がある。LinuxカーネルはNTとはまったく異なる実装で、互換性を目標にもしていないが、Windowsアプリの実行にカーネル全体の再設計が必要なわけではない。比較的一般的なカーネル機能いくつかと、ユーザー空間の互換レイヤーがあればよい。Wineの作成と保守には大きな労力がかかるが、Windows自体の実装よりははるかに少なく、Windows互換を目指していなかったプラットフォーム上で動作している。ただし、記事で述べられているように、Windowsを追いかけ続ける必要があり、基準実装のバグに依存するコードまで出てくるため、バグまで互換にしようとすると、まずどのバグを実装すべきかを突き止めなければならない。
    • 著者は、インタープリタ言語とコンパイル言語では新機能の実装難易度が異なる、という部分ですでにこの点に触れているように思う。
    • その通り。これはPythonを高速化するうえでの古典的な問題だ。CPythonは、最適化なしでコードの指示どおりに動く単純なインタープリタとして始まり、すべてが辞書であり、実質的な並行性もほとんどない。
      そのため、どんなコードでも実行中に別のものを変更できる。実際にそこまで多用されるわけではないが、取り除こうとすると人々が強く反発する。Pythonを実際にコンパイルする実装では、あるスレッドが突然ほかのスレッドの足元にある何かを書き換えるような状況まで処理しなければならない。
    • イノベーションのジレンマの観点では、参照実装が競合他社のものであることもあり得る。市場への理解が深まったいま、よりよいアーキテクチャを選べば、競合よりも安く速く新機能を追加できる立場に立てる。
      これは小さな会社が大きな会社の重荷を増やす方法の1つであり、技術的負債をあまり放置してこなかった会社にも可能だ。経営陣に技術的負債を明確に示せる数少ない瞬間でもある。なぜなら「Acmeよりこの機能の実装に時間がかかる」と言えるからだ。
    • こうしたアーキテクチャの違いが意図的な場合もある。たとえば、多くの基本的なUnixユーティリティのGNU版は、著作権侵害の疑いを避けるために、元のUnixやBSD版とはまったく異なるトレードオフを採っている。
  • 「Pythonのサブセットを作ろうとするな」という主張には同意する。「PythonだがXがより良い」とマーケティングされるプロジェクトは、基準実装と競争するのが常に難しく、特にXが速度ならなおさらだ。動的型付け言語を使う人は、結局のところ実行速度をそれほど重視しないことが多い。
    しかし、代替実装が常に失敗するわけではない。MicroPythonはPython 3.4相当より先をほとんどサポートしていないのに、かなり成功しているようだ。マイクロコントローラで動作するよう設計されており、CPythonと競争しているのではなく、別のマイクロコントローラ向けプログラミング環境と競争しているからだ。
    ただ、MicroPythonのメンテナは、もっと新しいPython機能の要望を多く受けている気がする。以前、アプリケーション組み込み向けに軽量で埋め込みに特化したPythonの代替実装を考えたことがあるが、この場合もCPythonではなくLuaと競う構想だった。ところが、最優先の機能要望は「NumPyをサポートしていますか?」だった。

    • 興味深いことに、MicroPythonは今ではWeb用途でも関心や要望を集めている。PyScriptのすばらしい取り組みのおかげだ。しかし、総RAMが1MBのマイクロコントローラと違って、Webでは既存コードが動き、完全なCPython体験との互換性があることが期待されるので、はるかに難しい。
      同時に、CPython側でもフロントエンドでよりうまく動作させようとする作業が進んでいるようで、中心的な痛点はパッケージサイズだ。
  • スタートアップを作る中で似たようなことを学んだ。やり直せるなら、自分たちの分野における最低限求められる機能を積極的に避けただろう。
    その代わり、エンタープライズ的な要件を自分たちのアーキテクチャが支えられると確信させるための最小限だけを作り、「ああ、これがどこまで行けるか見える」と思ってもらえる差別化要素にすべて集中すべきだった。「ただのXのコピーだね」といった反応を招く機能ではなく。

    • 言いたいことはわかるが、「table stakes」という用語は誤って使われている。定義上、それはゲームに参加するために必ず支払わなければならないものだ。
      ここで述べられている戦略は、基本的な必須機能は実装するが、その先の「一般的な」拡張機能までは深く踏み込まない、というものに近い。興味深い機能で再び連絡をもらえるようにしつつ、同時に必須要素が足りなくて脱落しない程度の基本機能は備える、というやり方だ。
    • 正しく理解しているなら、企業顧客に必要な最小限、つまり彼らのリーダーシップが取引を承認するために要求するものだけを実装し、それ以上は忘れて、競合と差別化できる本当に新しいことに集中せよ、という意味か?
    • iPhoneが最初に登場したとき、コピー&ペーストすらなく、GSMの無線性能も非常に期待外れだったのと似ている。
  • ほぼすべてのラッパーコードに同じような印象を持つ。誰かが「この API の内部向けバージョンが必要だ」と言うことがある
    理由はいろいろあるが、「公式 API をちゃんと使うとは信頼できない」といったものだったりする。そういう場合もあるかもしれないが、内部向けバージョンは標準化も弱く、ドキュメントもさらに貧弱だ
    ときには「追加機能が必要だ」が理由だが、そうなら API 全体を包まずに関数を 3 つ追加すればいい。時間がたつとコードベースの 99% がポリフィルになりかねない
    要するに、デフォルトを使わないと、あとでそのコードベースを引き継ぐ人に大きな苦痛を与えるということ

    • そうしたラッパーには、下層ライブラリと直接やり取りできるインターフェースがあると助かると思う
      たとえば Zig で Python ネイティブモジュールを書く ziggy-pydust ライブラリは、一般的な Python.h の import より明らかに見やすい。それでも、まだ実装されていないが Python.h にある関数へ直接アクセスするための .ffi もある
      こうした選択肢がないと、たいていその種のライブラリは捨てて元のものを好むようになる。ただし、これもある意味ではラッパー、つまりネイティブモジュールであり、開発速度のためにはときに ctypes を直接使うほうがよい場合もある
  • 良い記事で、優れた教訓も多いが、核となる材料が 1 つ欠けている。これはどんな製品に対する競合代替案にも似ている
    Amazon が、人々が慣れ親しんだ実店舗の書店を持てなかったから失敗した、というような話に近いが、実際にはそうではなかった
    こうした JIT の代替実装が失敗し、追随するばかりだった理由は、実務上 X 言語の開発者の大半がJITをそれほど重要視していないからだ。より正確には、言語機能と相互運用性を JIT より重視している
    だから競争する製品よりも「合流する」製品が勝つ。より高い安定性や相互運用性を提供できないからだ

  • 長いあいだ言語とコンパイラを扱ってきたが、この記事は非常に面白かった。同じ考えを別の言い方で述べるなら、言語とは単なるコンパイル速度以上のものだ
    コンパイル速度は非常に重要で、間違いなく上位 10 個の次元には入る。特にコンパイル速度を高めれば、開発者のフィードバックループが速くなり、中核チームがほかのあらゆる次元もより速く改善できる
    それでも、プログラミング言語には非常に重要な次元が 30 個以上さらにある

    • 記事は実行速度について述べているように見える。それでも、そこでさえ CPython の人気を見ると、実行速度が最優先要素でないことは明らかだ
  • こうしたプロジェクトが成功する方法についての結論は良いが、多くのプロジェクトがなぜ広まらないのかについては、まだ十分に触れられていない要素がある。代替実装の互換性は、実際には主張されるほど高くないことが多く、古い言語機能についても同様だ
    たとえば Ruby や Python のアプリでは、依存関係のどこかにネイティブ C 拡張があることが非常に多いが、主要な代替実装はこれをサポートしたことがないと理解している。試みはあったが、明白な技術的理由でうまくいかず、ライブラリ側に複数の実装対応を期待する代案も歴史的に順調ではなかった
    さらに、こうした言語が CPU より I/O のほうが大きな性能要因となる CRUD ウェブサイトでよく使われるという事実まで合わせると、より高速な代替案の魅力は大きく薄れる

  • 本当に素晴らしい記事だ。技術の社会学はとても興味深い
    言語実装のメンテナーは、ユーザーに有益な新機能を設計・展開するとき、できるだけ最大限の柔軟性を望む。機能を出す前に複数実装の合意を取る必要があって足を引っ張られたくはない。例として、JavaScript の進化が何年ものあいだどれほど氷河のように遅く感じられたか、そして TypeScript が比較的どれほど速く進化しているかを見ればよい
    同時に、代替実装は言語エコシステムが健全であることのシグナルにもなり得るので、利点もある。代替実装が本当に優れていれば、一部のニッチな要求を持つユーザーにしか役立たなくても、エコシステムに実際の価値を加えられる
    だから言語設計者やメンテナーであれば、代替実装に積極的に敵対はしないだろうが、欠点はある。一般にユーザーからのフィードバックは、新機能を出して言語を進化させてほしいという方向に向かう。PyPy、IronRuby、LuaJIT などが追いつけるように速度を落としてくれ、という要望は多くないだろう
    言語の利用者がどの実装の上に構築するかを選ぶとき、最大の優先事項はたいてい安全性と安定性だ。かつて非常に賢い博士課程の学生が作ったが、今は別のプロジェクトへ去ってしまった代替実装の微妙な挙動特性に、100 万行のコードベースが依存するような状況を望む人はいない。だから利用者は最も広く使われている実装へ集まり、その事実がさらに別の利用者を引き寄せる強い正のフィードバックループが生まれる
    結果として、逆方向へ押し返す強い力がない限り、ほとんどの言語は 1 つの基準実装へ収束する。これは良いことだと主張することもできる。言語実装に投入される工学的努力のほぼすべてが複数実装に分散されず、すべての利用者の利益になるからだ。もちろん欠点は、実装が局所最適解に閉じ込められる可能性があることだ

  • LuaJIT も言及されていたが、常に記事の結論どおりになるわけではない例でもある。多くの人やプロジェクトが、Lua ではなく意図的にLuaJITを選んできた

    • それを言うためにコメントしに来た。LuaJIT はTeX コミュニティで熱心に使われており、luajittex 実行ファイルも luatex と並んで容易に入手できる
  • あまり人気のない考えかもしれないが、人はときどき自分自身も含めて自尊心を点検すべきだ。既存のオープンソースプロジェクトに貢献するより、「自分のもの」である並行プロジェクトを作るほうが簡単なこともあるが、それが誰のためなのかを問うべきだ
    プロジェクトのメンテナーのためなのか、プロジェクトそのもののためなのか、ユーザーのためなのか、それとも自分の自尊心のためなのか。最後の項目に腹が立つなら、おそらく図星だ
    既存言語にJITを追加するのは大仕事なので、リファレンス実装が受け入れる基準も高くなるだろう。それでも目標はそこに置くべきだと思う。フォークしたり新しく作ったりすることは大きなことをする自由を与えてくれるが、多くの場合は一時的なものと見なすべきだ
    目標が自分に何ができるかを示すことなら、遠くまでは行けない可能性が高い。より良いものを作ることが目標なら、他人の制約の中で働く方法を学ぶことになる

    • 驚くことに、私は逆の考えだ。単一プロジェクト、リファレンスプロジェクトへの貢献だけ、代替実装は不要だと説教する人たちこそ、自尊心を点検し、複数のアプローチの存在と、競争が独占より優れている理由を学ぶべきだ
      この記事は、Python、Lua、Rubyがそのようなアプローチを取り、多くの人を失望させたやり方を示していると思う。その結果、何千人もの開発者と何百万人ものユーザーが、より遅い開発とソフトウェアを受け入れることになった。不可能だからではなく、制度上そうするインセンティブがないからだ
    • うーん、公式実装が完全に呪われている場合もあって、自分だけの小さな庭を作らなければならないこともある。たとえそれが20エーカーと輪作計画にまで広がったとしても、ときには火山よりましだ