最近、私はいくつかのSOアーカイブを読んでおり、x86アーキテクチャに対するステートメントに遭遇しました。
サーバーとミニ/メインフレームと混合コアに異なるCPUアーキテクチャが必要な理由
"PCアーキテクチャは混乱しているので、OS開発者なら誰でも教えてくれるでしょう。"
アセンブリ言語の学習は努力する価値がありますか? ( アーカイブ済み )と言う
"x86アーキテクチャがせいぜい恐ろしいことを理解する"
x86アセンブラーを学ぶ簡単な方法は?
"ほとんどの大学は、MIPSのようなものでアセンブリを教えています。理解がはるかに簡単であるため、x86アセンブリは本当にugいです"
などのコメント
検索してみましたが、理由は見つかりませんでした。 x86が悪いとは思わないが、これは私がよく知っている唯一のアーキテクチャだからだ。
他の人と比べてx86がugい/悪い/劣っていると考える理由を誰かが親切に教えてもらえますか。
考えられるいくつかの理由:
IN
やOUT
)のようなI/O命令がありますx86は多くの機能を備えた複雑なアーキテクチャであるため、x86アセンブリコードは複雑です。一般的なMIPSマシンの指示リストは、1文字サイズの紙に収まります。 x86の同等のリストは複数のページにまたがり、指示はさらに多くのことを行うため、リストが提供できるものよりも多くの場合、それらが何をするかについてのより大きな説明が必要です。たとえば、 MOVSB
命令 は、それが何をするのかを説明するために比較的大きなCコードブロックを必要とします。
if (DF==0)
*(byte*)DI++ = *(byte*)SI++;
else
*(byte*)DI-- = *(byte*)SI--;
これは、ロード、ストア、および2つの加算または減算(フラグ入力によって制御)を実行する単一の命令であり、それぞれがRISCマシン上の個別の命令になります。
MIPS(および同様のアーキテクチャ)のシンプルさは必ずしも優れているわけではありませんが、アセンブラクラスの概要を教えるには、よりシンプルな [〜#〜] isa [〜#〜] から始めるのが理にかなっています。一部のアセンブリクラスは、 y86 と呼ばれるx86の非常に簡略化されたサブセットを教えます。
編集:これはbash the x86 !パーティーではないはずです。私は選択の余地がほとんどありませんでしたが、質問の言葉通りにある程度のバッシングをしました。しかし、(1)を除いて、これらすべては正当な理由で行われました(コメントを参照)。インテルのデザイナーは愚かではありません。彼らは自分のアーキテクチャで何かを達成したかったのです。これらは、それらを実現するために支払わなければならなかった税金の一部です。
私の考えでは、x86に対する主なノックはそのCISCの起源です。命令セットには、多くの暗黙的な相互依存関係が含まれています。これらの相互依存性により、チップ上で命令の並べ替えなどを行うことが難しくなります。これは、これらの相互依存性のアーティファクトとセマンティクスを各命令で保持する必要があるためです。
たとえば、ほとんどのx86整数加算および減算命令は、フラグレジスタを変更します。加算または減算を実行した後、次の操作は多くの場合、フラグレジスタを調べてオーバーフロー、符号ビットなどをチェックします。その後に別の加算がある場合、2番目の加算の実行を開始しても安全かどうかを判断するのは非常に困難です最初の追加の結果がわかる前。
RISCアーキテクチャでは、add命令は入力オペランドと出力レジスタを指定し、操作に関するすべてはそれらのレジスタのみを使用して行われます。これにより、すべてを一列に並べて単一のファイルを実行するブルーミンフラグレジスタがないため、互いに近い追加操作の分離がはるかに容易になります。
MIPSスタイルのRISC設計であるDEC Alpha AXPチップは、使用可能な命令では非常に質素ですが、命令セットは命令間の暗黙的なレジスタ依存を回避するように設計されました。ハードウェア定義のスタックレジスタはありませんでした。ハードウェア定義のフラグレジスタはありませんでした。命令ポインタさえもOSで定義されていました-呼び出し元に戻りたい場合、呼び出し元がどのアドレスに戻るかをどのように知らせるかを考えなければなりませんでした。これは通常、OS呼び出し規約によって定義されていました。ただし、x86では、チップハードウェアによって定義されます。
とにかく、3世代または4世代以上のAlpha AXPチップ設計では、ハードウェアは、32個のintレジスタと32個のfloatレジスタを含むスパルタン命令セットのリテラル実装から、80個の内部レジスタ、レジスタ名の結果の転送(前の命令の結果が値に依存する後の命令に転送される)およびあらゆる種類のワイルドでクレイジーなパフォーマンスブースター。そして、これらすべての機能を備えたAXPチップダイは、当時の同等のPentiumチップダイよりもかなり小さく、AXPは非常に高速でした。
X86命令セットの複雑さにより、多くの種類の実行最適化が不可能ではないにしても法外に高価になるため、x86ファミリツリーでパフォーマンスを向上させるようなバーストはほとんど見られません。 Intelの天才は、x86命令セットをハードウェアに実装することをあきらめることでした-最新のx86チップはすべて、実際には、ある程度x86命令を解釈するRISCコアであり、元のx86のすべてのセマンティクスを保持する内部マイクロコードに変換しますただし、RISCの順序が乱れたり、マイクロコードを介したその他の最適化が可能になります。
多くのx86アセンブラーを作成しましたが、そのCISCルートの便利さを十分に理解できます。しかし、Alpha AXPアセンブラーの作成に時間を費やすまで、x86がどれほど複雑かを十分に理解していませんでした。 AXPのシンプルさと統一性に悩まされました。その違いは非常に大きく、深刻です。
X86アーキテクチャは、8008マイクロプロセッサとその親sの設計に基づいています。これらのCPUは、メモリが遅いときに設計されたもので、CPUダイで実行できる場合は、多くの場合lotより高速でした。ただし、CPUダイスペースも高価でした。これら2つの理由は、特別な目的を持つ傾向があるレジスタが少数であり、あらゆる種類の落とし穴や制限がある複雑な命令セットがある理由です。
同じ時代の他のプロセッサー(6502ファミリーなど)にも同様の制限と癖があります。興味深いことに、8008シリーズと6502シリーズの両方が組み込みコントローラーとして意図されていました。当時でも、組み込みコントローラーはアセンブラーでプログラミングされ、多くの点でコンパイラライターではなくアセンブリプログラマーに提供されることが期待されていました。 (VAXチップを見て、コンパイラの記述に対応したときに何が起こるかを見てください。)設計者は、それらが汎用コンピューティングプラットフォームになることを期待していませんでした。それがPOWERアーキテクチャの前身のようなものが目的だったのです。もちろん、ホームコンピューターの革命はそれを変えました。
ここにいくつかの追加の側面があります:
操作「a = b/c」を考えてください。x86はこれを次のように実装します。
mov eax,b
xor edx,edx
div dword ptr c
mov a,eax
Div命令の追加ボーナスとして、edxには残りが含まれます。
RISCプロセッサでは、最初にbとcのアドレスをロードし、bとcをメモリからレジスタにロードし、除算を行い、aのアドレスをロードしてから結果を保存する必要があります。 Dst、src構文:
mov r5,addr b
mov r5,[r5]
mov r6,addr c
mov r6,[r6]
div r7,r5,r6
mov r5,addr a
mov [r5],r7
ここでは通常、残りはありません。
ポインターを介して変数をロードする場合、両方のシーケンスが長くなる可能性がありますが、RISCには別のレジスタに既に1つ以上のポインターがロードされている可能性があるため、RISCの可能性は低くなります。 x86のレジスタは少ないため、ポインタがそれらのいずれかにある可能性は小さくなります。
長所と短所:
RISC命令は、命令スケジューリングを改善するために周囲のコードと混合することができますが、これは代わりにCPU自体の内部でこの作業を行う(シーケンスに応じて多かれ少なかれ)x86では可能性が低くなります。上記のRISCシーケンスは、32ビットアーキテクチャでは通常28バイト長(各32ビット/ 4バイト幅の7つの命令)です。これにより、命令をフェッチするとき(7回のフェッチ)にオフチップメモリがより機能します。より高密度のx86シーケンスには含まれる命令が少なく、幅は異なりますが、おそらく平均で4バイト/命令が表示されています。これを7回高速化する命令キャッシュがある場合でも、x86と比較して、他の3つの赤字を補うことができます。
保存/復元するレジスタが少ないx86アーキテクチャは、おそらくスレッド切り替えを行い、RISCよりも高速に割り込みを処理することを意味します。より多くのレジスタを保存および復元するには、割り込みを行うための一時的なRAMスタックスペースとスレッド状態を格納するためのより永続的なスタックスペースが必要です。
より個人的なメモでは、RISC Assemblyを書くのはx86より難しいと思います。これを解決するには、RISCルーチンをCで記述し、生成されたコードをコンパイルおよび変更します。これは、コード生成の観点からはより効率的であり、実行の観点からはおそらく非効率的です。追跡するこれらの32個すべてのレジスタ。 x86では、これとは逆の方法です。「実際の」名前で6〜8個のレジスタを使用すると、問題をより管理しやすくなり、生成されたコードが期待どおりに動作するという自信がつきます。
醜い?それは見る人の目です。 「違う」のが好きです。
この質問には誤った仮定があると思います。 x86をcallく呼ぶのは、主にRISCに取りessかれた学者だけです。実際には、x86 ISAは、RISC ISAで5〜6命令を実行する単一命令操作で実行できます。RISCファンは、現代のx86 CPUがこれらの「複雑な」命令をマイクロオペレーション; しかしながら:
mov %eax, 0x1c(%esp,%edi,4)
のようなもの、つまりアドレス指定モードであり、これらは分解されません。x86は10〜15年前にRISCのすべての優れた側面を吸収し、RISCの残りの品質(実際にはdefiningone-最小命令セット)は有害で望ましくありません。
製造CPUのコストと複雑さ、およびそれらのエネルギー要件は別として、x86は最高のISAです。そうでなければ、イデオロギーや議題に彼らの推論の邪魔をさせます。
一方、CPUのコストが重要な組み込みデバイス、またはエネルギー消費が最重要事項である組み込み/モバイルデバイスをターゲットにしている場合、ARMまたはMIPSがおそらくより理にかなっています。ただし、簡単に3〜4倍のサイズのコードを処理するために必要な追加のRAMとバイナリサイズに対処する必要があり、パフォーマンスに近づくことはできません。あなたはそれで走ります。
x86アセンブラー言語はそれほど悪くはありません。マシンコードに到達すると、非常にいものになります。命令のエンコード、アドレス指定モードなどは、ほとんどのRISC CPUのものよりもはるかに複雑です。また、下位互換性のために、プロセッサが特定の状態になったときにのみ作動するものが組み込まれています。
たとえば、16ビットモードでは、アドレス指定はまったく奇妙に思えます。 [BX+SI]
にはアドレッシングモードがありますが、[AX+BX]
にはアドレッシングモードがありません。そのようなことは、必要に応じて使用できるレジスタに値を確保する必要があるため、レジスタの使用を複雑にする傾向があります。
(幸いなことに、32ビットモードは非常に賢明です(セグメンテーションなどのように、それ自体は少し奇妙ですが、16ビットx86コードは、ブートローダーや一部の組み込み環境以外ではほとんど無関係です)。)
Intelがx86を究極のプロセッサにしようとしていた昔からの残り物もあります。誰も実際にそれ以上実行しないタスクを実行する数バイトの長さの命令は、率直に言って、非常に遅く、または複雑すぎます。 ENTERおよび LOOP命令 、2つの例では、Cスタックフレームコードは「Push ebp; mov ebp、esp」に似ており、ほとんどのコンパイラでは「enter」ではないことに注意してください。
私は専門家ではありませんが、人々がそれを好まない理由の多くは、それがうまく機能する理由であるようです。数年前、レジスタ(スタックの代わりに)、レジスタフレームなどを持つことは、アーキテクチャを人間にとって単純に見えるようにするニースのソリューションと見なされていました。ただし、今日では、重要なのはキャッシュのパフォーマンスであり、x86の可変長ワードにより、より多くの命令をキャッシュに格納できます。相手が指摘した「命令の解読」は、チップの半分を占めていたと思うが、もうそれほど多くはない。
並列処理は、今日では最も重要な要素の1つだと思います。少なくとも、使用可能なほど高速に実行されるアルゴリズムにとってはそうです。ソフトウェアで高度な並列性を表現することで、ハードウェアはメモリレイテンシを償却(または完全に隠蔽)できます。もちろん、アーキテクチャの未来がさらに広がるのは、おそらく量子コンピューティングのようなものです。
NVidiaから、Intelの間違いの1つは、バイナリ形式をハードウェアの近くに保持していたことだと聞いたことがあります。 CUDAのPTXはレジスタの高速計算(グラフの色付け)を行うため、nVidiaはスタックマシンの代わりにレジスタマシンを使用できますが、古いソフトウェアをすべて壊さないアップグレードパスを使用できます。
X86をターゲットとするコンパイラを作成しようとする場合、x86マシンエミュレータを作成する場合、またはISAハードウェア設計で。
「x86は見苦しい!」引数、私はまだそれがより多くだと思う楽しい MIPSよりx86アセンブリを書く(例えば)-後者はただ退屈だ。それは常に人間よりもコンパイラーにとってナイスであることを意味していました。チップがコンパイラー作成者にとってより敵対的である可能性があるかどうかはわかりません...
私にとって最もgliい部分は、(リアルモード)セグメンテーションの動作方法です。物理アドレスには、4096個のsegment:offsetエイリアスがあります。最後にneed that?セグメント部分が32ビットアドレスの厳密に高位のビットであった場合、物事は非常に簡単になります。
人々がすでに言及した理由に加えて:
__cdecl
、__stdcall
、__fastcall
など.x86には、非常に限られた汎用レジスタのセットがあります
効率的なロード/ストアの方法論ではなく、最低レベル(CISC hell)での非常に非効率的な開発スタイルを促進します。
Intelは、明らかに時代遅れのテクノロジーと互換性を保つために、明らかに愚かなセグメント/オフセット-メモリアドレスモデルを導入するという恐ろしい決断をしました。
誰もが32ビットになったとき、x86はメインストリームPCの世界をわずかな16ビット(そのほとんどは8088-8ビットの外部データパスのみでさえも、それはさらに恐ろしいことです!)
私にとって(そして、私は開発者の観点からあらゆる世代のPCを見てきたDOSベテランです!)ポイント3は最悪でした。
90年代前半(主流!)にあった次の状況を想像してください。
a)レガシーの理由で非常に制限のあるオペレーティングシステム(640kBの簡単にアクセス可能なRAM)-DOS
b)RAMの面でより多くのことができるオペレーティングシステム拡張(Windows)。ただし、ゲームなどのことになると制限され、地球上で最も安定したものではありませんでした(幸いなことに、これは後で変更されましたが、ここで90年代前半について話しています)
c)ほとんどのソフトウェアはまだDOSであり、特別なソフトウェア用にブートディスクを頻繁に作成する必要がありました。これは、一部のプログラムが好きで、他のプログラムが嫌いなEMM386.exeがあったためですここで話している)
d)MCGA 320x200x8ビットに制限されていました(OK、特別なトリックでもう少しありました、360x480x8は可能ですが、ランタイムライブラリのサポートなしでのみ)、他のすべては面倒で恐ろしいものでした(「VESA」-笑)
e)しかし、ハードウェアの面では、RAMと1024x768までをサポートするVGAカードのかなりの数メガバイトの32ビットマシンがありました
この悪い状況の理由は?
Intelによる単純な設計決定。マシン命令レベル(バイナリレベルではありません!)はすでに死にかけているものと互換性があり、8085だったと思います。他の、一見無関係な問題(グラフィックモードなど)は、技術的な理由と非常に狭いために関連していましたx86プラットフォームがもたらす心構えのアーキテクチャ。
今日、状況は異なりますが、アセンブラー開発者またはx86のコンパイラーバックエンドを構築する人々に尋ねてください。汎用レジスターの数がめちゃくちゃ少ないのは、恐ろしいパフォーマンスキラーに他なりません。