Чтение целых чисел из отформатированного файла с отображением памяти

У меня есть отображение в памяти большого форматированного (текстового) файла, содержащего одно целое число в строке, например:

123
345
34324
3232
...

Итак, у меня есть указатель на память в первом байте, а также указатель на память в последнем байте. Я пытаюсь как можно быстрее прочитать все эти целые числа в массиве. Изначально я создал специализированный класс std :: streambuf для работы с std :: istream для чтения из этой памяти, но это кажется относительно медленным.

Есть ли у вас какие-либо предложения по эффективному синтаксическому анализу строки типа «1231232 \ r \ n123123 \ r \ n123 \ r \ n1231 \ r \ n2387897 ...» в массиве {1231232,123123,1231,231,2387897 ,. ..}?

Количество целых чисел в файле заранее не известно.


person Community    schedule 16.11.2010    source источник


Ответы (4)


std::vector<int> array;
char * p = ...; // start of memory mapped block
while ( not end of memory block )
{
    array.push_back(static_cast<int>(strtol(p, &p, 10)));
    while (not end of memory block && !isdigit(*p))
        ++p;
}

Этот код немного небезопасен, поскольку нет гарантии, что strtol остановится в конце отображаемого в память блока, но это начало. Должен работать очень быстро, даже с добавленной дополнительной проверкой.

person Mark Ransom    schedule 16.11.2010
comment
Одна из оптимизаций может заключаться в замене isdigit на (* p & 0xF0). - person ronag; 16.11.2010
comment
Вы забыли array.reserve(...). Потеря производительности может быть серьезной. - person Yippie-Ki-Yay; 17.11.2010
comment
@ HardCoder1986, std :: vector будет экспоненциально увеличивать массив, поэтому потеря производительности составляет только log (n). В вопросе прямо сказано, что количество целых чисел неизвестно. Я согласен, что разумное предположение может помочь. - person Mark Ransom; 17.11.2010
comment
@ronag, твое выражение лица не то же самое. Я сомневаюсь, что isdigit все равно станет узким местом. - person Mark Ransom; 17.11.2010
comment
Более того, вызов isdigit с (возможно, подписанным) char, отличным от EOF, вызывает неопределенное поведение. - person Roland Illig; 17.11.2010
comment
@Mark Ransom. Верно в обоих случаях. Мое предложение проверяет разрыв строки, нуль и возврат носителя, которые являются единственными другими символами, которые существуют, кроме цифр. - person ronag; 17.11.2010
comment
Это решение было самым быстрым на моей машине, замена isdigit на оптимизацию ronag не дала заметной разницы. - person ; 17.11.2010

Для меня это было действительно интересной задачей - узнать немного больше о C ++.

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

#include <ctype.h>
#include <limits.h>
#include <stdio.h>

#include <iterator>
#include <vector>
#include <string>

static void
die(const char *reason)
{
  fprintf(stderr, "aborted (%s)\n", reason);
  exit(EXIT_FAILURE);
}

template <class BytePtr>
static bool
read_uint(BytePtr *begin_ref, BytePtr end, unsigned int *out)
{
  const unsigned int MAX_DIV = UINT_MAX / 10;
  const unsigned int MAX_MOD = UINT_MAX % 10;

  BytePtr begin = *begin_ref;
  unsigned int n = 0;

  while (begin != end && '0' <= *begin && *begin <= '9') {
    unsigned digit = *begin - '0';
    if (n > MAX_DIV || (n == MAX_DIV && digit > MAX_MOD))
      die("unsigned overflow");
    n = 10 * n + digit;
    begin++;
  }

  if (begin == *begin_ref)
    return false;

  *begin_ref = begin;
  *out = n;
  return true;
}

template <class BytePtr, class IntConsumer>
void
parse_ints(BytePtr begin, BytePtr end, IntConsumer out)
{
  while (true) {
    while (begin != end && *begin == (unsigned char) *begin && isspace(*begin))
      begin++;
    if (begin == end)
      return;

    bool negative = *begin == '-';
    if (negative) {
      begin++;
      if (begin == end)
        die("minus at end of input");
    }

    unsigned int un;
    if (!read_uint(&begin, end, &un))
      die("no number found");

    if (!negative && un > INT_MAX)
      die("too large positive");
    if (negative && un > -((unsigned int)INT_MIN))
      die("too small negative");

    int n = negative ? -un : un;
    *out++ = n;
  }
}

static void
print(int x)
{
  printf("%d\n", x);
}

int
main()
{
  std::vector<int> result;
  std::string input("2147483647 -2147483648 0 00000 1 2 32767 4 -17 6");

  parse_ints(input.begin(), input.end(), back_inserter(result));

  std::for_each(result.begin(), result.end(), print);
  return 0;
}

Я очень старался не вызывать какое-либо неопределенное поведение, которое может стать довольно сложным при преобразовании чисел без знака в числа со знаком или вызове isspace для неизвестного типа данных.

person Roland Illig    schedule 16.11.2010

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

open memory mapped file to output int buffer

declare small stack buffer of 20 chars
while not end of char array
  while current char not  line feed
    copy chars to stack buffer
    null terminate the buffer two chars back
    copy results of int buffer output buffer
    increment the output buffer pointer
  end while  
end while

Хотя здесь не используется библиотека, преимущество заключается в минимизации использования памяти для файлов с отображением памяти, поэтому временные буферы ограничены стеком и буфером, используемым внутри atoi. Буфер вывода можно выбросить или оставить в файле по мере необходимости.

person Preet Sangha    schedule 16.11.2010

ПРИМЕЧАНИЕ. Этот ответ редактировался несколько раз.

Считывает память построчно (на основе ссылка и ссылка).

class line 
{
   std::string data;
public:
   friend std::istream &operator>>(std::istream &is, line &l) 
   {
      std::getline(is, l.data);
      return is;
   }
   operator std::string() { return data; }    
};

std::streambuf osrb;
setg(ptr, ptr, ptrs + size-1);
std::istream istr(&osrb);

std::vector<int> ints;

std::istream_iterator<line> begin(istr);
std::istream_iterator<line> end;
std::transform(begin, end, std::back_inserter(ints), &boost::lexical_cast<int, std::string>);
person ronag    schedule 16.11.2010
comment
Похоже, что это не так, если он собирается скопировать файл с отображением памяти в память ... - person Steve M; 16.11.2010
comment
Что ж, я думаю, что отредактированный ответ в порядке ... по крайней мере, это была честная попытка. - person ronag; 16.11.2010
comment
Это почти то же решение, что и я со специализированными streambuf и istream, которые были относительно медленными. Однако это элегантный код. - person ; 17.11.2010