【问题标题】:Reading integers from a memory mapped formatted file从内存映射格式化文件中读取整数
【发布时间】:2010-11-16 20:03:48
【问题描述】:

我在内存中映射了一个大格式(文本)文件,每行包含一个整数,如下所示:

123
345
34324
3232
...

所以,我在第一个字节有一个指向内存的指针,在最后一个字节也有一个指向内存的指针。我正在尝试尽快将所有这些整数读入一个数组。最初我创建了一个专门的 std::streambuf 类来使用 std::istream 从该内存中读取,但它似乎相对较慢。

您对如何有效地将“1231232\r\n123123\r\n123\r\n1231\r\n2387897...”之类的字符串解析为数组 {1231232,123123,1231,231, 2387897,...} ?

事先不知道文件中的整数个数。

【问题讨论】:

    标签: c++ windows mmap streambuf memory-mapping


    【解决方案1】:

    这对我来说是一项非常有趣的任务,可以让我更多地了解 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 时会变得非常棘手。

    【讨论】:

      【解决方案2】:
      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 会在内存映射块的末尾停止,但这是一个开始。即使添加了额外的检查,也应该会非常快。

      【讨论】:

      • 一种优化可能是将 isdigit 替换为 (*p & 0xF0)。
      • 你忘记了array.reserve(...)。这样做的性能损失可能很严重。
      • @HardCoder1986,std::vector 将以指数方式增长数组,因此性能损失仅为 log(n)。该问题明确表示整数的数量是未知的。我同意做出合理的猜测会有所帮助。
      • @ronag,你的表达方式不太一样。我怀疑isdigit 无论如何都会成为瓶颈。
      • 此外,使用char 而非EOF 调用isdigit 会调用未定义的行为。
      【解决方案3】:

      由于这是内存映射,因此将字符简单地复制到堆栈数组并将 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
      

      虽然这不使用 a 库,但它的优点是可以最大限度地减少内存映射文件的内存使用量,因此临时缓冲区仅限于堆栈之一和 atoi 内部使用的堆栈。输出缓冲区可以根据需要丢弃或保存到文件中。

      【讨论】:

        【解决方案4】:

        注意:此答案已被编辑多次。

        逐行读取内存(基于linklink)。

        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>);
        

        【讨论】:

        • 如果他要将内存映射文件复制到内存中,这似乎有点失败......
        • 好吧,我认为编辑后的答案还可以……至少这是一次诚实的尝试。
        • 这与我使用相对较慢的专用 streambuf 和 istream 所做的解决方案几乎相同。这是优雅的代码。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-06
        • 1970-01-01
        • 2012-03-25
        • 2012-07-17
        相关资源
        最近更新 更多