bst アセンブリ命令を使用すると、より高速に動作するかどうか疑問に思っていました。そのため、3 つの実装を試し、1,000 万回の反復で次の結果を得ました。
あなたの実装 (Dr.Kameleon) 1.77 秒
log2() の実装 (icepack) 2.17 秒
私のアセンブリ実装 (私) 0.16 秒
出力:
bits version:
Function started at 0
ended at 177
spent 177 (1.770000 seconds)
c version:
Function started at 177
ended at 394
spent 217 (2.170000 seconds)
c version:
Function started at 394
ended at 410
spent 16 (0.160000 seconds)
C/C++ についての 1 つのポイント、静的は恐ろしいです。実際には、CPU命令のリストでコンパイルされます(私が期待するものでもありません!!!)代わりに、名前のない名前空間で関数の外側の配列を使用してください。そうすれば期待通りの効果が得られます。アセンブリでは、.long (または他のサイズ) を使用してから %rip を使用して、IP からのデータを参照できます。
注: コンパイルすると、アセンブリ バージョンで使用されているサイズ (n) が表示されないため、返された配列が有効かどうかはわかりません。それ以外では、コード自体が 5 つのアセンブリ命令のループになるため、速度がわずかに向上します (約 10 倍)。
log2() が遅い理由は、数値を xmm レジスタに変換してから別の関数を呼び出すためです。次に、xmm レジスターを通常のレジスターに戻します。
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/times.h>
#include <string.h>
#include <math.h>
#include <vector>
using namespace std;
namespace
{
const int index64[64] = {
63, 0, 58, 1, 59, 47, 53, 2,
60, 39, 48, 27, 54, 33, 42, 3,
61, 51, 37, 40, 49, 18, 28, 20,
55, 30, 34, 11, 43, 14, 22, 4,
62, 57, 46, 52, 38, 26, 32, 41,
50, 36, 17, 19, 29, 10, 13, 21,
56, 45, 25, 31, 35, 16, 9, 12,
44, 24, 15, 8, 23, 7, 6, 5 };
const uint64_t debruijn64 = 0x07EDD5E59A4E28C2ULL;
}
int firstBit(uint64_t bitboard)
{
return index64[((bitboard & -bitboard) * debruijn64) >> 58];
}
vector<int> bits(uint64_t bitboard)
{
vector<int> res;
res.reserve(64);
while(bitboard)
{
int first = firstBit(bitboard);
res.push_back(first);
bitboard &= ~(1ULL << first);
}
return res;
}
vector<int> bits_c(uint64_t bitboard)
{
int n;
vector<int> res;
res.reserve(64);
for (n = 0; bitboard != 0; n++, bitboard &= (bitboard - 1))
{
res.push_back(log2(bitboard & ~(bitboard - 1)));
}
return res;
}
vector<int> bits_asm(uint64_t bitboard)
{
int64_t n(0);
int res[64];
asm(
"bsf %[b], %%rax\n\t"
"je exit\n\t"
".align 16\n"
"loop:\n\t"
"mov %%eax, (%[r],%[n],4)\n\t"
"btr %%rax, %[b]\n\t"
"inc %[n]\n\t"
"bsf %[b], %%rax\n\t"
"je loop\n"
"exit:\n\t"
: /* output */ "=r" (n)
: /* input */ [n] "r" (n), [r] "r" (res), [b] "r" (bitboard)
: /* state */ "eax", "cc"
);
return vector<int>(res, res + n);
}
class run_timer
{
public:
run_timer()
{
}
void start()
{
times(&f_start);
}
void stop()
{
times(&f_stop);
}
void report(const char *msg)
{
printf("%s:\n"
"Function started at %ld\n"
" ended at %ld\n"
" spent %ld (%f seconds)\n",
msg,
f_start.tms_utime,
f_stop.tms_utime,
f_stop.tms_utime - f_start.tms_utime,
(double)(f_stop.tms_utime - f_start.tms_utime)/(double)sysconf(_SC_CLK_TCK));
}
struct tms f_start;
struct tms f_stop;
};
int main(int argc, char *argv[])
{
run_timer t;
t.start();
for(int i(0); i < 10000000; ++i)
{
bits(rand());
}
t.stop();
t.report("bits version");
t.start();
for(int i(0); i < 10000000; ++i)
{
bits_c(rand());
}
t.stop();
t.report("c version");
t.start();
for(int i(0); i < 10000000; ++i)
{
bits_asm(rand());
}
t.stop();
t.report("c version");
return 0;
}
次のコマンド ラインを使用して g++ でコンパイルします。
c++ -msse4.2 -O2 -o bits -c bits.cpp
-msse4.2 が log2() バージョンの問題である可能性があると思われるかもしれませんが、それなしで試してみたところ、log2() は遅くなりました。
ところで、この方法は移植性がないためお勧めしません。これらの命令を理解できるのは、Intel ベースのコンピューターだけです。