C ++: Как прочитать в программу большое количество данных из форматированных текстовых файлов?

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

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

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

  1. Файл Simulation.config
 % Dimension: 2D или 3DN_Dimension = 2  % Количество фаз жидкости N_Phases = 1% Плотность жидкости (кг/м3) Density_Phase1 = 1000,0 Density_Phase2 = 1,0% Кинематическая вязкость (м ^ 2/с) Visacity_Phase1 = 1e-6Visidity_Phase2 = 1,48e-05 ...  
  1. Файл Geometry.mesh
 % Dimension: 2D или 3DN_Dimension = 2% Points (index: x, y  , z) N_Points = 100x0 y0x1 y1 ... x99 y99% Faces (линии в 2D: P1-> p2) N_Faces = 550 23 4 ...% Cells (полигоны в 2D: тип ячейки и точки по часовой стрелке).  6: треугольник;  9: quadN_Cells = 209 0 1 6 209 1 3 4 7 ...% Boundary Faces (index) Left_Faces = 40123Bottom_Faces = 6789101112 ...  

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


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

Или вы можете взглянуть на это:

GMSH_IO

OpenMesh:

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

1


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

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

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

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

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

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

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

  • Если вы пишете и читать двоичные данные в системах с различным расположением памяти для определенных значений (например, с прямым порядком байтов или с прямым порядком байтов) — вам необходимо перетасовать байты в памяти. См. Также этот вопрос SO о порядке байтов.
  • Возможно, не стоит максимально оптимизировать скорость чтения. Вам следует учитывать закон Амдала и оптимизировать его только до такой степени, чтобы он больше не занимал значительную часть общего времени выполнения. Лучше потерять несколько процентов времени выполнения, но получить удобные для чтения файлы данных, которые можно использовать с другими инструментами, поддерживающими установленный формат.

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

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

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

  #include //требуется для ввода-вывода файла # include //требуется для  console IO # include //требуется для создания хеш-таблицы для хранения идентификаторов int main () {std :: unordered_map  identifiers;  std :: string configPath;  std :: cout > configPath;  std :: ifstream config (configPath); //открыть указанный файл, если (! config.is_open ())//ошибка, если не удалось открыть файл {std :: cerr  

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

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


Предполагая:

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

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

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

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

  #include  #include  #include  #include  namespace AST {using Identifier = std :: string; //Имя переменной.  using Value = std :: variant ; //Значение переменной.  using Assignment = std :: pair ; //Идентификатор = Значение.  using Root = std :: vector ; //Целый файл: все назначения.}  

Описание грамматики:

  #include  #include  анализатор пространства имен {using пространство имен x3; //Строка: Идентификатор = значение const x3 :: rule  assignment = "assignment"; //Строка: комментарий const x3 :: rule  comment = "comment"; //Имя переменной const x3 :: rule  identifier = "identifier"; //Файл const x3 :: rule  root = "root"; //Любое допустимое значение в конфигурационном файле const x3 :: rule  value = "value"; //Семантическое действие auto emplace_back = [] (const auto & ctx) {x3 :: _ val (ctx). emplace_back (x3 :: _ attr (ctx));  }; //Грамматика const auto assignment_def = skip (blank) [идентификатор >> '=' >> значение];  const auto comment_def = '%' >> опустить [* (char_ - eol)];  const auto identifier_def = lexeme [alpha >> + (alnum | char _ ('_'))];  const auto root_def = * ((комментарий | назначение [emplace_back]) >> eol) >> опустить [* пусто];  const auto value_def = double_ |  int_;  BOOST_SPIRIT_DEFINE (root, assignment, comment, identifier, value);}  
 //Принимает итераторы в строке/потоке ...//Возвращает AST  input.template  AST :: Root parse (IteratorType & begin, const IteratorType & end) {AST :: Root result;  bool parsed = x3 :: parse (начало, конец, Parser :: root, результат);  if (! parsed || begin! = end) {throw std :: domain_error ("Парсер получил неверный ввод.");  } return result;}  

Интерактивная демонстрация

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

      //100 X_n Y_nconst auto point_def = lit ("N_Points") >> ':' >> int_ >> eol >> * (double_ >> double_ >> eol)  


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

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

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



Использование файлов текстовых данных

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

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

У вас могут быть поля, которые обрабатывались как числа или отметки времени в таблице, а затем использовать ALTER TABLE ... REPLACE COLUMNS , чтобы переключить их на строки, или обеспечить регресс.

Создание текстовых таблиц

Вы можете создавать таблицы с определенными символами-разделителями для импорта текстовых файлов в знакомых форматах, таких как CSV, TSV или pipe- разделены предложением FIELDS TERMINATED BY , которому предшествует предложение ROW FORMAT DELIMITED . Например:
  СОЗДАТЬ ТАБЛИЦУ tsv (id INT, s STRING, n INT, t TIMESTAMP, b BOOLEAN) СТРОКА ФОРМАТИРОВАТЬ ДЕЛИМИРОВАННЫЕ ПОЛЯ, ЗАКРЫВАЕМЫЕ ' t';  

Вы можете указать символ-разделитель ' 0' , чтобы использовать ASCII 0 ( nul ) для текстовых таблиц.

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

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

Выполните инструкцию DESCRIBE FORMATTED table_name , чтобы увидеть подробности того, как каждая таблица представлена ​​внутри Impala.

Замечания по поводу сложных типов: Хотя вы можете создавать таблицы в этом формате файлов, используя сложные типы ( ARRAY , STRUCT и MAP ), в настоящее время Impala не может запрашивать эти типы в текстовых таблицах.

Файлы данных для текстовых таблиц

Когда Impala запрашивает таблицу с данными в текстовом формате, она обращается ко всем файлам данных в каталог данных для этой таблицы, за некоторыми исключениями:

  • Impala игнорирует любые скрытые файлы, то есть файлы, имена которых начинаются с точки или символа подчеркивания.

  • Запросы Impala игнорируют файлы с расширениями, обычно используемыми для временных рабочих файлов инструментами Hadoop. Любые файлы с расширениями .tmp или .copying не считаются частью таблицы Impala. При сопоставлении суффиксов регистр не учитывается, поэтому, например, Impala игнорирует суффиксы .copying и .COPYING .

  • Impala использует суффиксы, чтобы распознавать, когда файлы текстовых данных являются сжатым текстом. Чтобы Impala могла распознать сжатые текстовые файлы, они должны иметь соответствующее расширение файла, соответствующее кодеку сжатия, либо .bz2 , .gz , .snappy или .zst , .deflate . Расширения могут быть в верхнем или нижнем регистре.

  • В противном случае имена файлов не имеют значения. Когда вы помещаете файлы в каталог HDFS с помощью заданий ETL, или указываете Impala существующему каталогу HDFS с помощью оператора CREATE EXTERNAL TABLE , или перемещаете файлы данных под внешний контроль с помощью LOAD DATA , Impala сохраняет исходные имена файлов.

Оператор INSERT ... SELECT создает по одному файлу данных из каждого узла, который обрабатывает часть SELECT заявления. Оператор INSERT ... VALUES создает отдельный файл данных для каждого оператора; Поскольку Impala более эффективно запрашивает небольшое количество огромных файлов, чем большое количество маленьких файлов, синтаксис INSERT ... VALUES не рекомендуется для загрузки значительного объема данных. Если вы обнаружите, что таблица неэффективна из-за слишком большого количества небольших файлов данных, реорганизуйте данные в несколько больших файлов, выполнив INSERT ... SELECT , чтобы перенести данные в новую таблицу. .

Не заключайте строковые значения в кавычки в создаваемых вами текстовых файлах данных. Если вам нужно включить символ-разделитель внутри значения поля, например, чтобы поместить строковое значение с запятой внутри файла данных в формате CSV, укажите escape-символ в операторе CREATE TABLE с помощью предложение ESCAPED BY и вставьте этот символ непосредственно перед любыми символами-разделителями, которые необходимо экранировать.

Специальные значения в файлах текстовых данных:

  • Impala распознает буквальные строки inf для бесконечности и nan для Not a Number , для FLOAT и DOUBLE столбцы.

  • Impala распознает буквальную строку N для представления NULL . При использовании Sqoop укажите параметры - null-non-string и - null-string , чтобы гарантировать, что все NULL значения правильно представлены в выходных файлах Sqoop. N необходимо экранировать, как в примере ниже:

      - null-string '\ N' --null-non  -string '\ N'  
  • По умолчанию Sqoop записывает значения NULL , используя строку null , что вызывает ошибку преобразования, когда такие строки оцениваются Impala. (Обходной путь для существующих таблиц и файлов данных — изменить свойства таблицы с помощью ALTER TABLE name SET TBLPROPERTIES ("serialization.null.format" = "null") .)

  • Impala может опционально пропускать произвольное количество строк заголовков из текстовых файлов ввода в HDFS на основе skip.header.line.count значение в поле TBLPROPERTIES метаданных таблицы.

Загрузка данных в текстовые таблицы

Чтобы загрузить существующий текстовый файл в текстовую таблицу Impala, используйте LOAD DATA и укажите путь к файлу в HDFS. Этот файл перемещается в соответствующий каталог данных Impala.

Чтобы загрузить несколько существующих текстовых файлов в текстовую таблицу Impala, используйте оператор LOAD DATA и укажите путь HDFS к каталогу, содержащему файлы. Все не скрытые файлы перемещаются в соответствующий каталог данных Impala.

Используйте оператор DESCRIBE FORMATTED , чтобы увидеть каталог HDFS, в котором хранятся файлы данных, затем используйте команды Linux, такие как hdfs dfs -ls hdfs_directory и hdfs dfs -cat hdfs_file для отображения содержимого текстового файла, созданного Impala.

При создании текстового файла для использования с текстовой таблицей Impala укажите N для представления значения NULL .

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

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

Вы также можете использовать ручные операции HDFS, такие как hdfs dfs -put или hdfs dfs -cp , чтобы поместить файлы данных в каталог данных для таблицы Impala. Когда вы копируете или перемещаете новые файлы данных в каталог HDFS для таблицы Impala, выполните инструкцию REFRESH table_name в impala-shell перед тем, как выдать следующий запрос к эту таблицу, чтобы Impala распознала вновь добавленные файлы.

Производительность запросов для текстовых таблиц

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

Для часто запрашиваемых данных вы можете загрузить исходные текстовые файлы данных в одну таблицу Impala, а затем использовать оператор INSERT для передачи данных в другую таблицу, которая использует формат файла Parquet. Данные преобразуются автоматически по мере их сохранения в целевой таблице.

Для более компактных данных. рассмотрите возможность использования текстовых данных, сжатых в форматах gzip, bzip2 или Snappy. Однако эти сжатые форматы не являются разделяемыми , поэтому у Impala меньше возможностей распараллеливать запросы к ним. У вас также есть возможность преобразовать данные в Parquet с помощью оператора INSERT ... SELECT , чтобы скопировать исходные данные в таблицу Parquet..

Используя bzip2, deflate, gzip, Snappy-Compressed или текстовые файлы zstd

Impala поддерживает использование файлов текстовых данных, которые используют сжатие bzip2, deflate, gzip, Snappy или zstd. Эти типы сжатия предназначены в первую очередь для удобства в рамках существующего конвейера ETL, а не для максимальной производительности. Хотя для чтения сжатого текста требуется меньше операций ввода-вывода, чем для эквивалентного несжатого текста, файлы, сжатые этими кодеками, не являются разделяемыми и, следовательно, не могут в полной мере использовать возможности параллельных запросов Impala. Impala может читать сжатые текстовые файлы, написанные Hive.

При обработке каждого файла, сжатого с помощью Snappy, узел, выполняющий эту работу, считывает весь файл в память, а затем распаковывает его. Следовательно, у узла должно быть достаточно памяти для хранения как сжатых, так и несжатых данных из текстового файла. Объем памяти, необходимый для хранения несжатых данных, трудно оценить заранее, что может вызвать проблемы в системах с низкими ограничениями памяти или с включенным управлением ресурсами. Эти накладные расходы на память уменьшаются для текстовых файлов, сжатых с помощью bzip2, deflate, gzip и zstd. Сжатые данные распаковываются по мере чтения, а не все сразу.

Чтобы создать таблицу для хранения сжатого текста, создайте текстовую таблицу без специальных параметров сжатия. При необходимости укажите разделитель и escape-символ с помощью предложения ROW FORMAT .

Поскольку Impala может запрашивать сжатые текстовые файлы, но в настоящее время не может их записывать, создавайте сжатые текстовые файлы вне Impala и используйте оператор LOAD DATA , вручную команды HDFS для перемещения их в соответствующий каталог данных Impala. (Или вы можете использовать CREATE EXTERNAL TABLE и указать атрибут LOCATION на каталог, содержащий существующие сжатые текстовые файлы.)

Оцените статью
clickpad.ru
Добавить комментарий