любой знает, как преобразовать огромный массив char в float, очень большой массив, производительность лучше, чем у atof / strtod / sscanf - PullRequest
2 голосов
/ 09 января 2010

Я получил массив char, огромный массив char p [n], прочитанный из txt вроде.

//1.txt
194.919 -241.808 234.896
195.569 -246.179 234.482
194.919 -241.808 234.896
...

foo (char * p, float x, float y, float z) {

}

Я пытался использовать atof, strtod, но они занимают в реальном времени, когда массив слишком велик, потому что они будут вызывать strlen (). и sscanf тоже очень медленный ....

I отлаживает в коде и находит, что и atof (), и strtod вызывают strlen () в visual studio , мы можем проверить код crt.

strtod() call:
        answer = _fltin2( &answerstruct, ptr, (int)strlen(ptr), 0, 0, _loc_update.GetLocaleT());


atof() call:
        return( *(double *)&(_fltin2( &fltstruct, nptr, (int)strlen(nptr), 0, 0, _loc_update.GetLocaleT())->dval) );

Я также пытаюсь использовать strtok, но мы не должны изменять какие-либо данные в 1.txt.

так что у любого есть лучший способ преобразовать все это в плавающие x, y, z.

Visual studio 2008 + WIN7

Ответы [ 10 ]

1 голос
/ 09 января 2010

Если вы можете сделать дополнительные предположения относительно формата значений с плавающей запятой, их синтаксический анализ может повысить производительность.

Пример кода для разбора ' ' или '\n' -разделенных значений без показателей степени и без проверки входных данных:

float parsef(const char **str)
{
    const char *cc = *str;

    _Bool neg = (*cc == '-');
    if(neg) ++cc;

    float value = 0, e = 1;

    for(; *cc != '.'; ++cc)
    {
        if(*cc == ' ' || *cc == '\n' || !*cc)
        {
            *str = cc;
            return neg ? -value : value;
        }

        value *= 10;
        value += *cc - '0';
    }

    for(++cc;; ++cc)
    {
        if(*cc == ' ' || *cc == '\n' || !*cc)
        {
            *str = cc;
            return neg ? -value : value;
        }

        e /= 10;
        value += (*cc - '0') * e;
    }
}

Пример кода:

const char *str = "42 -15.4\n23.001";
do printf("%f\n", parsef(&str));
while(*str++);
1 голос
/ 09 января 2010

Хорошо, как насчет того, чтобы сделать токенизацию самостоятельно и затем вызвать strtod.

Я думаю, что-то вроде этого:

char *current = ...;  // initialited to the head of your character array
while (*current != '\0')
{
    char buffer[64];
    unsigned int idx = 0;

    // copy over current number
    while (*current != '\0' && !isspace(*current))
    {
        buffer[idx++] = *current++;
    }
    buffer[idx] = '\0';

    // move forward to next number
    while (*current != '\0' && isspace(*current))
    {
        current++;
    }

    // use strtod to convert buffer   
}

Некоторые проблемы с токенизацией очень просты. Он будет работать с тем форматом, который вы опубликовали, но если формат меняется (используется другая строка: для разделения чисел), он не будет работать.

Другая проблема заключается в том, что код предполагает, что все числа имеют <64 символов. Если они длиннее, вы получите переполнение буфера. </p>

Кроме того, копирование во временный буфер добавит некоторые накладные расходы (но, надеюсь, меньше, чем накладные расходы на постоянное выполнение strlen для всего буфера). Я знаю, что вы сказали, что вы не можете изменить исходный буфер, но можете ли вы сделать временное изменение (то есть буфер может измениться, если вы вернете его в исходное состояние, прежде чем вернуться):

char *current = ...;  // initialited to the head of your character array
while (*current != '\0')
{
    char *next_sep = current;
    while (*next_sep != '\0' && !isspace(*next_sep))
    {
        next_sep++;
    }

    // save the separator before overwriting it
    char tmp = *next_sep;
    *next_sep = '\0';

    // use strtod on current

   // Restore the separator.
   *next_sep = tmp;

    current = next_sep;

    // move forward to next number
    while (*current != '\0' && isspace(*current))
    {
        current++;
    }
}

Этот метод означает, что копирование не нужно, и не нужно беспокоиться о переполнении буфера. Вам нужно временно изменить буфер; надеюсь, что это

1 голос
/ 09 января 2010

Проверьте этот код.

Его можно дополнительно оптимизировать, если не требуется поддержка научного представления, знака «+» или ведущих вкладок.

Он не использует strlen или любую другую стандартную библиотечную строковую подпрограмму.

// convert floating-point value in string represention to it's numerical value
// return false if NaN
// F is float/double
// T is char or wchar_t
// '1234.567' -> 1234.567
template <class F, class T> inline bool StrToDouble(const T* pczSrc, F& f)
{
    f= 0;

    if (!pczSrc)
        return false;

    while ((32 == *pczSrc) || (9 == *pczSrc))
        pczSrc++;

    bool bNegative= (_T('-') == *pczSrc);

    if ( (_T('-') == *pczSrc) || (_T('+') == *pczSrc) )
        pczSrc++;

    if ( (*pczSrc < _T('0')) || (*pczSrc > _T('9')) )
        return false;

    // todo: return false if number of digits is too large

    while ( (*pczSrc >= _T('0')) && (*pczSrc<=_T('9')) )
    {
        f= f*10. + (*pczSrc-_T('0'));
        pczSrc++;
    }

    if (_T('.') == *pczSrc)
    {
        pczSrc++;

        double e= 0.;
        double g= 1.;

        while ( (*pczSrc >= _T('0')) && (*pczSrc<=_T('9')) )
        {
            e= e*10. + (*pczSrc-_T('0'));
            g= g*10.                    ;
            pczSrc++;
        }

        f+= e/g;
    }

    if ( (_T('e') == *pczSrc) || (_T('E') == *pczSrc) ) // exponent, such in 7.32e-2
    {
        pczSrc++;

        bool bNegativeExp= (_T('-') == *pczSrc);

        if ( (_T('-') == *pczSrc) || (_T('+') == *pczSrc) )
            pczSrc++;

        int nExp= 0;
        while ( (*pczSrc >= _T('0')) && (*pczSrc <= _T('9')) )
        {
            nExp= nExp*10 + (*pczSrc-_T('0'));
            pczSrc++;
        }

        if (bNegativeExp)
            nExp= -nExp;

        // todo: return false if exponent / number of digits of exponent is too large

        f*= pow(10., nExp);
    }

    if (bNegative)
        f= -f;

    return true;
}
0 голосов
/ 10 января 2010

Я сомневаюсь, что strlen стоит вам дорого.

Если вы можете воспользоваться тем, что ваши числа попадают в относительно ограниченный диапазон, то я предлагаю вам разобрать его самостоятельно, выполнив как можно меньше вычислений, например:

#define DIGIT(c) ((c)>='0' && (c)<='9')

BOOL parseNum(char* *p0, float *f){
  char* p = *p0;
  int n = 0, frac = 1;
  BOOL bNeg = FALSE;
  while(*p == ' ') p++;
  if (*p == '-'){p++; bNeg = TRUE;}
  if (!(DIGIT(*p) || *p=='.')) return FALSE;
  while(DIGIT(*p)){
    n = n * 10 + (*p++ - '0');
  }
  if (*p == '.'){
    p++;
    while(DIGIT(*p)){
      n = n * 10 + (*p++ - '0');
      frac *= 10;
    }
  }
  *f = (float)n/(float)frac;
  if (bNeg) *f = -*f;
  *p0 = p;
  return TRUE;
}
0 голосов
/ 09 января 2010

Как насчет чего-то вроде:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static float frac[] =
{
    0.000,
    0.001,
    0.002,
    ...               // fill in
    0.997,
    0.998,
    0.999,
};

static float exp[] =
{
    1e-38,
    1e-37,
    1e-36,
    ...               // fill in
    1e+36,
    1e+37,
    1e+38,
};

float cvt(char* p)
{
    char* d = strchr(p, '.');   // Find the decimal point.
    char* e = strchr(p, 'e');   // Find the exponent.
    if (e == NULL)
        e = strchr(p, 'E');

    float num = atoi(p);
    if (num > 0) {
        num += frac[atoi(d + 1)];
    } else {
        num -= frac[atoi(d + 1)];
    }
    if (e)
        num *= exp[atoi(e)];
    return num;
}

int main()
{
    char line[100];
    while(gets(line)) {
        printf("in %s, out %g\n", line, cvt(line));
    }
}

Должно быть хорошо до трех значащих цифр.


Редактировать: остерегайтесь больших мантисс.
Отредактируйте снова: и отрицательные показатели. : - (
0 голосов
/ 09 января 2010

Как уже говорили другие, я не думаю, что вы будете делать намного лучше, чем вызовы стандартной библиотеки. Они были вокруг в течение долгого времени и довольно высоко оптимизированы (ну, они должны быть, по крайней мере, в хороших реализациях).

Тем не менее, есть некоторые вещи, которые мне не понятны. Вы читаете весь файл в память, а затем конвертируете массив в другой массив? Если это так, вы можете проверить, что в системе, в которой вы работаете, достаточно памяти для подкачки. Если вы делаете это, возможно ли будет просто преобразовывать одну строку за раз, когда вы читаете их с диска, вместо того, чтобы хранить их?

Вы могли бы рассмотреть многопоточность вашей программы. Один поток для чтения и буферизации строк с диска, и n потоков для обработки строк. В журнале Dr. Dobb's Journal была опубликована замечательная реализация блокировки без единого считывателя / единственного записывающего устройства , которую вы можете использовать. Я использовал это в похожем приложении. Каждый из моих рабочих потоков имеет входную очередь, а затем поток чтения считывает данные с диска и помещает их в эти очереди в стиле циклического перебора.

0 голосов
/ 09 января 2010

Почему вы думаете, что strtod использует strlen? Я никогда не реализовывал их, но не представляю, зачем им нужно знать длину входной строки. Это не имело бы значения для них. Я бы использовал strtod согласно ответу Джейсона. Вот для чего это.

И да, если у вас очень большой объем текста, для конвертации потребуется некоторое время. Так оно и есть.

0 голосов
/ 09 января 2010

Я не вижу причин, по которым strod() должен звонить strlen(). Конечно, это может , но ничто в его спецификации не требует этого, и я был бы удивлен, если бы это произошло. И я бы сказал, что strtod() примерно так же быстро, как и вы, если не считать того, что вы сами написали какие-то вещи, специфичные для процессора FPU.

0 голосов
/ 09 января 2010

Использование strtod. Это почти наверняка не вызывает strlen. Зачем ему нужно знать длину ввода? Он просто пробегает начальные пробелы, затем использует столько символов, сколько возможно, что имеет смысл для литерала с плавающей запятой, а затем возвращает указатель сразу после него. Вы можете увидеть пример реализации Возможно, вы используете его неоптимально? Вот пример того, как использовать strtod:

#include <stdio.h>
#include <stdlib.h>
int main() {
    char *p = "1.txt 194.919 -241.808 234.896 195.569 -246.179 234.482 194.919 -241.808 234.896";
    char *end = p;
    char *q;
    double d;
    while(*end++ != ' '); // move past "1.txt"
    do {
        q = end; 
        d = strtod(q, &end);
        printf("%g\n", d);
    } while(*end != '\0');
}

Это выводит:

194.919
-241.808
234.896
195.569
-246.179
234.482
194.919
-241.808
234.896

на моей машине.

0 голосов
/ 09 января 2010

Пока вы не используете особо плохую стандартную библиотеку (в наши дни это невозможно, они все хороши), это невозможно сделать быстрее, чем atof.

...