世界を動かす技術を、日本語で。

TRAMPをより速くする

概要

  • EmacsのTRAMPを高速化するための設定と工夫を解説
  • TRAMPの遅さの原因やボトルネックの分析方法を紹介
  • MagitやLSPなど主要パッケージとTRAMPの相性・対策
  • キャッシュ活用や非同期プロセス最適化による効率化手法
  • 今後のTRAMP改善アイデアやディスカッションへの誘い

Emacs TRAMPを快適に使うための高速化ガイド

  • TRAMP はEmacs用のリモートアクセスパッケージで、リモートマシンをローカルのように操作可能
  • SCPやrsyncなど多様なプロトコル対応、VSCodeのRemote Development Extensionに類似
  • デフォルト設定だと 動作が遅い ことが多く、特に既存設定との組み合わせで顕著
  • emacs -Q (クリーン起動)ではTRAMPも軽快に動作する場合が多い
  • 基本設定例:
    • ロックファイルやオートセーブ抑制
      (setq remote-file-name-inhibit-locks t
            tramp-use-scp-direct-remote-copying t
            remote-file-name-inhibit-auto-save-visited t)
      
  • ファイル転送方式(inline/アウトオブバンド)の選択と閾値調整が重要
    • デフォルト10KBだが、 2MB程度までinlineが高速 な場合が多い
    • 例:
      (setq tramp-copy-size-limit (* 1024 1024)  ; 1MB
            tramp-verbose 2)
      
  • rsyncはSCPより 既存ファイル更新が高速 だが、リモートシェルとの相性注意

Direct Async Processの活用

  • 非同期プロセス は従来TRAMP経由だと非常に遅い
  • TRAMP 2.7以降は direct async process 導入で大幅に改善
    • 設定例:
      (connection-local-set-profile-variables
       'remote-direct-async-process
       '((tramp-direct-async-process . t)))
      (connection-local-set-profiles
       '(:application tramp :protocol "scp")
       'remote-direct-async-process)
      (setq magit-tramp-pipe-stty-settings 'pty)
      
  • Magitやgit-gutterなどのパフォーマンス向上

コンパイル時のSSHコントロール維持

  • 新しいTRAMPは SSHコネクション共有 で高速化
  • compileコマンド実行時もコントロール維持推奨
    • 設定例:
      (with-eval-after-load 'tramp
        (with-eval-after-load 'compile
          (remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))
      

パフォーマンス問題のデバッグ方法

  • M-x profiler-start で遅い操作のプロファイリング
  • tramp-wait-for-output が目立つ場合TRAMPがボトルネック
  • debug-on-entry でtramp-send-command呼び出し元特定
  • doom-modelineやforgeなど、 TRAMP非対応のパッケージ機能 を無効化推奨
    • 例:
      (remove-hook 'evil-insert-state-exit-hook #'doom-modeline-update-buffer-file-name)
      (remove-hook 'find-file-hook #'doom-modeline-update-buffer-file-name)
      (remove-hook 'find-file-hook 'forge-bug-reference-setup)
      

MagitのTRAMP最適化

  • Magit はEmacsの強力なgitインターフェースだが、TRAMP経由では極端に遅い
    • magit-statusで10~20秒かかる場合も
    • ステータスバッファ更新のたびに多くのシェルコマンド実行
  • 対策:
    • magit-dispatchmagit-file-dispatch の活用で必要最小限の操作
    • ステータスバッファは全体状況把握や大量ファイル操作時のみ利用
    • シェル直接実行(M-S-!)で部分的なgit操作
    • 不要な自動リフレッシュやdiff表示の無効化
      • 例:
        (setq magit-commit-show-diff nil)
        (setq magit-branch-direct-configure nil)
        (setq magit-refresh-status-buffer nil)
        
    • キャッシュ機能の追加も有効(今後Magit本体での対応予定)

LSPとTRAMP

  • LSP-mode はTRAMP経由でも動作するが、direct async processは非対応
  • lsp-bridgeも選択肢だが、リモートPythonのFFI対応が必要
  • リモート時は LSP自動有効化を回避 し、Eldocやcompletionも停止
    • 例:
      (defun $lsp-unless-remote ()
        (if (file-remote-p buffer-file-name)
            (progn (eldoc-mode -1)
                   (setq-local completion-at-point-functions nil))
          (lsp)))
      

キャッシュの徹底活用

  • TRAMP経由の呼び出しは高コストのため キャッシュ利用が効果的
  • project-current, magit-toplevel, vc-git-root, counsel-git-candsなどを memoize
    • 例:
      (defvar project-current-cache nil)
      (defun memoize-project-current (orig &optional prompt directory)
        (memoize-remote (or directory project-current-directory-override default-directory)
                        'project-current-cache orig prompt directory))
      (advice-add 'project-current :around #'memoize-project-current)
      
  • キャッシュリセットは変数をnilに

今後の展望

  • 現状の工夫でTRAMPは十分実用的に
  • さらなる根本的な高速化には TRAMP本体の改良 が必要
  • 今後の開発・議論への参加を呼びかけ

Hackerたちの意見

なんかもう使うの諦めちゃった。リモコンがジャンプホストを通ってるのが多くて、シームレスに接続できなかったんだ。繋がるはずなんだけど、vscodeがssh設定で普通に動くから、デバッグするのは時間の無駄だなって思った。Mac使ってるからかもしれないけど、よくわからん。

https://www.gnu.org/software/tramp/tramp-emacs.html#Multi_00... がかなりうまく動いてると思ったけど、正しい設定を見つけるのにはちょっといじったり実験したりが必要だった。

(customize-set-variable 'tramp-use-connection-share nil) を設定したら、.ssh設定で「そのまま動く」ようになったよ(その変数のドキュメントには、これが期待されてるみたいに書いてある)。もし接続共有がまだ必要なら、.ssh設定で設定しなきゃいけないけど、なくても動くよ。

一般的に言えば、SSHで何か特別なことをするなら、Trampを使うよりSSHを設定した方がいいと思う。https://wiki.gentoo.org/wiki/SSH_jump_hostみたいな感じ。基本的には、「ssh someSSHConfig」と入力してデバイスのシェルにアクセスできるのが目標だよ。それができれば、Trampが何をできるか気にする必要もないし、SSHがSSH設定で動く機能を追加しても、Trampが使えるかどうか考える必要もない。しかも、SSHで動く他のすべてのものとも連携できるしね。

~/ssh:you_but_jumping@bastion|ssh:you@remote:path/to/file~を使うと、バスティオンホストを透過的にジャンプできるよ™。 (https://stackoverflow.com/a/16408592) 俺がいじったのは、sshとtrampが同じControlPathを使ってソケットを共有するようにすることだった。これをやると、ssh-agentを使って接続を持続させてる場合、スムーズに動くんだよね。でも、まあ、trampを諦める方が楽だったのも分かる。接続するだけじゃなくて、途切れがちなインターネットのイライラもあって、避ける方が大体はいい体験になるから。trampを使おうとした後はあんまり頼らなくなったな。tmux+emacsの方がその時は全体的に良かったから、RTOが起きたときにちょうど使ってたセッションにワークステーションからすぐに飛び込めたし。最近は主にsudoとか一時的なことに使ってるけど、リモートプロジェクトでリモートコンパイルやlspにはもう使ってない。

それはすごいね!シェアしてくれてありがとう。たまにtramp使うけど、いくつかは自分で発見しなきゃいけなかった。 > 「もっといい方法があるはずだ」とずっと考えてた。TRAMPのパフォーマンスを根本的に改善する方法を考え始めたんだけど、パッケージ自体に変更が必要そう。近いうちにそのことについてもっと書くつもりだから、楽しみにしてて!TRAMPは本当に素晴らしいし、もっと速くなるべきだと思う。設定をちょっと現代化するか、キャッシュや同期ロジックを改善すればいい感じになると思う。

うん、全部書いてくれてありがとう。俺はTRAMPをめっちゃ使ってるから、パフォーマンス向上は大歓迎だよ。

最近はtrampが役立つワークフローにいないな。使ってた頃はほぼ魔法みたいだったのを覚えてる。ネットワーク接続の良さにかなり依存してるんじゃないかな?今は記憶よりも設定が多い気がする。ここでいろんな設定をベンチマークしてくれてありがとう。もっとシームレスにするためにどんなことが試されるのか、すごく興味あるよ。

必要な設定はほとんどないよ。以前はTRAMPを結構使ってたけど、何も設定しなかったし。もちろん、他のemacsパッケージと同じように、好きなように設定や調整はできるよ。

ちょっと考えたら悲しくなったんだけど、少なくともここ数年はSSHでマシンに入ってないな。すべてウェブフロントエンド、git、CI/CD、Terraformとかで動いてる。サーバー上のファイルを実際に編集する代わりに、すべてを無形の一時的なコンピュートとして扱うのは、実際には本当に罪悪感を感じる楽しみだね!

https://www.gnu.org/software/tramp/#Overview trampについて詳しく知りたい人は、(私みたいに)何も知らなかった場合のために。

TRAMPは面白いけど、watchexec+rsyncの方がずっとパフォーマンスがいいと思う。これだとローカルでファイルを編集して、変更があったらリモートホストに同期されるだけなんだ。このワークフローは、ローカルのツールを全部使えるっていう利点もあるし、よく必要なローカルコピーも保持できるし、どんなエディタでも使える(ごめんね、父よ、rms)、簡単に設定できる(ファイルの含める・除外する、リモートのファイルを削除する、など)。

リモートマシンで頻繁に作業してたとき、俺も同じことやってたよ。内部の片道同期ツールかUnisonを使ってたけど、基本的には君が言ってることと同じだと思う。 (Watchexecで変更を検知して、ローカルのrsyncをリモートマシンに実行するってことだよね?) TRAMPはいじる価値があまりない気がする、特にそのワークフローがCLIで動くツールもサポートしてるときはね。ローカルでフォーマッターや他の自動化を実行して、変更を反映させる? git pullをローカルで、同じく?なんでやらないの?

いいね、でも残念ながらWindowsで作業しながらリモート編集もたくさんしなきゃいけないから、trampを使うためにwslでemacsに切り替えたんだ。watchexecのことは知らなかったけど、こっちの方がずっといい解決策みたいだね。ただ、trampの良さの一部は、emacsをローカルのように使えることだから、agやmagitとかはスムーズに動かないかも。

でも、そうすると他のことができなくなるよね。リモートLSPとか、gdbとか、いろいろ。

watchexecは、最近探してたものそのものだ!ありがとう。

lsyncdも別の選択肢かもしれないね。 https://github.com/axkibe/lsyncd

trampは最高だね。他の解決策は「俺がやりたいことを、気を散らさずにやる」には全然及ばない。vscode?「信じてくれ、サーバーでネットワークデーモンを実行するから」。リモートでどのプラグインを再インストールするか考えるのを楽しんでね。gitを使うために、プロプライエタリなシェアウェアやテレメトリプラグインをインストールするのも楽しんで。ローカルファイルとリモートファイルを同じウィンドウで並べて開くのを試してみて。Wi-Fi接続が一瞬切れた?ああ、ブラウザウィンドウ全体をリフレッシュしなきゃいけないよ。あまり接続しないホストのファイルを一つだけ編集したい?自動同期ソリューションを設定するのに10分かかるのを楽しんで。上記のどれかで、ああ、そのファイルに/etcでsudoが必要?じゃあ、シェルに落ちてvimで編集してね。特定の事前定義されたワークフローには他の選択肢もあるけど、trampの柔軟性はやっぱり無敵だよ、特にemacsを使うならね。俺が問題に直面したのは、リモートに変なシェル設定があるときだけだよ。その場合は/sshx:を使うといい。

SFTPでリモートファイルシステムをマウントするのはどう?リモートにEMACSか、好きなエディタをインストールするのは?

俺は一日中trampを使ってるから、リモートVMで作業することが多くて、欠かせないツールなんだ。trampを使えば、そのリモートホストのdockerコンテナにも入れるよ:/ssh:|docker: それに、dired経由で(大きいかもしれない)ファイルをコピーするのが好きなら、一時的に(setq tramp-default-method "rsync")を考えてみて。ブログではrsyncとsshについて少し触れてるよ。

rsyncがリモートシェルを壊すってブログ記事の意味が分からないんだけど。著者が言いたいこと知ってる?rsyncを使うべき時とscpを使うべき時はいつ?

それに、持続的なSSH接続を使うと、すごく効果があるよ。特に、ローカルやリモートのシェル初期化が重いときはね。

Host *
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlMaster auto
  ControlPersist 600

最近のトランプのバージョンはデフォルトでこれを使ってるんじゃないの?

TRAMPはSSH設定のその設定を上書きすると思ってたんだけど。

TRAMPは可能であればデフォルトでControlMaster=autoのOpenSSHオプションを使います。ただし、sshセッションを開始する際にControlPathの設定を上書きします。TRAMPは、Emacsセッションの外で開かれたマスターセッションがもう開いていない場合にスタールしないようにするためにこれを行います。だから、すでにsshが開いていてもTRAMPは再度パスワードを求めるんです。

いい最適化だけど、普通の動作と100%互換性がないから注意が必要だよ。マスター接続(最初に確立するやつ)は、他の接続がすべて閉じるまで閉じることができないからね。すべてのソフトウェアがこれを正しく処理できるわけじゃない。

トランプのパフォーマンスをデバッグしてるときに気づいたんだけど、遅延の半分はトランプのせいじゃなくて、elispのコードパスが安いと思って同期操作を呼び出してるからなんだ。バッファリストの切り替えのたびにvc-git-rootが発火してて、あるモードがバッジを更新したりモデルラインをリフレッシュしたりしたいからなんだけど、どれもパスがリモートだってことを意識してないんだよね。

まさにその通り。TRAMP自体は問題ないけど、ローカルマシンでしかテストされてないパッケージが、実際には必要ないシェルコールをたくさん使ってるんだよね(つまり、結果をキャッシュできるのに)。

Emacsでは、モデルラインを無効にするとパフォーマンスが向上することが多いよ。頻繁に更新されるけどキャッシュされないからね。ちゃんとしたモードラインの項目は、通常、定期的に変数を更新するからブロッキングは起こらないんだ。

非同期プロセスは期待できそうだけど、残念ながらeglotはそれと一緒に動かないんだ。redditから、tramp 2.8.0-preがその問題を修正したってメッセージをもらったけど、前回trampリポジトリからHEADをコンパイルしたときにはまだ動かなかった。記事から他の設定を適用したら、デフォルト設定より速くなったみたい。共有してくれた著者に感謝!