この記事を書いた理由は、修正した分散 PyTorch プログラムを Facebook のクラスター上でより速く起動させたいからです。探索プロセスは興味深いものであり、産業用機械学習に必要な知識システムも実証されました。 私は2007年に卒業してすぐに3年間Googleで働きました。当時、分散オペレーティングシステムBorgは本当に使いやすいと思いました。 2010 年に Google を退職して以来、Kubernetes が登場するまでは、同社のオープンソース開発を心待ちにしていました。 Kubernetes がスケジュールする計算単位はコンテナです (正確な翻訳は「コンテナ」であり、一般的な「コンテナ」ではありません。Docker 社のロゴに何が描かれているかを見ると、作者の意図がわかります)。 コンテナは、プロセスがプログラムを実行するのと同じようにイメージを実行します。 残念ながら、Google 社員も元 Google 社員も、Borg を使用する際にコンテナとイメージの概念に遭遇したことはありません。なぜこれら 2 つの概念は Borg では利用できず、Kubernetes では導入されているのでしょうか? この疑問が頭に浮かんだが、無視した。結局、その後、Baidu Paddle や Ant の SQLFlow や ElasticDL など、さらに多くのオープンソース プロジェクトを担当することになりましたが、Docker は非常に使いやすかったです。だから、あまり考えませんでした。 今年(2021年)初めに、私はFacebookに参加しました。偶然にも、Facebookは分散クラスタ管理システムTupperwareを紹介する論文[1]を発表しました。 しかし、タッパーウェアは1946年に登録されたブランドなので(https://en.wikipedia.org/wiki/Tupperware_Brands)、論文では別の名前であるTwineを使用する必要がありました。 業界内ではタッパーウェアという名前を知っている人が多いため、この記事ではTwineについては触れません。 つまり、この論文の発表をきっかけに、私は以前の問題を再検討するようになりました。Facebook には Docker がないのです。 Facebook Tuppware チームと Google Borg の新旧の同僚数人と詳細に話し合った後、ようやく理解できました。業界に関連するレビューがないため、この記事は記録用です。 一言で言えば簡単に言えば、モノリシック リポジトリを使用してコードを管理する場合、Docker イメージ (または ZIP、tarball、RPM、deb) などの「パッケージ」は必要ありません。 モノリシック リポジトリとは、企業のすべてのプロジェクトのすべてのコードが 1 つの (またはごく少数の) リポジトリに集中しているリポジトリです。 モノリシック リポジトリには、サポートする統合ビルド システムが必要です。そうしないと、このような大量のコードをコンパイルできなくなります。 統一されたビルド システムがあるため、クラスター ノードが実行する必要があるプログラムが依存するモジュールが変更されたことが判明すると、そのモジュールをこのノードに同期できます。パッケージ化して再同期する必要はまったくありません。 逆に、各プロジェクトが別々の git/svn リポジトリにあり、異なるビルド システムを使用している場合 (各オープン ソース プロジェクトが異なる GitHub リポジトリにあるなど)、各プロジェクトのビルド結果をパッケージ化する必要があります。 Docker イメージは階層化パッケージ形式をサポートしているため、変更された項目を含む最上位レイヤーのみを転送し、ノードによってキャッシュされている下位レイヤーを再利用する必要があります。 GoogleとFacebookはどちらもモノリシックなリポジトリを使用しており、独自のビルドシステムを持っています(私の以前の記事「Finding Google Blaze」[2]でGoogleのビルドシステムを説明しています)。そのため、「パッケージ」は必要なく、もちろんDockerイメージも必要ありません。 ただし、Borg と Tupperware の両方に、ジョブ間の分離を実現するためのコンテナ (10 年以上前に Google Borg チームによって Linux カーネルに提供された cgroup など、Linux カーネルによって提供されるいくつかのシステム コールを使用) があります。 ただ、Docker イメージをビルドする必要がなければ、コンテナの存在は簡単には気づかれないでしょう。 もしあなたが上記のことに惑わされずに、この問題を詳しく調べたいのであれば、GoogleとFacebookのR&D技術システムとコンピューティング技術システムを層ごとに剥がしていくのを待ってください。 パッケージ分散ジョブをクラスターに送信して実行する場合、実行するプログラム (*.so、*.py などの実行可能ファイルと関連ファイルを含む) を、スケジューリング システムによってこのジョブに割り当てられたいくつかのマシン (ノード) に転送する必要があります。 パッケージ化されるこれらのファイルはどこから来るのでしょうか?それはその時に建てられたものです。 Google には Blaze があり、Facebook には Buck があります。 興味のある方は、Google Blazeのオープンソース版であるBazel[3]やFacebook Buckのオープンソース版[4]をご覧ください。 ただし、注意点があります。Blaze と Facebook Buck の内部バージョンはモノリシック リポジトリに使用され、オープン ソース バージョンは誰でも非モノリシック リポジトリを使用できるようにするため、概念と実装に違いがありますが、基本的な使用方法の感覚をつかむことはできます。 Buck または Bazel 構文で記述された次のモジュール依存関係があるとします (構文はほぼ同じです)。 python_binary(name="A", srcs=["A.py"], deps=["B", "C"], ...) python_library(名前="B", srcs=["B.py"], deps=["D"], ...) python_library(名前="C", srcs=["C.py"], deps=["E"], ...) cxx_library(名前="D", srcs=["D.cxx", "D.hpp"], 依存関係="F", ...) cxx_library(名前="E", srcs=["E.cxx", "E.hpp"], 依存関係="F", ...) モジュール (ビルド結果) の依存関係は次のようになります。 A.py --> B.py --> D.so -\ \-> C.py --> E.so --> F.so オープンソース プロジェクトの場合は、想像力を働かせて、上記のモジュールを GPT-3、PyTorch、cuDNN、libc++ などのプロジェクトに置き換えてください。 もちろん、各プロジェクトには複数のモジュールが含まれ、各モジュールに複数のサブモジュールがあるのと同様に、他のプロジェクトに依存します。 ターボールパッケージ化する最も簡単な方法は、上記のファイル {A,B,C}.py、{D,E,F}.so をファイル A.zip または A.tar.gz にパッケージ化することです。 より正確には、ファイル名にバージョン番号を含める必要があります。たとえば、A-953bc.zip の場合、バージョン番号 953bc は git/Mercurial コミット ID です。 バージョン番号を導入すると、ファイルをノード上でローカルにキャッシュできるため、次回同じ tarball を実行するときにファイルをダウンロードする必要がなくなります。 ここでパッケージ キャッシュの概念を紹介したことに注意してください。 Docker に関する以下の説明を準備してください。 ユーロZIP または tarball ファイルをクラスター ノードにコピーした後、ローカル ファイル システム上のどこかに解凍する必要があります (例: /var/packages/A-953bc/{A,B,C}.py,{D,E,F}.so)。 もう少しクールな方法は、Tarball を使用せず、上記のファイルをオーバーレイ ファイルシステムのループバック デバイス イメージに配置することです。このように、「unzip」は「mount」になります。 ここでループバックデバイスイメージの概念を紹介したことに注意してください。 Docker に関する以下の説明を準備してください。 ループバックデバイスイメージとは何ですか? Unix では、ファイルのディレクトリ ツリーはファイルシステムと呼ばれます。 通常、ファイルシステムはブロックデバイスに保存されます。ブロックデバイスとは何ですか? 簡単に言えば、バイト配列として表示できるすべてのストレージ スペースがブロック デバイスです。 たとえば、ハードディスクはブロックデバイスです。新しく購入したハードディスクに空のディレクトリツリー構造を作成するプロセスをフォーマットと呼びます。 ブロックデバイスは単なるバイト配列なので、ファイルもバイト配列ではないでしょうか? はい! Unix の世界では、固定サイズの空のファイルを作成し (truncate コマンドを使用)、そのファイルを「フォーマット」してその中に空のファイル システムを作成できます。次に、上記のファイル {A,B,C}.py、{D,E,F}.so をその中に配置します。 例えば、FacebookはXARファイル[5]形式をオープンソース化しました。これは Buck で使用します。 buck build A を実行すると、A.xar が生成されます。このファイルには、ヘッダーと、squanshfs イメージと呼ばれる squashfs ループバック デバイス イメージが含まれています。 ここで、squashfs はオープン ソース ファイル システムです。興味のある方は、このチュートリアル[6]を参考にして、空のファイルを作成し、それをsquashfsにフォーマットしてから、ローカルファイルシステムのディレクトリ(マウントポイント)にマウントしてください。 アンマウントすると、マウント ポイントに追加されたファイルはこの「空のファイル」に残ります。 それをコピーして他の人に配布することができ、誰でもそれをマウントして追加したファイルを見ることができます。 XAR は squashfs イメージの前にヘッダーを追加するため、mount -t squashf コマンドを使用してマウントすることはできません。mount -t xar または xarexec -m コマンドを使用する必要があります。 たとえば、ノードに /packages/A-953bc.xar がある場合、次のコマンドを使用して、CPU リソースを消費せずに解凍してその内容を表示できます。 xarexec -m A-953bc.xar このコマンドは、XAR ファイルのマウント ポイントである一時ディレクトリを出力します。 レイヤーここで A.py を変更すると、tarball または XAR に組み込まれているかどうかに関係なく、パッケージ全体を更新する必要があります。 もちろん、ビルド システムがキャッシュをサポートしている限り、各 *.so ファイルを再生成する必要はありません。 しかし、これでは、.tar.gz ファイルと .xar ファイルをクラスター内の各ノードに再配布する必要があるという問題は解決されません。 A-953bc87fe.{tar.gz,xar} の古いバージョンがノード上に以前から存在している可能性がありますが、再利用することはできません。再利用するには、階層化が必要です。 上記の状況では、モジュール依存関係グラフに基づいて複数の XAR ファイルを作成できます。 A-953bc.xar --> B-953bc.xar --> D-953bc.xar -\ \-> C-953bc.xar --> E-953bc.xar --> F-953bc.xar 各 XAR ファイルには、対応するビルド ルールによって生成されたファイルのみが含まれます。たとえば、F-953bc.xar には F.so のみが含まれます。 このように、A.py のみを変更する場合は、A.xar のみを再構築してクラスター ノードに転送する必要があります。このノードは、以前にキャッシュされた {B,C,D,E,F}-953bc.xar ファイルを再利用できます。 ノードにすでに /packages/{A,B,C,D,E,F}-953bc.xar があると仮定すると、モジュールの依存関係の順序で xarexec -m コマンドを実行して、これらの XAR ファイルを同じマウント ポイント ディレクトリにマウントし、その中のすべてのコンテンツを取得することはできますか? 残念ながら、いいえ。次の xarexec/mount コマンドはエラーを報告します。マウント ポイントが前の xarexec/mount コマンドによって占有されているためです。 ファイルシステム イメージが tarball よりも優れている理由は次のとおりです。 では、一歩引いて考えてみましょう。XAR を使用する代わりに、ZIP または tar.gz を使用するのはいかがでしょうか?はい、でもゆっくりです。すべての .tar.gz ファイルを同じディレクトリに抽出できます。 しかし、A.py が更新されると、古い A.py を識別して新しいものに置き換えることができなくなります。代わりに、すべての .tar.gz ファイルを再解凍して新しいフォルダーを取得する必要があります。また、すべての {B,C,D,E,F}.tar.gz を再抽出すると時間がかかります。 オーバーレイファイルシステム オープンソース ツール fuse-overlayfs があります。複数のディレクトリを「オーバーレイ」できます。 たとえば、次のコマンドは、ディレクトリ /tmp/{A,B,C,D,E,F}-953bc の内容をディレクトリ /pacakges/A-953bc に「オーバーレイ」します。 fuse-overlayfs -o \ 下位ディレクトリ="/tmp/A-953bc:/tmp/B-953bc:..." \ /パッケージ/A-953bc ディレクトリ /tmp/{A,B,C,D,E,F}-953bc は、xarcexec -m /packages/{A,B,C,D,E,F}-953bc.xar から取得されます。 ここでオーバーレイ ファイルシステムの概念を紹介したことに注意してください。 Docker に関する以下の説明を準備してください。 fuse-overlayfs はこれをどうやって行うのでしょうか? /packages/A などのファイル システム ディレクトリにアクセスすると、使用するコマンド ライン ツール (ls など) がシステム コール (open/close/read/write など) を呼び出して、そのディレクトリ内のファイルにアクセスします。 これらのシステム コールはファイル システム ドライバーを処理し、ドライバーに「/packages/A ディレクトリに A.py というファイルはありますか?」と問い合わせます。 Linux を使用する場合、一般的にハードディスク上のファイルシステムは ext4 または btrfs です。つまり、Linux ユニバーサル ファイル システム ドライバーは各パーティションのファイル システムを調べ、システム コールを対応する ext4/btrfs ドライバーに転送して処理します。 一般的なファイルシステム ドライバーは、他のデバイス ドライバーと同様にカーネル モードで実行されます。 このため、ファイルシステムを操作する mount や umount などのコマンドを実行するときは、通常 sudo が必要になります。 FUSE は、ユーザーランドでファイルシステム ドライバーを開発するためのライブラリです。 fuse-overlayfs コマンドは、FUSE ライブラリを使用して、ユーザーランドで実行される fuse-overlayfs ドライバーを開発します。 ls コマンドが overlayfs ドライバーに /packages/A-953bc ディレクトリに何があるかを尋ねると、fuse-overlayfs ドライバーは、ユーザーが以前に fuse-overlayfs コマンドを実行して /tmp/{A,B,C,D,E}-953bc ディレクトリをオーバーレイしたことを記憶し、これらのディレクトリ内のファイルを返します。 このとき、ディレクトリ /tmp/{A,B,C,D,E}-953bc は実際には /packages/{A,B,C,D,E,F}-953bc.xar のマウント ポイントであるため、各 XAR はレイヤーに相当します。 fuse-overlayfs ドライバーのように、複数のディレクトリを「オーバーレイ」するファイルシステム ドライバーは、オーバーレイ ファイルシステム ドライバーと呼ばれ、オーバーレイ ファイルシステムと呼ばれることもあります。 Docker イメージとレイヤー 前述のように、階層化を実現するためにオーバーレイ ファイルシステムが使用されます。 Docker を使用したことがある人なら、Docker イメージが複数のレイヤーで構成されていることはご存知でしょう。 docker pull <image-name> コマンドを実行すると、ローカル マシンにこのイメージの一部のレイヤーがすでにキャッシュされている場合は、これらのレイヤーのダウンロードはスキップされます。これは実際にはオーバーレイ ファイルシステムを使用して実現されます。 Docker チームは、overlayfs と呼ばれるファイルシステム (ドライバー) を開発しました。これは特定のファイルシステムの名前です。 名前が示すように、Docker overlayfs は「オーバーレイ」機能も実装しているため、各 Docker イメージには複数のレイヤーを含めることができます。 Docker の overlayfs とその後継である overlayfs2 はどちらもカーネル モードで実行されます。 これが、Docker がマシン上でルート権限を必要とする理由の 1 つであり、Docker が簡単にセキュリティ上の抜け穴を引き起こすと批判される理由でもあります。 近年 Linux の世界で急速に発展し、ハードディスクの管理に非常に有効な btrfs というファイルシステムがあります。 このファイルシステム ドライバーはオーバーレイもサポートします。したがって、Docker は overlayfs の代わりにこのファイルシステムを使用するように構成することもできます。 ただし、Docker ユーザーのコンピューターのローカル ファイル システムが btrfs である場合にのみ、Docker は btrfs を使用してその上にレイヤーをオーバーレイできます。 したがって、macOS または Windows を使用している場合は、Docker で btrfs を使用することは絶対にできません。 しかし、fuse-overlayfs を使用している場合は、万能薬を使用していることになります。 FUSE を介してユーザーランドで実行されるファイルシステムのパフォーマンスは非常に平均的ですが、この記事で説明する状況ではそれほど高いパフォーマンスは必要ありません。 実際、Docker は fuse-overlayfs を使用するように構成することもできます。 Dockerでサポートされている階層型ファイルシステムのリストは、Dockerストレージドライバー[7]で入手できます。 Docker イメージが必要なのはなぜですか? 上記をまとめると、プログラミングからクラスター上での実行まで、いくつかの手順を実行する必要があります。
ソース コードをモジュールに分割すると、コンパイル手順で、各変更によってコードの小さな部分のみが変更され、変更されたモジュールのみが再コンパイルされるという事実を最大限に活用できるため、時間が節約されます。 2、3、4 の時間を節約するために、「パッケージ」を階層化する必要があります。各レイヤーには、1 つまたは少数のコード モジュールのみを含めることをお勧めします。このように、モジュール間の依存関係を活用して、基礎となるモジュールを含む「レイヤー」を可能な限り再利用することができます。 オープンソースの世界では、階層化された機能をサポートするために Docker イメージを使用します。ベース レイヤーには、ls、cat、grep など、特定の Linux ディストリビューション (CentOS など) のユーザーランド プログラムのみが含まれる場合があります。 その上に、CUDA を含むレイヤーが存在する場合があります。次に、Python と PyTorch をインストールします。その次のレイヤーは、GPT-3 モデルのトレーニング手順です。 このように、GPT-3 のトレーニング手順のみを変更する場合は、次の 3 つのレイヤーを再パッケージ化して転送する必要はありません。 ここでの論理の核心は、「プロジェクト」という概念があることです。各プロジェクトには、独自のリポジトリ、独自のビルド システム (GNU make、CMake、Buck、Bazel など)、独自のリリースを設定できます。 したがって、各プロジェクトのリリースは Docker イメージのレイヤーに配置されます。前のレイヤーと合わせてイメージと呼ばれます。 Google と Facebook が Docker を必要としない理由 上で述べた知識の準備がすべて整ったら、ようやく本題に入ります。 Google と Facebook はモノリシック リポジトリと統合ビルド システム (Google Blaze または Facebook Buck) を使用しているためです。 ただし、「プロジェクト」の概念を使用して、各プロジェクトのビルド結果を Docker イメージのレイヤーにロードすることもできます。しかし、実際には必要ありません。 Blaze および Buck ビルド ルールで定義されたモジュールとモジュール間の依存関係を使用することで、パッケージ化とアンパックの概念を完全に排除できます。 パッケージがなければ、zip、tarball、Docker イメージ、レイヤーは必要ありません。 各モジュールをレイヤーとして扱うだけです。 D.cpp を変更したために D.so が再コンパイルされた場合、D.so を含むレイヤーを送信する必要はなく、D.so を再送信するだけで済みます。 したがって、Google と Facebook はモノリシック リポジトリと統合ビルド ツールの恩恵を受けています。 上記の 4 つのステップを 2 つに短縮しました。
GoogleとFacebookはDockerを使用していない 前のセクションでは、モノリシック リポジトリを使用すると、Google と Facebook は Docker イメージを必要としないことを説明しました。 現実には、Google と Facebook は Docker を使用していません。これら 2 つの概念には違いがあります。 まず「使用されていない」としましょう。歴史的に、Google と Facebook は Docker と Kubernetes が登場する前はハイパースケール クラスターを使用していました。当時はパッケージ化の都合上、tarball すら存在しませんでした。 C/C++ プログラムの場合、完全な静的リンクを直接行い、*.so は一切使用しません。したがって、実行可能なバイナリ ファイルは「パッケージ」です。 今日でも、オープンソースの Bazel と Buck を使用すると、デフォルトのリンク方法が完全な静的リンクであることがわかります。 Java は「完全に動的にリンクされた」言語ですが、その誕生と進化はインターネットの歴史的な機会と一致しました。Java の開発者は、完全に静的なリンクをサポートする jar ファイル形式を発明しました。 Python 言語自体には jar パッケージがないため、Blaze と Bazel は Python 用の jar を設計することに相当する PAR ファイル形式 (英語では subpar と呼ばれます) を発明しました。オープンソースの実装はここから入手できます[8]。 同様に、Buck は XAR 形式を発明しました。これは、squashfs イメージの前にヘッダーを追加した、上で説明した形式です。オープンソース実装はここから入手できます[9]。 Go 言語は、デフォルトでは完全に静的にリンクされています。 Rob Pike の初期の概要のいくつかでは、完全な静的リンクを含む Go の設計は、基本的に Google の C/C++ の実践で遭遇したさまざまな落とし穴を回避するものであると述べられていました。 Google C++ スタイル ガイドに精通している友人は、Go 構文はガイドに記載されている「使用すべき C++ 構文」をカバーしているが、ガイドに記載されている「使用すべきでない C++ 部分」をサポートしていないと感じるはずです。 簡単に言えば、歴史的に Google と Facebook は Docker イメージを使用していません。重要な理由の 1 つは、それらのビルド システムがさまざまな一般的な言語のプログラムを完全に静的にリンクできるため、実行可能ファイルが「パッケージ」であることです。 しかし、これは最善の解決策ではありません。結局のところ、階層化が存在しないからです。メイン関数のコード行を 1 行変更しただけでも、再コンパイルして公開するには 10 分、場合によっては数十分と長い時間がかかります。完全な静的リンクによって取得された実行可能ファイルのサイズは、多くの場合、GB 単位になることを知っておく必要があります。 したがって、完全な静的リンクは Google と Facebook が Docker を使用していない理由の 1 つですが、これは良い選択ではありません。 そのため、他社は追随しませんでした。階層型キャッシュをサポートする Docker イメージの使用は依然として好まれています。 完璧なソリューションの技術的課題 完璧なソリューションは、階層型キャッシュ (より正確には、ブロック キャッシュ) をサポートすることです。したがって、上で紹介したモノリシック リポジトリと統合ビルド システムの機能を引き続き使用する必要があります。 しかし、ここで技術的な課題があります。ビルド システムはモジュールで記述されており、モジュールは通常「プロジェクト」よりもはるかに細かい粒度になっています。 C/C++ 言語を例にとると、各モジュールがキャッシュ ユニットとして機能する「レイヤー」または「ブロック」として .so ファイルを生成する場合、アプリケーションに必要な .so ファイルの数が多すぎます。 アプリケーションを起動すると、シンボルを解決してリンクを完了するまでに数十分かかる場合があります。 したがって、モノリシック リポジトリには多くの利点がありますが、欠点もあります。オープン ソースの世界とは異なり、誰もが手動でコードを「プロジェクト」に分割します。 各プロジェクトは通常、多くのモジュールを持つことができる GitHub リポジトリですが、各プロジェクト内のすべてのモジュールはキャッシュ ユニットとして *.so に組み込まれます。 アプリケーションが依存するプロジェクトの数は決して多くならないため、レイヤーの合計数を制御できます。 幸いなことに、この問題は解決不可能なものではありません。アプリケーションのさまざまなモジュールへの依存関係は DAG であるため、グラフ分割を実行して、この DAG をより少ない数の複数のサブグラフに分解する方法が常に見つかります。 引き続き C/C++ プログラムを例にとると、各サブグラフ内の各モジュールを .a にコンパイルし、各サブグラフ内のすべての .a をキャッシュ ユニットとして *.so にリンクできます。 したがって、グラフ分割アルゴリズムをどのように設計するかが最も重要な問題になります。 関連リンク: https://engineering.fb.com/2019/06/06/data-center-engineering/twine/ https://zhuanlan.zhihu.com/p/55452964 https://bazel.build/ https://buck.build/ https://github.com/facebookincubator/xar https://tldp.org/HOWTO/SquashFS-HOWTO/creatingandusing.html ストレージドライバーの選択 https://github.com/google/subpar https://github.com/facebookincubator/xar Google と Facebook が Docker を使用しないことの原理分析に関するこの記事はこれで終わりです。Google と Facebook が Docker を使用しないことに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
書き換えモジュールは ngx_http_rewrite_module モジュールです。その主な機能は...
序文最近、私は夜に時間を取って「CSS World」という本を読んでいます。この本は非常に興味深く、...
編集者:この記事では、インタラクティブデザインがブランドコミュニケーションチェーン全体で果たすべき役...
今日、研究室のプロジェクトを見ていたとき、私にとって「難しい」問題に遭遇しました。実は、それは私があ...
この記事では、例を使用して、MySQL 派生テーブルの簡単な使用方法を説明します。ご参考までに、詳細...
まず、VMware 14のアクティベーションコードをお渡ししますFF31K-AHZD1-H8ETZ-...
1. ダウンロード2. 減圧3. パス環境変数を追加し、mysqlが配置されているbinディレクトリ...
MySQL の explain コマンドは SQL のパフォーマンスを分析できます。その 1 つが ...
前回の記事では、Docker を使用して、コンパイルされた jar パッケージをイメージに組み込む ...
この記事の例では、カスケードセレクターを実装するためのelementUIの具体的なコードを参考までに...
職業的な観点からも、人生の観点からも、良い再建をすることは本当に簡単ではありません。楽観的で熱心で前...
この記事の例では、カウントダウンプロンプトボックスを実装するためのJavaScriptの具体的なコー...
位置が絶対の場合、関連する属性のパーセンテージは、参照先の要素 (包含ブロック) を基準として計算さ...
概要リレーショナル データベースでは、インデックスは、データベース テーブル内の 1 つ以上の列の値...
1 問題の説明: 1.1 Windows 10 に VMware を初めてインストールする場合、また...