C ++: Как прочитать много данных из форматированных текстовых файлов в программу? - PullRequest
10 голосов
/ 28 июня 2019

Я пишу решатель CFD для конкретных проблем с жидкостью.До сих пор сетка генерируется каждый раз, когда выполняется моделирование, и при изменении геометрии и свойств жидкости программу необходимо перекомпилировать.

Для задач небольшого размера с небольшим количеством ячеек она работает просто отлично.Но для случаев с более чем 1 миллионом ячеек, и свойства жидкости нужно менять очень часто, это довольно неэффективно.

Очевидно, что нам нужно хранить данные настройки симуляции в файле конфигурации, а информацию о геометрии в отформатированном виде.файл сетки.

  1. Файл Simulation.config
% Dimension: 2D or 3D
N_Dimension= 2
% Number of fluid phases
N_Phases=  1
% Fluid density (kg/m3)
Density_Phase1= 1000.0
Density_Phase2= 1.0
% Kinematic viscosity (m^2/s)
Viscosity_Phase1=  1e-6
Viscosity_Phase2=  1.48e-05
...
Файл Geometry.mesh
% Dimension: 2D or 3D
N_Dimension= 2
% Points (index: x, y, z)
N_Points: 100
x0 y0
x1 y1
...
x99 y99
% Faces (Lines in 2D: P1->p2)
N_Faces: 55
0 2
3 4
...
% Cells (polygons in 2D: Cell-Type and Points clock-wise). 6: triangle; 9: quad
N_Cells: 20
9 0 1 6 20
9 1 3 4 7
...
% Boundary Faces (index)
Left_Faces: 4
0
1
2
3
Bottom_Faces: 6
7
8
9
10
11
12
...

Легко записывать информацию о конфигурации и сетке в форматированные текстовые файлы.Проблема в том, как мы можем эффективно читать эти данные в программу?Интересно, есть ли какая-нибудь простая в использовании библиотека c ++ для этой работы.

Ответы [ 5 ]

5 голосов
/ 30 июня 2019

В качестве решения для первой итерации, чтобы просто получить что-то приемлемое - возьмите предложение @ JosmarBarbosa и используйте установленный формат для ваших данных - который также, вероятно, имеет бесплатные библиотеки с открытым исходным кодом, которые вы можете использовать , Одним из примеров является OpenMesh , разработанный в RWTH Aachen. Поддерживает:

  • Представление произвольных многоугольных (общий случай) и чисто треугольных сеток (обеспечивающих более эффективные специализированные алгоритмы)
  • Явное представление вершин, половин, ребер и граней.
  • Быстрый доступ по соседству, особенно по соседству с одним кольцом (см. Ниже).
  • [Настройка]

Но если вам действительно нужно ускорить чтение данных меша, подумайте о следующем:

  1. Отделение метаданных ограниченного размера от больших данных ячеек неограниченного размера;
  2. Поместите метаданные ограниченного размера в отдельный файл и читайте их по своему усмотрению, это не имеет значения.
  3. Упорядочить данные сетки в виде нескольких массивов элементов фиксированного размера или структур фиксированного размера (например, ячеек, граней, точек и т. Д.).
  4. Сохраняйте каждый из массивов данных фиксированной ширины в своем собственном файле - без использования потоковых отдельных значений в любом месте: просто читайте или записывайте массив как есть, напрямую. Вот пример того, как будет выглядеть чтение . Вы узнаете соответствующий размер чтения, посмотрев на размер файла или метаданные.

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

самая быстрая техника чтения файла в память?

Примечания / предостережения:

  • Если вы пишете и читаете двоичные данные в системах с разным расположением в памяти определенных значений (например, little-endian против big-endian) - вам нужно будет переставлять байты в памяти. См. Также этот ТАК вопрос о порядке байтов.
  • Возможно, не стоит максимально оптимизировать скорость чтения. Вам следует учитывать закон Амдала и оптимизировать его только до такой степени, чтобы он больше не составлял значительную долю вашего общего времени выполнения. Лучше потерять несколько процентов времени выполнения, но получить читаемые человеком файлы данных, которые можно использовать с другими инструментами, поддерживающими установленный формат.
5 голосов
/ 28 июня 2019

Хорошо, хорошо. Вы можете реализовать свой собственный API на основе коллекции конечных элементов, словаря, некоторого Regex и, в конце концов, применить практику ставок в соответствии с каким-то международным стандартом.

Или вы можете посмотретьна этом:

GMSH_IO

OpenMesh:

Я только что использовал OpenMesh в моей последней реализации для проекта C ++ OpenGL.

4 голосов
/ 02 июля 2019

Предполагая, что:

  • вы не хотите использовать существующий формат для сеток
  • вы не хотите использовать общий текстовый формат (json, yml, ...)
  • вам не нужен двоичный формат (даже если вы хотите что-то эффективное)

Короче говоря, вам действительно нужен собственный текстовый формат.

Вы можете использовать любой генератор синтаксических анализаторов , чтобы начать. Хотя вы, вероятно, могли бы проанализировать ваш конфигурационный файл, так как он использует только регулярные выражения, они могут быть действительно ограничены в долгосрочной перспективе. Поэтому я предложу синтаксический анализатор без контекста , сгенерированный с помощью Boost spirit :: x3 .

AST

Абстрактное синтаксическое дерево будет содержать конечный результат синтаксического анализатора.

#include <string>
#include <utility>
#include <vector>
#include <variant>

namespace AST {
    using Identifier = std::string; // Variable name.
    using Value = std::variant<int,double>; // Variable value.
    using Assignment = std::pair<Identifier,Value>; // Identifier = Value.
    using Root = std::vector<Assignment>; // Whole file: all assignments.
}

Parser

Грамматическое описание:

#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/home/x3.hpp>

namespace Parser {
    using namespace x3;

    // Line: Identifier = value
    const x3::rule<class assignment, AST::Assignment> assignment = "assignment";
    // Line: comment
    const x3::rule<class comment> comment = "comment";
    // Variable name
    const x3::rule<class identifier, AST::Identifier> identifier = "identifier";
    // File
    const x3::rule<class root, AST::Root> root = "root";
    // Any valid value in the config file
    const x3::rule<class value, AST::Value> value = "value";

    // Semantic action
    auto emplace_back = [](const auto& ctx) {
        x3::_val(ctx).emplace_back(x3::_attr(ctx));
    };

    // Grammar
    const auto assignment_def = skip(blank)[identifier >> '=' >> value];
    const auto comment_def = '%' >> omit[*(char_ - eol)];
    const auto identifier_def = lexeme[alpha >> +(alnum | char_('_'))];
    const auto root_def = *((comment | assignment[emplace_back]) >> eol) >> omit[*blank];
    const auto value_def = double_ | int_;

    BOOST_SPIRIT_DEFINE(root, assignment, comment, identifier, value);
}

Использование

// Takes iterators on string/stream...
// Returns the AST of the input.
template<typename IteratorType>
AST::Root parse(IteratorType& begin, const IteratorType& end) {
    AST::Root result;
    bool parsed = x3::parse(begin, end, Parser::root, result);
    if (!parsed || begin != end) {
        throw std::domain_error("Parser received an invalid input.");
    }
    return result;
}

Живая демоверсия

Эволюции

  • Чтобы изменить место, где разрешены пробелы, добавьте / переместите x3::skip(blank) в выражениях xxxx_def.
  • В настоящее время файл должен заканчиваться символом новой строки. Переписав выражение root_def, можно это исправить.
  • Вы наверняка захотите узнать, почему не удалось выполнить синтаксический анализ неверных входных данных. См. руководство по обработке ошибок .
  • Вы только в нескольких правилах разбираете более сложные вещи:

    //                                               100              X_n        Y_n
    const auto point_def = lit("N_Points") >> ':' >> int_ >> eol >> *(double_ >> double_ >> eol)
    
4 голосов
/ 30 июня 2019

В следующем ответе я полагаю:

  1. что если первый символ строки %, то он должен игнорироваться как комментарий.
  2. Любая другая строка структурирована точно следующим образом: identifier= value.

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

#include <fstream>          //required for file IO
#include <iostream>         //required for console IO
#include <unordered_map>    //required for creating a hashtable to store the identifiers

int main()
{
    std::unordered_map<std::string, double> identifiers;

    std::string configPath;

    std::cout << "Enter config path: ";
    std::cin >> configPath;

    std::ifstream config(configPath);   //open the specified file
    if (!config.is_open())              //error if failed to open file
    {
        std::cerr << "Cannot open config file!";
        return -1;
    }

    std::string line;
    while (std::getline(config, line))  //read each line of the file
    {
        if (line[0] == '%') //line is a comment
            continue;

        std::size_t identifierLenght = 0;
        while (line[identifierLenght] != '=')
            ++identifierLenght;
        identifiers.emplace(
            line.substr(0, identifierLenght),
            std::stod(line.substr(identifierLenght + 2))
        ); //add entry to identifiers
    }

    for (const auto& entry : identifiers)
        std::cout << entry.first << " = " << entry.second << '\n';
}

После прочтения идентификаторов вы, конечно, можете делать с ними все, что вам нужно. Я просто печатаю их в качестве примера, чтобы показать, как их получить. Для получения дополнительной информации о std::unordered_map смотрите здесь . Для получения очень полезной информации о создании парсеров посмотрите здесь .

Если вы хотите ускорить ввод в программу, вставьте следующую строку в начале main: std::ios_base::sync_with_stdio(false). Это десинхронизирует C ++ IO с C IO и, как следствие, делает это быстрее.

2 голосов
/ 30 июня 2019

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

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

https://developers.google.com/protocol-buffers/

...