C ++ найбольш эфектыўны спосаб для пераўтварэння радкі ў цэлае (хутчэй, чым atoi)

Як было згадана ў назве, я шукаю нешта, што можа даць мне больш прадукцыйнасці, чым atoi. У цяперашні час самы хуткі спосаб я ведаю,

atoi(mystring.c_str())

Нарэшце, я хацеў бы рашэнне, якое не абапіраецца на Boost. Хто-небудзь ёсць добрыя трукі прадукцыйнасці для рабіць гэта?

Дадатковая інфармацыя: INT не перавысіць 2 мільярды, гэта заўсёды станоўча, то радок не мае знака пасля коскі ў ім.

18
Я дадаў некаторую дадатковую інфармацыю, рэгулярны памерам Інта заўсёды станоўчыя, ні дзесятковых знакаў у радку.
дададзена аўтар user788171, крыніца
Вы атрымліваеце добрыя адказы, але я заўсёды павінен задацца пытанне - вы на самой справе ведаеце, atoi усё сам па сабе спажываюць здаровыя працэнты ад вашага агульнага часу? Людзі часта задаюць падобныя пытанні, калі на самай справе ёсць што-то яшчэ, што дало б значна больш паскарэння, але яны не ведаюць, як знайсці такія рэчы.
дададзена аўтар Mike Dunlavey, крыніца
Вы будзеце мець цяжкі час збіцця atoi.
дададзена аўтар Joel, крыніца
Адказ на гэтае пытанне можа залежаць трохі ад таго, што лікі дыяпазону вы дазваляеце. Вы хочаце, каб пераўтварыць <�я> любы </я> цэлы лік, або ваш дапушчальны ўваход больш канкрэтна? Вы ведаеце напэўна, што MyString ўтрымлівае <�я> толькі цэлы лік без іншых сымбаляў? Ці можа гэта быць адмоўным?
дададзена аўтар paddy, крыніца

10 адказы

Я эксперыментаваў з рашэннямі з выкарыстаннем даведачных табліц, але знайшоў іх багатыя праблемамі, і на самай справе не вельмі хутка. Самае хуткае рашэнне апынулася найменш imaginitive:

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

Запуск бенчмарк з мільёна выпадкова згенераваных радкоў:

fast_atoi : 0.0097 seconds
atoi      : 0.0414 seconds

Каб быць справядлівым, я праверыў гэтую функцыю, прымушаючы кампілятар ня ўбудоўваць яго. Вынікі былі яшчэ добра:

fast_atoi : 0.0104 seconds
atoi      : 0.0426 seconds

Пры ўмове, што вашыя дадзеныя адпавядаюць патрабаванням fast_atoi функцыя, то ёсць даволі разумнае ўяўленне. патрабаванні:

  1. Input string contains only numeric characters, or is empty
  2. Input string represents a number from 0 up to INT_MAX
18
дададзена

atoi can be improved upon significantly, given certain assumptions. This was demonstrated powerfully in a presentation by Andrei Alexandrescu at the C++ and Beyond 2012 conference. Hi s replacement used loop unrolling and ALU parallelism to achieve orders of magnitude in perf improvement. I don't have his materials, but this link uses a similar technique: http://tombarta.wordpress.com/2008/04/23/specializing-atoi/

15
дададзена
Я думаю, што я бачыў гэта. Ці з'яўляецца гэта прэзентацыі вы спасылаецеся? Гэта не C ++ і за яго межамі, хоць. І я думаю, што гэта ў асноўным аб ИНТ да радка, а не наадварот. Але ёсць шмат, каб даведацца ад гэтага ў любым выпадку.
дададзена аўтар jogojapan, крыніца

This page compares conversion speed between different string->int functions using different compilers. The naive function, which offers no error checking, offers speeds roughly twice as fast as aуi(), according у the results presented.

// Taken from http://tinodidriksen.com/uploads/code/cpp/speed-string-у-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;
}
9
дададзена
(* Р ++) і 15, верагодна, хутчэй, чым * р ++ - '0'
дададзена аўтар johnnycrash, крыніца
+ Гэта ў асноўным так, як я гэта зрабіць, хоць у цыкле я імкнуся пісаць {х * = 10; х + = (* р ++ - '0');} . Гэта, верагодна, складае прыкладна тое ж самае.
дададзена аўтар Mike Dunlavey, крыніца
@johnnycrash не ведаю, чаму гэта было б. Пабітавае & і пастаяннае адніманне як адна інструкцыя.
дададзена аўтар Eric Pauley, крыніца
@DyP: '\ 0' зламаецца з пятлі ... гэта < '0' . Ва ўсякім выпадку, звязаная старонка не пералічыце, акрамя гэтага наіўнай завесы, якія з'яўляюцца сур'ёзнымі кандыдаты апярэджваць atoi функцыі - яны не выкарыстоўваюць якую-небудзь эфектыўнасць, якая паступае ад ідэі вышэй паўторна чакання дапушчальных сімвалаў, заўсёды дадатны, вядомыя максімальнага памеру , няма неабходнасці ў якой-небудзь праверкі памылак ....
дададзена аўтар Tony Delroy, крыніца
Тым не менш, наіўная рэалізацыя не адпавядае Стандарту: ён не адмаўляецца ад вядучага прабельных. Калі вам не патрэбныя гарантыі стандарт ..
дададзена аўтар dyp, крыніца
На жаль, вы маеце рацыю, аператар параўнання мозг зламаны, СРЮ .. Але тады вы маглі б змяніць яго у той час як (* р! = «\ 0») ..
дададзена аўтар dyp, крыніца

Вельмі нешматлікія з прыкладаў кода тут даволі складаныя і рабіць непатрэбную працу, то ёсць код можа быць танчэй і хутчэй.

цыклы пераўтварэнні часта пішацца, каб зрабіць тры рэчаў з кожным персанажам:

  • выручаць, калі гэта канец з-радка знакаў
  • выручаць, калі гэта не лічба
  • пераўтварыць яго з кодавай кропкі да фактычнага значэння лічбаў

Першае назіранне: няма неабходнасці правяраць персанаж канца-радка ў асобнасці, так як гэта не лічба. Такім чынам, праверка на «digitness» ахоплівае стан EOS няяўна.

Second observation: double conditions for range testing as in (c >= '0' && c <= '9') can be converted to a single test condition by using an unsigned type and anchoring the range at zero; that way there can be no unwanted values below the beginning of the range, all unwanted values are mapped to the range above the upper limit: (uint8_t(c - '0') <= 9)

Проста так атрымалася, што с - «0» павінен быць вылічаны тут у любым выпадку ...

Такім чынам, унутраны цыкл пераўтварэнні можа быць скарочаны да

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

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

Гэты код называецца з папярэдняй умовай, што р паказваць на лічбы, таму першая лічба здабываецца без лішніх слоў (што таксама дазваляе пазбегнуць лішняга MUL).

Гэта папярэдняя ўмова менш дзіўнымі, чым можа здацца на першы, так як р паказвае на лічбу з'яўляецца прычынай, чаму гэты код называецца аналізатарам, у першую чаргу. У маім кодзе ўвесь прытон выглядае наступным чынам (зацвярджэння і іншай прадукцыі якасці шуму Апушчаныя):

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 happens to be faster than manual shifting (e.g. n = (n << 3) + (n << 1) + d), at least on my machine with gcc 4.8.1 and VC++ 2013. My guess is that both compilers use LEA with index scaling for adding up to three values in one go and scaling one of them by 2, 4, or 8.

У любым выпадку гэта менавіта так і павінна быць: мы пішам добры чысты код у асобных функцыях і выказаць патрэбную логіку (п * 10, х% CHAR_BIT, безадносна) і кампілятар пераўтворыць яго зняцце, маскіроўкі, LEAing і гэтак далей, Внутристрочные усё ў вялікую дрэнную пятлю сінтаксічнага аналізатара і клапоціцца пра ўсё неабходнай бязладны пад капотам, каб зрабіць рэчы хутка. Мы нават не павінны прытрымлівацца убудаваны перад усім больш. Калі што-небудзь, то мы павінны рабіць супрацьлеглае, выкарыстоўваючы __ declspec (noinline) разважліва, калі кампілятары атрымаць больш-нецярплівыя.

Я выкарыстоўваю прыведзены вышэй код у праграме, якая счытвае мільярды лічбаў з тэкставых файлаў і труб; ён пераўтворыць 115 мільёнаў uints у секунду, калі даўжыня складае 9..10 лічбаў, і 60 мільёнаў/с даўжынёй 19..20 лічбаў (GCC 4.8.1). Гэта больш чым у дзесяць разоў хутчэй, чым strtoull() (і абрэз для маіх мэтаў, але я адцягнуўся ...). Гэты час для пераўтварэння тэкставых згусткаў, якія змяшчаюць 10 млн нумароў кожнага (100..200 МБ), а гэта азначае, што таймінгі памяці робяць гэтыя лічбы з'яўляюцца трохі горш, чым яны былі б у сінтэтычным цесцю працуе з кэша.

5
дададзена

Чаму б не выкарыстоўваць stringstream? Я не ўпэўнены ў сваёй канкрэтнай службовай, але вы можаце вызначыць:

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

Вядома, вы павінны былі б

#include  
3
дададзена
Гэта кананічны C ++ шлях, але гэта на некалькі парадкаў больш павольна, чым палегчаны «наіўны» цыкл пераўтварэнні.
дададзена аўтар DarthGizka, крыніца

Рэалізацыя Пэдді з fast_atoi з'яўляецца хутчэй, чым <�моцны> atoi - без ценю сумневу - аднак гэта працуе толькі для цэлых лікаў без знака .

Below, I put evaluated version of Paddy's fast_atoi that also allows only unsigned integers but speeds conversion up even more by replacing costly operation * with +

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

Тут я паставіў <�моцны> поўная версія з <�моцны> 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;
} 
2
дададзена
Не ўпэўнены, што калі рашэнне крыху зруху на самай справе хутчэй, так як x86 ўсечанага множанне адна інструкцыі і НКА будзе ведаць, што верхнія біты не мае значэння.
дададзена аўтар Eric Pauley, крыніца

Вось паўната функцыі 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
дададзена
Я быў у стане выкарыстаць гэта, каб стварыць шаблон функцыі для розных цэлалікавых тыпаў і запусціць яго на AVR.
дададзена аўтар Caleb Reister, крыніца

Вось мая. Atoi гэта самы хуткі я мог прыдумаць. Я скампіляваў з MSVC 2010, каб можна было б аб'яднаць абодва шаблону. У MSVC 2010 года, калі я аб'яднаў шаблоны гэта зрабіў той выпадак, калі вы падасце центибар аргумент больш павольна.

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  
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 
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= 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<= 9 ; ++p)
      n = n*10 + ch; 
    return (fNeg) ? -n : n;
}
0
дададзена

Толькі канчатковы адказ з праверкі з вашым кампілятарам, вашыя рэальныя дадзеныя.

Нешта я хацеў бы паспрабаваць (нават калі ён выкарыстоўвае доступ да памяці, так што можа быць павольным, у залежнасці ад кэшавання) з'яўляецца

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 ...

калі t1 , t10 і гэтак далей статычна і пастаянны кампілятар не павінен баяцца ступеністасць і машынны код, згенераваны павінен быць цалкам прыстойным.

0
дададзена

хутчэй канвертаваць функцыі

множанне заўсёды павольней, што сума і зрух, такім чынам, змяніць шматкроць са зрухам

int fast_atoi( const char * str )
{
    int val = 0;
    while( *str ) {
        val = (val << 4) - (val << 2) - (val << 1) + (*str++ - '0');
    }
return val;
}
0
дададзена