从非常大的文件中解析每四行的最有效方法

时间:2015-10-17 18:42:33

标签: c++ performance c++11 boost

我有一个格式如下的文件:

1: some_basic_info_in_this_line
2: LOTS_OF_INFO_IN_THIS_LINE_HUNDREDS_OF_CHARS
3: some_basic_info_in_this_line
4: LOTS_OF_INFO_IN_THIS_LINE_HUNDREDS_OF_CHARS
...

这种格式重复数万次,文件最多可达50 GiB +。我需要一种有效的方法来处理这种格式的第2行。我愿意使用C,C ++ 11 STL或者提升。我已经查看了有关SO上的文件流的各种其他问题,但我觉得我的情况是独特的,因为文件很大,只需要每四行中就有一行。

从我读过的内容映射文件似乎是最有效的,但是映射50+ GB文件会占用大多数计算机RAM(你可以假设这个应用程序将被"平均"用户 - 比如4-8 GiB RAM)。此外,我只需要一次处理其中一行。以下是我目前的做法(是的,我知道这不是有效的,这就是我重新设计它的原因):

std::string GL::getRead(ifstream& input)
{
    std::string str;
    std::string toss;
    if (input.good())
    {
        getline(input, toss);
        getline(input, str);
        getline(input, toss);
        getline(input, toss);
    }
    return str;
}

将mmap打破成块可以解决我的问题吗?反正我是否只能利用4行中的1行?谢谢你的帮助。

3 个答案:

答案 0 :(得分:5)

使用ignore代替getline

std::string GL::getRead(ifstream& input)
{
    std::string str;
    if (!input.fail())
    {
        input.ignore(LARGE_NUMBER, '\n');
        getline(input, str);
        input.ignore(LARGE_NUMBER, '\n');
        input.ignore(LARGE_NUMBER, '\n');
    }
    return str;
}

LARGE_NUMBER可能是std::numeric_limits<std::streamsize>::max()如果您没有充分理由拥有较小的号码(想想DOS攻击)

  

提示考虑通过引用传递str。通过每次读取相同的字符串,您可以避免大量分配,这通常是程序运行缓慢的第一个原因。

     

提示考虑使用备忘录映射文件(Boost Iostreams,Boost Interpocess或mmap(1)

答案 1 :(得分:2)

内存映射文件不会将其加载到RAM中。它占用了进程的虚拟地址空间,但不占用物理RAM。 mmap系统调用将在32位系统上失败,因为4GiB的虚拟地址空间不足以容纳50GiB文件。在64位系统上,它需要几微秒。 (没有磁盘读取,因为文件已经打开,因此文件元数据已经加载。)

只有您实际读取的页面才会从磁盘加载,并且只要操作系统想要回收某些内存,就可以再次取消映射页面。 (因为如果你以后再次阅读它们,操作系统可以从磁盘重新加载。就像将它们交换到交换空间/页面文件一样,但不必写,因为磁盘上已有一个干净的副本。)

内存映射可让您的进程读取操作系统的页面缓存页面,而不是通过read系统调用来复制它们。

Have a look at wikipedia了解更多信息。

答案 2 :(得分:0)

这是我能提出的最有效的解决方案,它与平台无关。我想到了所有的用户,现在我假设每个人都有64位机器,如果他们使用4 + GiB文件大小。如果这种情况发生变化,我将不得不修改该类以将“块”数据处理到单独的mmap区域。

#include <string>
#include <boost/iostreams/device/mapped_file.hpp>

//////////////////////////////////////////////////////////////////////////////////////////
/// @class LineParser
///
/// @brief Class to efficiently parse a file and take the second line out of every 4 lines
///
/// This class uses memory-mapped io to efficiently extract and return sequences from a
/// file
//////////////////////////////////////////////////////////////////////////////////////////
class LineParser
{
private:
    boost::iostreams::mapped_file mmap; ///< Object for memory mapped file
    const char* curr;                   ///< Current position of the file
    const char* end;                    ///< End position of the file

public:
    //////////////////////////////////////////////////////////////////////////////////////
    /// @fn valid
    ///
    /// Indicates whether the parser is in a valid state or not
    ///
    /// @return Boolean indicating if the parser is open and in a valid state
    ///
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///       many times in only a few spots    
    //////////////////////////////////////////////////////////////////////////////////////
    inline bool valid(void)
    {
        return (curr && end && (curr < end) && (mmap.is_open()));
    }

    //////////////////////////////////////////////////////////////////////////////////////
    /// @fn peek
    ///
    /// Obtains the next sequence string - if it exists - but maintains parsers state
    ///
    /// @return Next sequence available in the file. Emptry string returned if none 
    ///         exist
    ///
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///       many times in only a few spots
    //////////////////////////////////////////////////////////////////////////////////////
    inline std::string peek(void)
    {
        const char* save = curr;
        std::string ret;

        getRead(ret);

        curr = save;
        return ret;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    /// @fn getRead
    ///
    /// Sets container to the current read being processed
    ///
    /// @param container String container to place current sequence into
    ///
    /// @return Boolean indicating if getting a new read was successful
    ///
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///       many times in only a few spots
    //////////////////////////////////////////////////////////////////////////////////////
    inline bool getRead(std::string& container)
    {
        if (valid() == false)
        {
            return false;
        }

        curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1;
        const char* index = static_cast<const char*>(memchr(curr, '\n', end-curr));
        container = std::string(curr, index - curr);
        curr = index + 1;
        curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1;
        curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1;

        return true;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    /// @fn LineParser
    ///
    /// Constructor to initialize memory mapped file and set index values
    //////////////////////////////////////////////////////////////////////////////////////
    LineParser(const std::string& filepath) 
        : mmap(filepath, boost::iostreams::mapped_file::readonly)
    {
        if (mmap.is_open())
        {
            curr = mmap.const_data();
            end = curr + mmap.size();
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////
    /// @fn ~LineParser
    ///
    /// Default destructor
    //////////////////////////////////////////////////////////////////////////////////////
    ~LineParser(void) = default;
};

请注意,这个类并不完美,可能无法很好地处理文件格式的偏差,但在完美文件格式的假设下,它可以很好地工作。