17

s0128x86-64 アセンブリ言語でコード ライブラリを作成して、 、s0256s0512s1024符号付き整数型およびf0128f0256f0512f1024浮動小数点型のすべての従来のビット単位、シフト、論理、比較、算術、および数学関数を提供しています。浮動小数点関数は、整数型用に作成された内部ルーチンを呼び出す可能性が高いため、ここまでは符号付き整数型に取り組んでいます。

これまで、さまざまな単項演算子、比較演算子、および加算、減算、乗算の各演算子を実行する関数を作成してテストしてきました。

現在、除算演算子の関数を実装する方法を決定しようとしています。

私の最初の考えは、「ニュートン・ラフソンが最善のアプローチに違いない」というものでした。なんで?適切なシード (最初の推測) が与えられると非常に迅速に収束するため、オペランドでネイティブの 64 ビット除算命令を実行して優れたシード値を取得する方法を理解できるはずです。実際、シード値が 64 ビットまで正確である場合、正しい答えを得るには次のようにする必要があります。

`s0128` : 1~2 iterations : (or 1 iteration  plus 1~2 "test subtracts")
`s0256` : 2~3 iterations : (or 2 iterations plus 1~2 "test subtracts")
`s0512` : 3~4 iterations : (or 3 iterations plus 1~2 "test subtracts")
`s1024` : 4~5 iterations : (or 4 iterations plus 1~2 "test subtracts")

しかし、この質問についてもう少し考えてみると、私は疑問に思います。たとえば、すべての大きな整数型に対して乗算演算を実行する、私が書いたコア ルーチンを思い出します。

s0128 :   4 iterations ==   4 (128-bit = 64-bit * 64-bit) multiplies +  12 adds
s0256 :  16 iterations ==  16 (128-bit = 64-bit * 64-bit) multiplies +  48 adds
s0512 :  64 iterations ==  64 (128-bit = 64-bit * 64-bit) multiplies + 192 adds
s1024 : 256 iterations == 256 (128-bit = 64-bit * 64-bit) multiplies + 768 adds

ループがかなり短く効率的 (キャッシュを含む) であるにもかかわらず、より広いデータ型の操作の増加はかなりのものです。このループは、結果の各 64 ビット部分を 1 回だけ書き込み、その後の処理のために結果のどの部分も読み戻すことはありません。

これにより、特に大きな型の場合、従来のシフトと減算型の除算アルゴリズムの方が高速になるのではないかと考えるようになりました。

私の最初の考えはこれでした:

result = dividend / divisor                  // if I remember my terminology
remainder = dividend - (result * divisor)    // or something along these lines

#1: 各ビットを計算するには、通常、除数が被除数以下の場合、被除数から除数を減算します。通常、最も重要な 64 ビット部分を検査するだけで、除数が被除数より確実に小さいか、または大きいかを判断できます。これらの ms64 ビット部分が等しい場合にのみ、ルーチンは次の下位 64 ビット部分をチェックする必要があり、それらが等しい場合にのみさらに下位をチェックする必要があります。したがって、ほぼすべての反復 (結果の各ビットを計算する) で、このテストを計算するために実行される命令を大幅に減らすことができます。

#2:しかし...平均して、約50%の確率で、被除数から除数を引く必要があることがわかります。とにかく、幅全体を引く必要があります。この場合、実際には従来のアプローチよりも多くの命令を実行しました (最初にそれらを減算し、次にフラグをテストして除数 <= 被除数かどうかを判断します)。したがって、半分の時間は節約を実現し、半分の時間は損失を実現します。s1024(16 ~ 64 ビットのコンポーネントを含む) のような大きな型では、大幅な節約と損失が小さいため、このアプローチは大幅な正味の節約を実現するはずです。(-2- 64 ビット コンポーネントを含む) のような小さな型s0128では、節約はわずかであり、大きな損失ではありませんが、大きなものではありません。


したがって、私の質問は、「最も効率的な除算アルゴリズムは何ですか」ということです

#1: modern x86-64 CPUs like FX-8350
#2: executing in 64-bit mode only (no 32-bit)
#3: implementation entirely in assembly-language
#4: 128-bit to 1024-bit integer operands (nominally signed, but...)

注: 私の推測では、実際の実装は符号なし整数でのみ動作します。乗算の場合、負のオペランドを正に変換してから符号なし乗算を実行し、元のオペランドが 1 つでも負であれば結果を否定する方が簡単で効率的であることが (おそらく) 判明しました。ただし、効率的である場合は、符号付き整数アルゴリズムを検討します。

注: 浮動小数点型 ( f0128f0256f0512f1024) の最良の答えが異なる場合は、その理由を説明してください。

注: これらすべての整数データ型に対して乗算演算を実行する内部コアの unsigned-multiply ルーチンは、倍幅の結果を生成します。言い換えると:

u0256 = u0128 * u0128     // cannot overflow
u0512 = u0256 * u0256     // cannot overflow
u1024 = u0512 * u0512     // cannot overflow
u2048 = u1024 * u1024     // cannot overflow

私のコード ライブラリには、符号付き整数データ型ごとに 2 つのバージョンの乗算が用意されています。

s0128 = s0128 * s0128     // can overflow (result not fit in s0128)
s0256 = s0256 * s0256     // can overflow (result not fit in s0256)
s0512 = s0512 * s0512     // can overflow (result not fit in s0512)
s1024 = s1024 * s1024     // can overflow (result not fit in s1024)

s0256 = s0128 * s0128     // cannot overflow
s0512 = s0256 * s0256     // cannot overflow
s1024 = s0512 * s0512     // cannot overflow
s2048 = s1024 * s1024     // cannot overflow

これは、「精度を失わない」および「オーバーフローしない」という私のコード ライブラリのポリシーと一致しています (精度の低下またはオーバーフロー/アンダーフローが原因で回答が無効な場合、エラーが返されます)。ただし、倍幅の戻り値関数が呼び出された場合、そのようなエラーは発生しません。

4

5 に答える 5

6

既存の任意精度パッケージ (例: http://gmplib.org/ ) とその動作についてご存知ですか? それらは一般に、任意の精度で「できるだけ速く」実行するように設計されています。

それらを固定サイズに特化した場合 (たとえば、[手動で]部分評価手法を適用して定数を折りたたみ、ループを展開する)、必要な種類の特定の固定サイズの精度に対してかなり優れたルーチンが得られると期待しています。

また、まだご覧になっていない場合は、D. Knuth のSeminumerical Algorithmsと、多精度演算の主要なアルゴリズムを提供する古き良きが本当に良いものをご覧ください。(ほとんどのパッケージはこれらのアイデアに基づいていますが、Knuth には優れた説明と非常に多くの正しい情報があります)。

重要なアイデアは、多倍精度数を非常に大きな基数 (基数 2^64 など) であるかのように扱い、標準の 3 級演算を「数字」 (64 ビット ワードなど) に適用することです。除算は、「商 (大基数) 桁の推定、除数による推定値の乗算、被除数からの減算、左への 1 桁のシフト、満足のいく桁数になるまでの繰り返し」で構成されます。除算については、はい、すべて署名されていません(ラッパーで符号処理を行っています)。基本的なトリックは、商の桁を適切に推定し (プロセッサが提供する単精度命令を使用して)、1 桁で高速な多精度乗算を行うことです。詳細については、クヌートを参照してください。エキゾチックな(「可能な限り最速」の)改善については、多倍精度演算に関する技術研究論文を参照してください(いくつかの研究を行うことができます)。

于 2014-01-02T04:19:59.053 に答える
1

乗算については、こちらをご覧ください。

http://www.math.niu.edu/~rusin/known-math/99/karatsuba http://web.archive.org/web/20141114071302/http://www.math.niu.edu/~rusin /known-math/99/カラツバ

基本的に、512 x 512 ビットの乗算を 3 回 (4 回ではなく) 使用して、1024 x 1024 の乗算を行うことができます。または、9 つ​​の 256 x 256 ビット、または 27 の 128 x 128 ビット。追加された複雑さは、1024 x 1024 の場合でもブルート フォースに勝てないかもしれませんが、おそらくより大きな製品の場合です。これは、n ^ (log 3 / log 2) = n ^ 1.585 の乗算を使用する、最も単純な「高速」アルゴリズムです。

アセンブラを使用しないことをお勧めします。add-with-carry と同じように、インライン アセンブラで 64 x 64 -> 128 ビットの乗算を実装します (最近では gcc と clang に組み込み操作があると思います)。次に、たとえば n ビット x 256 ビット (任意の数のワード x 4 ワード) を並列に乗算し、乗算のすべての待ち時間を回避し、アセンブラーに夢中になることはありません。

于 2014-03-18T19:24:18.413 に答える
1

「大基数」アプローチは、特にアセンブリ言語で 64 ビット命令で分割された 128 ビットを実行できる場合、言及した種類の巨大なデータ型に対してより効率的です。

Newton-Raphson 反復はすぐに収束しますが、各反復では、反復ごとに膨大な数の乗算および累算ステップが必要になります。

于 2014-01-16T06:12:08.203 に答える
0

ビット数が多い場合、最も速いアルゴリズムは次のようになることを学びました。 x / y を除算する代わりに、1 / y を計算して x を掛けます。1 / y を計算するには:

1 / y is the solution t of (1 / ty) - 1 = 0.
Newton iteration: t' = t - f (t) / f' (t) 
  = t - (1 / ty - 1) / (-1 / t^2 / y)
  = t + (t - t^2 y)
  = 2t - t^2 y

ニュートン反復は二次収束します。ここでの秘訣: 1024 ビットの精度が必要な場合は、32 ビットで開始し、1 つの反復ステップで 64 ビットが得られ、次の反復ステップで 128 ビットが得られ、次に 256、次に 512、次に 1024 になります。 1 つは完全な精度を使用します。全体として、1 つの 512 x 512-> 1024 積 (t^2)、1 つの 1024 x 1024 -> 1024 積 (t^2 y = 1 / y)、および別の 1024 x 1024 積 (x * ( 1 /年))。

もちろん、反復ごとにエラーが何であるかを非常に正確に把握する必要があります。おそらく40ビットから始めて、各ステップで少し精度を失うので、最後には十分です。

あなたが学校で学んだように、これがどの時点で単純な力ずくの分割よりも速く実行されるかはわかりません. そして y は全ビット数よりも少ない場合があります。

于 2014-03-18T19:04:28.983 に答える
0

代替手段は力ずくです。x の最上位 128 ビットを取り、y の最上位 64 ビットで割り、商の最上位 64 ビット r を取得し、x から rxy を引くことができます。必要に応じて繰り返し、エラーの大きさを注意深く確認します。

部門は遅いです。したがって、2^127 / (y の上位 64 ビット) を 1 回計算します。次に、次の 64 ビットを推定するには、x の最上位 64 ビットにこの数値を掛けて、すべてを正しい場所にシフトします。掛け算は割り算よりもはるかに高速です。

次に、これらすべての操作に長い待ち時間があることがわかります。たとえば、結果を得るのに 5 サイクルかかりますが、サイクルごとに乗算を行うことができます。したがって、結果の 64 ビットを推定します。x の上限で r * y の減算を開始すると、次の 64 ビットをできるだけ早く推定できます。次に、遅延によるペナルティを回避するために、x から 2 つ以上の積を同時に減算します。これを実装するのは大変です。1024 ビット (16 個の 64 ビット整数) でも価値がないものもあります。

于 2014-03-18T19:14:11.510 に答える