web-dev-qa-db-ja.com

文字列をintに変換するC ++の最も効率的な方法(atoiより高速)

タイトルで述べたように、私はatoiよりも多くのパフォーマンスを提供できるものを探しています。現在、私が知っている最速の方法は

atoi(mystring.c_str())

最後に、Boostに依存しないソリューションを好みます。これを行うための優れたパフォーマンスのトリックはありますか?

追加情報:intは20億を超えず、常に正であり、文字列には小数点以下の桁はありません。

22
user788171

ルックアップテーブルを使用したソリューションを試しましたが、問題が多く、実際にはそれほど高速ではありませんでした。最速のソリューションは、想像力が最も低いことが判明しました。

int fast_atoi( const char * str )
{
    int val = 0;
    while( *str ) {
        val = val*10 + (*str++ - '0');
    }
    return val;
}

100万のランダムに生成された文字列でベンチマークを実行します。

fast_atoi : 0.0097 seconds
atoi      : 0.0414 seconds

公平を期すために、コンパイラーにインライン化を強制しないことで、この関数もテストしました。結果は依然として良好でした:

fast_atoi : 0.0104 seconds
atoi      : 0.0426 seconds

データがfast_atoi関数の要件に準拠している場合、それはかなり妥当なパフォーマンスです。要件は次のとおりです。

  1. 入力文字列に数字のみが含まれているか、空です
  2. 入力文字列は、0からINT_MAXまでの数字を表します
25
paddy

atoiは、特定の仮定を考慮して大幅に改善できます。これは、C++ and Beyond 2012カンファレンスでのAndrei Alexandrescuによるプレゼンテーションで強力に実証されました。 Hiの置換では、ループの展開とALUの並列性を使用して、パフォーマンスを大幅に改善しました。私は彼の資料を持っていませんが、このリンクは同様のテクニックを使用しています: http://tombarta.wordpress.com/2008/04/23/specializing-atoi/

16
Scott Jones

このページ 異なるコンパイラを使用して、異なる文字列-> int関数間の変換速度を比較します。エラーチェックを提供しない単純な関数は、提示された結果によると、atoi()の約2倍の速度を提供します。

// Taken from http://tinodidriksen.com/uploads/code/cpp/speed-string-to-int.cpp
int naive(const char *p) {
    int x = 0;
    bool neg = false;
    if (*p == '-') {
        neg = true;
        ++p;
    }
    while (*p >= '0' && *p <= '9') {
        x = (x*10) + (*p - '0');
        ++p;
    }
    if (neg) {
        x = -x;
    }
    return x;
}

それは常にポジティブです

マイクロ最適化のために、上記のコードのネガティブチェックを削除します。

文字列に数字以外の文字が含まれないことを保証できる場合は、ループを変更してさらに最適化できます

while (*p >= '0' && *p <= '9') {

while (*p != '\0' ) {

あなたを残す

unsigned int naive(const char *p) {
    unsigned int x = 0;
    while (*p != '\0') {
        x = (x*10) + (*p - '0');
        ++p;
    }
    return x;
}
10
x-x

ここのコード例のかなりの部分は非常に複雑であり、不必要な作業を行っています。つまり、コードはよりスリムで高速になります。

変換ループは、多くの場合、各文字で3つの異なることを行うために作成されます。

  • 文字列の終わりの文字である場合は救済する
  • 数字でない場合は救済する
  • コードポイントから実際の数値に変換します

最初の観察:数字ではないため、文字列の終わりの文字を個別にチェックする必要はありません。したがって、「桁数」のチェックはEOS条件を暗黙的にカバーします。

2番目の観測:_(c >= '0' && c <= '9')_のような範囲テストの二重条件は、符号なしの型を使用して範囲をゼロに固定することにより、単一のテスト条件に変換できます。そのようにして、範囲の先頭より下に不要な値が存在することはありません。すべての不要な値は上限より上の範囲にマップされます:_(uint8_t(c - '0') <= 9)_

とにかくここで_c - '0'_を計算する必要があります...

したがって、内部変換ループは、

_uint64_t n = digit_value(*p);
unsigned d;

while ((d = digit_value(*++p)) <= 9)
{
   n = n * 10 + d;
}
_

ここのコードは、pが数字を指しているという前提条件で呼び出されます。これが、最初の数字がさらに苦労せずに抽出される理由です(余分なMULも回避します)。

その前提条件は、最初に現れるかもしれないほど奇妙ではありません。なぜなら、数字を指すpが、このコードが最初にパーサーによって呼び出される理由であるためです。私のコードでは、シバン全体が次のようになっています(アサーションおよびその他の製品品質のノイズが除去されています):

_unsigned digit_value (char c)
{
   return unsigned(c - '0');
}

bool is_digit (char c)
{
   return digit_value(c) <= 9;
}

uint64_t extract_uint64 (char const **read_ptr)
{
   char const *p = *read_ptr;
   uint64_t n = digit_value(*p);
   unsigned d;

   while ((d = digit_value(*++p)) <= 9)
   {
      n = n * 10 + d;
   }

   *read_ptr = p;

   return n;
}
_

コードがインライン化され、呼び出し元のコードが既にdigit_value()を呼び出してその値を計算している場合、最初のis_digit()への呼び出しはコンパイラーによって省略されます。

_n * 10_は、少なくともgcc 4.8.1とVC++ 2013を搭載したマシンでは、手動シフト(例n = (n << 3) + (n << 1) + d)よりも高速です。両方のコンパイラがLEAを使用していると思います一度に最大3つの値を追加し、そのうちの1つを2、4、または8でスケーリングするインデックススケーリング.

いずれにせよ、それはまさにそのとおりです。ニースのきれいなコードを別の関数で記述し、目的のロジック(n * 10、x%CHAR_BIT、何でも)を表現し、コンパイラーはそれをシフト、マスキング、LEAingなど、インラインに変換しますすべてを大きな悪いパーサーループに入れ、高速化するためにフードの下で必要なすべての乱雑さを処理します。もうinlineをすべての前に置く必要さえありません。どちらかと言えば、コンパイラーが熱心になったときに__declspec(noinline)を慎重に使用することで、反対のことをしなければなりません。

テキストファイルとパイプから数十億の数値を読み取るプログラムで上記のコードを使用しています。長さが9..10桁の場合、1秒あたり1億1500万uintを変換し、長さ19..20桁(gcc 4.8.1)の場合は6,000万/ sを変換します。それはstrtoull()の10倍以上の速さです(そして、私の目的にはかろうじて十分ですが、私は逃げます...)。それは、それぞれ1,000万個の数値(100..200 MB)を含むテキストBLOBを変換するタイミングです。つまり、メモリのタイミングにより、これらの数値は、キャッシュから実行される合成ベンチマークよりも少し悪くなります。

7
DarthGizka

Paddyのfast_atoiisよりも速いatoi-疑いの影なし-ただし、nsigned integersに対してのみ機能します。

以下に、符号なし整数のみを許可するが、コストのかかる操作*+に置き換えることで変換をさらに高速化するPaddyのfast_atoiの評価版を配置します

unsigned int fast_atou(const char *str)
{
    unsigned int val = 0;
    while(*str) {
        val = (val << 1) + (val << 3) + *(str++) - 48;
    }
    return val;
}

ここでは、complete version of fast_atoi()を入れました。

int fast_atoi(const char *buff)
{
    int c = 0, sign = 0, x = 0;
    const char *p = buff;

    for(c = *(p++); (c < 48 || c > 57); c = *(p++)) {if (c == 45) {sign = 1; c = *(p++); break;}}; // eat whitespaces and check sign
    for(; c > 47 && c < 58; c = *(p++)) x = (x << 1) + (x << 3) + c - 48;

    return sign ? -x : x;
} 
3
soerium

文字列ストリームを使用しないのはなぜですか?特定のオーバーヘッドについてはわかりませんが、以下を定義できます。

int myInt; 
string myString = "1561";
stringstream ss;
ss(myString);
ss >> myInt;

もちろん、あなたはする必要があります

#include <stringstream> 
2
Rome_Leader

Gccのatoi関数全体は次のとおりです。

long atoi(const char *str)
{
    long num = 0;
    int neg = 0;
    while (isspace(*str)) str++;
    if (*str == '-')
    {
        neg=1;
        str++;
    }
    while (isdigit(*str))
    {
        num = 10*num + (*str - '0');
        str++;
    }
    if (neg)
        num = -num;
    return num;
 }

空白とネガティブチェックはあなたの場合には不要ですが、ナノ秒のみを使用します。

isdigitはほぼ確実にインライン化されるため、いつでもコストはかかりません。

ここには改善の余地がありません。

1
Joel

唯一の決定的な答えは、コンパイラー、実際のデータをチェックすることです。

私が試してみたいこと(メモリアクセスを使用している場合でも、キャッシュに応じて遅くなる可能性があります)は

int value = t1[s[n-1]];
if (n > 1) value += t10[s[n-2]]; else return value;
if (n > 2) value += t100[s[n-3]]; else return value;
if (n > 3) value += t1000[s[n-4]]; else return value;
... continuing for how many digits you need to handle ...

if t1t10などは静的に割り当てられ、定数であり、コンパイラはエイリアスを恐れず、生成されるマシンコードはかなりまともです。

0
6502

より高速な変換機能

乗算は常にその合計とシフトが遅いため、シフトで乗算を変更します

int fast_atoi( const char * str )
{
    int val = 0;
    while( *str ) {
        val = (val << 4) - (val << 2) - (val << 1) + (*str++ - '0');
    }
return val;
}
0
hamSh

これが私のAtoiは私が思いつく最も速いものです。私はmsvc 2010でコンパイルしたので、両方のテンプレートを組み合わせることが可能かもしれません。 msvc 2010では、テンプレートを組み合わせたときに、cb引数を指定する時間が遅くなりました。

Atoiは、ほとんどすべての特別なatoiケースを処理し、これと同じかそれより高速です。

int val = 0;
while( *str ) 
    val = val*10 + (*str++ - '0');

コードは次のとおりです。

#define EQ1(a,a1) (BYTE(a) == BYTE(a1))
#define EQ1(a,a1,a2) (BYTE(a) == BYTE(a1) && EQ1(a,a2))
#define EQ1(a,a1,a2,a3) (BYTE(a) == BYTE(a1) && EQ1(a,a2,a3))

// Atoi is 4x faster than atoi.  There is also an overload that takes a cb argument.
template <typename T> 
T Atoi(LPCSTR sz) {
    T n = 0;
    bool fNeg = false;  // for unsigned T, this is removed by optimizer
    const BYTE* p = (const BYTE*)sz;
    BYTE ch;
    // test for most exceptions in the leading chars.  Most of the time
    // this test is skipped.  Note we skip over leading zeros to avoid the 
    // useless math in the second loop.  We expect leading 0 to be the most 
    // likely case, so we test it first, however the cpu might reorder that.
    for ( ; (ch=*p-'1') >= 9 ; ++p) { // unsigned trick for range compare
      // ignore leading 0's, spaces, and '+'
      if (EQ1(ch, '0'-'1', ' '-'1', '+'-'1'))
        continue;
      // for unsigned T this is removed by optimizer
      if (!((T)-1 > 0) && ch==BYTE('-'-'1')) {
        fNeg = !fNeg;
        continue;
      }
      // atoi ignores these.  Remove this code for a small perf increase.
      if (BYTE(*p-9) > 4)  // \t, \n, 11, 12, \r. unsigned trick for range compare
        break;
    }
    // deal with rest of digits, stop loop on non digit.
    for ( ; (ch=*p-'0') <= 9 ; ++p) // unsigned trick for range compare
      n = n*10 + ch; 
    // for unsigned T, (fNeg) test is removed by optimizer
    return (fNeg) ? -n : n;
}

// you could go with a single template that took a cb argument, but I could not
// get the optimizer to create good code when both the cb and !cb case were combined.
// above code contains the comments.
template <typename T>
T Atoi(LPCSTR sz, BYTE cb) {
    T n = 0;
    bool fNeg = false; 
    const BYTE* p = (const BYTE*)sz;
    const BYTE* p1 = p + cb;
    BYTE ch;
    for ( ; p<p1 && (ch=*p-'1') >= 9 ; ++p) {
      if (EQ1(ch,BYTE('0'-'1'),BYTE(' '-'1'),BYTE('+'-'1')))
        continue;
      if (!((T)-1 > 0) && ch == BYTE('-'-'1')) {
        fNeg = !fNeg;
        continue;
      }
      if (BYTE(*p-9) > 4)  // \t, \n, 11, 12, \r
        break;
    }
    for ( ; p<p1 && (ch=*p-'0') <= 9 ; ++p)
      n = n*10 + ch; 
    return (fNeg) ? -n : n;
}
0
johnnycrash