4

これは宿題ではなく、私が思っていることです。したがって、ストレートコンピューティングの階乗は正確に高速ではありません。メモ化は役立ちますが、結果が32ビットまたは64ビットに収まる場合、階乗はと0を介した入力に対してのみ機能1220ます。したがって...ルックアップテーブルを使用することもできます。

n   n!
0   1       
1   1       
2   2       
3   6       
4   24      
5   120     
6   720     
7   5040        
8   40320       
9   362880      
10  3628800     
11  39916800        
12  479001600       
13  6227020800  2^32=   4294967296
14  87178291200     
15  1.30767E+12     
16  2.09228E+13     
17  3.55687E+14     
18  6.40237E+15     
19  1.21645E+17     
20  2.4329E+18      
        2^64=   1.84467E+19

したがって、インラインアセンブリを使用するインラインC ++因数分解関数が必要であり、結果として32ビットまたは64ビットの符号なし整数が期待されるとします。入力が負であるか、オーバーフローを引き起こすのに十分な大きさである場合、出力は0である必要があります。これをアセンブリで実行して、消費するサイクルを最小限に抑えるにはどうすればよいでしょうか。このコードは、64ビットのIntel/AMDアーキテクチャで実行されます。可能であれば、私は最悪のシナリオを改善することに興味があるので20!、計算にそれほど長くはかからないはずです0!-うまくいけば、バイナリ検索アプローチがあります。うまくいけば、それを行うための巧妙なトリックがありif (n == 0 || n == 1) { return 1; }ます。また、出力が32ビットである必要がある場合、アセンブリ命令にはコードとデータの両方を含めることができると思います。私のアセンブリの知識は弱いです。質問があまり意味をなさない場合は、私に知らせてください。

C ++で関数を使用できると便利ですが、より現実的な問題になります。たとえば、関数の呼び出しにコストがかかる場合、アセンブリの本体で1〜2クロックサイクルを節約しようとしてもあまり役に立ちません。

4

7 に答える 7

12

アセンブリでルックアップテーブルを巧みに作成しました。アセンブリが錆びている場合に備えて、ルーチンはパラメータがecxレジスタにあることを期待しています。範囲内にあることを確認してから、ルックアップテーブルの値をeaxedxレジスタに読み込みます。値が範囲外の場合は、xorを実行して自分自身eaxedx登録します(これにより、強制的に0になります)。残念ながら、これはアセンブリルーチンであるため、コンパイラはコードをインライン化できません。しかし、すばらしいアセンブリルーチンを作成することで節約した数サイクルは、インライン化することで得られる利益を補う以上のものになると確信しています。

factorial:
    xorl    %eax, %eax
    xorl    %edx, %edx
    cmpl    $20, %ecx
    ja  .TOOBIG
    movl    CSWTCH.1(,%ecx,8), %eax
    movl    CSWTCH.1+4(,%ecx,8), %edx
.TOOBIG:

LOOKUP_TABLE:
    .section    .rodata
    .align 32
    .type   CSWTCH.1, @object
    .size   CSWTCH.1, 168
CSWTCH.1:
    .long   1
    .long   0
    .long   1
    .long   0
    .long   2
    .long   0
    .long   6
    .long   0
    .long   24
    .long   0
    .long   120
    .long   0
    .long   720
    .long   0
    .long   5040
    .long   0
    .long   40320
    .long   0
    .long   362880
    .long   0
    .long   3628800
    .long   0
    .long   39916800
    .long   0
    .long   479001600
    .long   0
    .long   1932053504
    .long   1
    .long   1278945280
    .long   20
    .long   2004310016
    .long   304
    .long   2004189184
    .long   4871
    .long   -288522240
    .long   82814
    .long   -898433024
    .long   1490668
    .long   109641728
    .long   28322707
    .long   -2102132736
    .long   566454140

ルックアップテーブルは維持するのが難しいので、それを構築するために使用したスクリプトを含めました

static constexpr uint64_t const_factorial(uint32_t i) {
    return (i==0)? 1: (i * const_factorial(i-1));
}

uint64_t factorial(uint32_t i) {
    switch(i) {
        case 0: return const_factorial(0);
        case 1: return const_factorial(1);
        case 2: return const_factorial(2);
        case 3: return const_factorial(3);
        case 4: return const_factorial(4);
        case 5: return const_factorial(5);
        case 6: return const_factorial(6);
        case 7: return const_factorial(7);
        case 8: return const_factorial(8);
        case 9: return const_factorial(9);
        case 10: return const_factorial(10);
        case 11: return const_factorial(11);
        case 12: return const_factorial(12);
        case 13: return const_factorial(13);
        case 14: return const_factorial(14);
        case 15: return const_factorial(15);
        case 16: return const_factorial(16);
        case 17: return const_factorial(17);
        case 18: return const_factorial(18);
        case 19: return const_factorial(19);
        case 20: return const_factorial(20);
        default: return 0;
    }
}

あなたが私のユーモアの貧弱な試みでそれを逃した場合に備えて。C ++コンパイラは、コードを正しく最適化する能力を超えています。ご覧のとおり、ルックアップテーブル、二分探索木、またはハッシュについては、特別なことをする必要はありませんでした。単純なswitchステートメントとコンパイラーが残りを行いました。

于 2010-07-08T22:22:45.390 に答える
5

骨格筋を曲げてからしばらく経ちましたので、一般的なアドバイスをさせていただきます。

すべてのアイテムの正確な数とサイズが事前にわかっているので、値の連続した配列を作成するだけです(ハードコーディングまたは事前計算)。関数への入力を検証した後(<0または> 12/20)、単純なオフセットアドレス指定を使用して適切な値を取得できます。これはO(1)時間で機能します。

于 2010-07-08T19:22:26.660 に答える
1

2021年から更新。C++17が手元にある。

以下より速い方法はないと思います。アセンブラは必要ありません。

符号なし64ビット値に適合する階乗の数は非常に少ないため(21)、コンパイル時のconstexpr配列は主に21 * 8=168バイトのみを使用します。

168バイト

その数は非常に少ないため、コンパイル時間を簡単に構築して、constexpr std::arrayそれ以上の考慮事項をすべて停止できます。

実際、すべてはコンパイル時に実行できます。

最初に、階乗をconstexpr関数として計算するためのデフォルトのアプローチを定義します。

constexpr unsigned long long factorial(unsigned long long n) noexcept {
    return n == 0ull ? 1 : n * factorial(n - 1ull);
}

これにより、階乗はコンパイル時に簡単に計算できます。次に、std::arrayすべての階乗を入力します。また、を使用constexprして、可変個引数パラメーターパックを使用したテンプレートにします。

std::integer_sequenceインデックス0、1、2、3、4、5、...の階乗を作成するために使用します。

それは単純で複雑ではありません。

template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { factorial(ManyIndices)... } };
};

この関数には整数シーケンス0、1、2、3、4、...が供給されstd::array<unsigned long long, ...>、対応する階乗とともにaが返されます。

最大21個の値を保存できることがわかっています。したがって、次の関数を作成します。これは、次のように、整数シーケンス1,2,3,4、...、20,21で上記を呼び出します。

constexpr auto generateArray()noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}

そして今、ついに、

constexpr auto Factorial = generateArray();

std::array<unsigned long long, 21>すべての階乗を含むFactorialという名前のコンパイル時が表示されます。そして、i番目の階乗が必要な場合は、単純にと書くことができますFactorial[i]。実行時に計算は行われません。

階乗を計算するより速い方法があるとは思いません。

以下の完全なプログラムをご覧ください。

#include <iostream>
#include <array>
#include <utility>
// ----------------------------------------------------------------------
// All the below will be calculated at compile time
// constexpr factorial function
constexpr unsigned long long factorial(unsigned long long n) noexcept {
    return n == 0ull ? 1 : n * factorial(n - 1ull);
}
// We will automatically build an array of factorials at compile time
// Generate a std::array with n elements 
template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { factorial(ManyIndices)... } };
};
// Max index for factorials for an 64bit unsigned value 
constexpr size_t MaxIndexFor64BitValue = 21;

// Generate the required number of elements
constexpr auto generateArray()noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}
// This is an constexpr array of all factorials numbers
constexpr auto Factorial = generateArray();

// All the above was compile time
// ----------------------------------------------------------------------

// Test function
int main() {
    for (size_t i{}; i < MaxIndexFor64BitValue; ++i)
        std::cout << i << '\t' << Factorial[i] << '\n';
    return 0;
}

Microsoft Visual Studio Community 2019バージョン16.8.2で開発、コンパイル、テストされています

さらに、gcc10.2およびclang11.0.1でコンパイルおよびテストされています

言語:C ++ 17

于 2021-02-12T23:05:35.800 に答える
0

とにかく、アセンブリバージョンはC++バージョンよりも高速になると誰が言いますか。実際、誰がそれが速度でさえ一致すると言いますか?私はあなたがコンパイラーのようにそれを速くすることさえできなかった100ドルに賭けるでしょう。

于 2010-07-08T19:41:47.567 に答える
0

一般的な要求に応じて、パフォーマンス的には、ハッシュテーブルではなくバイナリ検索であると言われています(std C ++には私が信じていることはありません)。

#include <map>

void main()
{
    std::map<int, BigIntThing> factMap;
    // insert all elements here, probably fancier ways to do this
    factMap.insert( 1 );
    factMap.insert( 1 );
    factMap.insert( 2 );
    // ....
    // to access, say 15!
    BigIntThing factMap[15]; // I think the index is right >_<
}

それでおしまい。Astd::mapが注文されているので、BigIntThingに比較演算子がある場合は、すべて問題ありません。constこれを取得する方法、および/staticまたはglobal希望する方法でコンパイルする方法があるはずです。

于 2010-07-08T20:59:49.240 に答える
0

0〜19の数値のみを使用している場合は、ハッシュテーブルまたはバイナリツリーはやり過ぎです。を作成してunsigned int[20]から、インデックスをクエリするだけです。

const unsigned int FACTORIALS[20] = {1,1,2,6,24,120,etc..};

unsigned int factorial(unsigned int num) {
    if(num >= 0 && num <= 19) {
        return FACTORIALS[num];
    }
    else {
        throw // some sort of exception
    }
}

テンプレートを使用してアレイを構築することもできます。

于 2010-07-08T23:11:11.680 に答える
0

gccの回答

...おそらくあなたのものを打ち負かす、からコンパイルされました:

uint64_t answers[] = {
    1ULL,
    1ULL,
    2ULL,
    6ULL,
    24ULL,
    ...
    2432902008176640000ULL,
};

uint64_t factorial(unsigned int i) {
    if(i >= sizeof(answers) / sizeof(*answers))
        return 0;
    else
        return answers[i];
}

...そしてアセンブリ...

factorial:
    cmpl    $20, %edi
    movl    $0, %eax
    ja  .L3
    movslq  %edi,%eax
    movq    answers(,%rax,8), %rax
.L3:
    rep
    ret
answers:
    .quad 1
    .quad 1
    ...

...これは最初の64ビットアセンブラの回答のようです...

于 2010-07-08T23:12:11.883 に答える