Настройка
Здравствуйте, у меня есть код Fortran для чтения в данных двойной точности ASCII (пример файла данных в нижней части вопроса):
program ReadData
integer :: mx,my,mz
doubleprecision, allocatable, dimension(:,:,:) :: charge
! Open the file 'CHGCAR'
open(11,file='CHGCAR',status='old')
! Get the extent of the 3D system and allocate the 3D array
read(11,*)mx,my,mz
allocate(charge(mx,my,mz) )
! Bulk read the entire block of ASCII data for the system
read(11,*) charge
end program ReadData
и "эквивалентный" код С++:
#include <fstream>
#include <vector>
using std::ifstream;
using std::vector;
using std::ios;
int main(){
int mx, my, mz;
// Open the file 'CHGCAR'
ifstream InFile('CHGCAR', ios::in);
// Get the extent of the 3D system and allocate the 3D array
InFile >> mx >> my >> mz;
vector<vector<vector<double> > > charge(mx, vector<vector<double> >(my, vector<double>(mz)));
// Method 1: std::ifstream extraction operator to double
for (int i = 0; i < mx; ++i)
for (int j = 0; j < my; ++j)
for (int k = 0; k < mz; ++k)
InFile >> charge[i][j][k];
return 0;
}
Фортран, пиная @$$ и принимая имена
Обратите внимание, что строка
read(11,*) charge
выполняет ту же задачу, что и код С++:
for (int i = 0; i < mx; ++i)
for (int j = 0; j < my; ++j)
for (int k = 0; k < mz; ++k)
InFile >> charge[i][j][k];
где InFile
- объект if stream
(обратите внимание, что хотя итераторы в коде Fortran начинаются с 1, а не 0, диапазон один и тот же).
Однако, код Fortran работает намного быстрее, чем код на С++, я думаю, потому что Fortran делает что-то умное, как чтение/разбор файла в соответствии с диапазоном и формой (значения mx
, my
, mz
) за один раз, а затем просто указав charge
на память, на которую были прочитаны данные. Для сравнения, С++-код должен получить доступ к InFile
, а затем charge
(который обычно является большим) назад и вперед с каждой итерацией, в результате чего (я считаю) происходит много операций ввода-вывода и памяти.
Я читаю потенциально миллиарды значений (несколько гигабайт), поэтому я действительно хочу максимизировать производительность.
Мой вопрос:
Как я могу добиться производительности кода Fortran на С++?
Перемещение...
Вот намного быстрее (чем выше С++) реализация С++, где файл читается за один проход в массив char
, а затем charge
заселяется при анализе массива char
:
#include <fstream>
#include <vector>
#include <cstdlib>
using std::ifstream;
using std::vector;
using std::ios;
int main(){
int mx, my, mz;
// Open the file 'CHGCAR'
ifstream InFile('CHGCAR', ios::in);
// Get the extent of the 3D system and allocate the 3D array
InFile >> mx >> my >> mz;
vector<vector<vector<double> > > charge(mx, vector<vector<double> >(my, vector<double>(mz)));
// Method 2: big char array with strtok() and atof()
// Get file size
InFile.seekg(0, InFile.end);
int FileSize = InFile.tellg();
InFile.seekg(0, InFile.beg);
// Read in entire file to FileData
vector<char> FileData(FileSize);
InFile.read(FileData.data(), FileSize);
InFile.close();
/*
* Now simply parse through the char array, saving each
* value to its place in the array of charge density
*/
char* TmpCStr = strtok(FileData.data(), " \n");
// Gets TmpCStr to the first data value
for (int i = 0; i < 3 && TmpCStr != NULL; ++i)
TmpCStr = strtok(NULL, " \n");
for (int i = 0; i < Mz; ++i)
for (int j = 0; j < My; ++j)
for (int k = 0; k < Mx && TmpCStr != NULL; ++k){
Charge[i][j][k] = atof(TmpCStr);
TmpCStr = strtok(NULL, " \n");
}
return 0;
}
Опять же, это намного быстрее, чем простой >>
метод на основе операторов, но все же значительно медленнее, чем версия Fortran - не говоря уже о гораздо большем количестве кода.
Как повысить производительность?
Я уверен, что метод 2 - это путь, если я сам его реализую, но мне любопытно, как я могу повысить производительность, чтобы соответствовать коду Fortran. Типы вещей, которые я рассматриваю и в настоящее время исследую:
- Функции С++ 11 и С++ 14
- Оптимизированная библиотека C или С++ для выполнения этого типа
- Усовершенствования отдельных методов, используемых в методе 2
- библиотека токенизации, например, в С++ String Toolkit Library вместо
strtok()
- более эффективное преобразование
char
вdouble
чемatof()
- библиотека токенизации, например, в С++ String Toolkit Library вместо
Инструмент С++ String Toolkit
В частности, библиотека инструментов С++ String Toolkit примет FileData
и разделители " \n"
и предоставит мне объект токена строки (назовите его FileTokens
, тогда цикл triple for
будет выглядеть как
for (int k = 0; k < Mz; ++k)
for (int j = 0; j < My; ++j)
for (int i = 0; i < Mx; ++i)
Charge[k][j][i] = FileTokens.nextFloatToken();
Это немного упростило бы код, но есть дополнительная работа по копированию (по существу) содержимого FileData
в FileTokens
, что может привести к гибели любой выгоды от использования метода nextFloatToken()
(предположительно более эффективного, чем комбинация strtok()
/atof()
).
На странице С++ String Toolkit (StrTk) учебника по токенизатору (см. внизу вопроса) используется StrTk for_each_line()
, который похож на мое желаемое приложение. Однако разница между случаями заключается в том, что я не могу предположить, сколько данных будет отображаться в каждой строке входного файла, и я не знаю достаточно о StrTk, чтобы сказать, является ли это жизнеспособным решением.
NOT DUPLICATE
Тема быстрого чтения данных ASCII в массив или структуру появилась раньше, но я просмотрел следующие сообщения, и их решения были недостаточными:
- Самый быстрый способ чтения данных из большого количества файлов ASCII
- Как читать числа из файла ASCII (С++)
- Чтение числовых данных из текстового файла на С++
- Чтение файла и сохранение содержимого в массиве
- C/С++ Быстрое чтение большого файла данных ASCII в массив или структуру
- Чтение файла ASCII в матрицу на С++
- Как читать файл данных ASCII на С++
- Чтение файла и сохранение содержимого в массиве
- Чтение данных в столбцах из файла (С++)
- Самый быстрый способ прочитать файл .txt
- Как работает быстрый ввод/вывод в C/С++, используя регистры, шестнадцатеричное число и т.д.
- чтение файла в массив структуры
Примеры данных
Вот пример файла данных, который я импортирую. Данные ASCII разделяются пробелами и разрывами строк, как показано ниже:
5 3 3
0.23080516813E+04 0.22712439791E+04 0.21616898980E+04 0.19829996749E+04 0.17438686650E+04
0.14601734127E+04 0.11551623512E+04 0.85678544224E+03 0.59238325489E+03 0.38232265554E+03
0.23514479113E+03 0.14651943589E+03 0.10252743482E+03 0.85927499703E+02 0.86525872161E+02
0.10141182750E+03 0.13113419142E+03 0.18057147781E+03 0.25973252462E+03 0.38303754418E+03
0.57142097675E+03 0.85963728360E+03 0.12548019843E+04 0.17106124085E+04 0.21415379433E+04
0.24687336309E+04 0.26588012477E+04 0.27189091499E+04 0.26588012477E+04 0.24687336309E+04
0.21415379433E+04 0.17106124085E+04 0.12548019843E+04 0.85963728360E+03 0.57142097675E+03
0.38303754418E+03 0.25973252462E+03 0.18057147781E+03 0.13113419142E+03 0.10141182750E+03
0.86525872161E+02 0.85927499703E+02 0.10252743482E+03 0.14651943589E+03 0.23514479113E+03
Пример StrTk
Вот пример StrTk, упомянутый выше. В сценарии анализируется файл данных, содержащий информацию для 3D-сетки:
входные данные:
5
+1.0,+1.0,+1.0
-1.0,+1.0,-1.0
-1.0,-1.0,+1.0
+1.0,-1.0,-1.0
+0.0,+0.0,+0.0
4
0,1,4
1,2,4
2,3,4
3,1,4
код:
struct point
{
double x,y,z;
};
struct triangle
{
std::size_t i0,i1,i2;
};
int main()
{
std::string mesh_file = "mesh.txt";
std::ifstream stream(mesh_file.c_str());
std::string s;
// Process points section
std::deque<point> points;
point p;
std::size_t point_count = 0;
strtk::parse_line(stream," ",point_count);
strtk::for_each_line_n(stream,
point_count,
[&points,&p](const std::string& line)
{
if (strtk::parse(line,",",p.x,p.y,p.z))
points.push_back(p);
});
// Process triangles section
std::deque<triangle> triangles;
triangle t;
std::size_t triangle_count = 0;
strtk::parse_line(stream," ",triangle_count);
strtk::for_each_line_n(stream,
triangle_count,
[&triangles,&t](const std::string& line)
{
if (strtk::parse(line,",",t.i0,t.i1,t.i2))
triangles.push_back(t);
});
return 0;
}