Был бы рад, если бы кто-нибудь покритиковал эту маленькую программку.
Нужно было сконвертировать большой файл состоящий из double в файл состоящий из float. По идее эту програмку можно использовать для конвертации любых простых POD струкур. Первая версия этой программы открывала поток, читала по одному значению и сразу его писала в другой поток. Конвертация трёхгигового файла занимала:
$ time double2float tmp.double tmp.float
real 1m24.239s
user 1m9.987s
sys 0m8.628s
Для сравнения, копирование файла занимает столько:
$ time cp input.vel.double tmp.double
real 0m13.322s
user 0m0.521s
sys 0m10.723s[/code]
Эта версия с буффером отрабатывает заметно быстрее:
[code]
$ time double2float tmp.double tmp.float
real 0m9.680s
user 0m1.511s
sys 0m7.625s
Вот код:
#include <algorithm>
#include <cassert>
#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
template<typename UnaryFunction>
void buffered_binary_stream_transform(std::ifstream& input, std::ofstream& output, UnaryFunction op, size_t buffer_size_bytes)
{
typedef typename UnaryFunction::argument_type argument_type;
typedef typename UnaryFunction::result_type result_type;
const size_t argument_type_size = sizeof(argument_type);
assert(buffer_size_bytes >= argument_type_size);
const size_t result_type_size = sizeof(result_type);
const typename std::vector<argument_type>::size_type buffer_size_elements = buffer_size_bytes / argument_type_size; //might truncate actual buffer_size_bytes
const int bytes_to_read = argument_type_size * buffer_size_elements;
std::vector<argument_type> elements_to_transform(buffer_size_elements);
std::vector<result_type> transformed_elements(buffer_size_elements);
while(true) {
input.read(reinterpret_cast<char*>(&elements_to_transform[0]), bytes_to_read);
const std::streamsize bytes_read = input.gcount();
if (bytes_read == 0) break;
const std::streamsize elements_read = bytes_read / argument_type_size;
const std::streamsize bytes_to_write = result_type_size * elements_read;
std::transform(elements_to_transform.begin(),
elements_to_transform.begin() + elements_read,
transformed_elements.begin(),
op);
output.write(reinterpret_cast<const char*>(&transformed_elements[0]), bytes_to_write);
}
}
struct Double2FloatConverter : public std::unary_function<double, float>
{
result_type operator() (const argument_type& to_convert)
{
return static_cast<result_type>(to_convert);
}
};
int main (int args, char** argv)
{
if (! (args == 4 || args == 3 ) )
{
std::cerr << "Wrong number of args" << std::endl << "usage: " << argv[0]
<< " input_file output_file" << std::endl;
exit(EXIT_FAILURE);
}
std::string input_file_name(argv[1]);
std::ifstream input(input_file_name.c_str(), std::ios::binary);
if(!input)
{
std::cerr << "Can't open input file \"" << input_file_name << "\"" << std::endl;
exit(EXIT_FAILURE);
}
std::string outputFileName(argv[2]);
if (outputFileName == input_file_name)
{
std::cerr << "Can't write to the same file I am reading from\n";
exit(EXIT_FAILURE);
}
std::ofstream output(outputFileName.c_str(), std::ios::binary);
if(!output)
{
std::cerr << "Can't open output file \"" << input_file_name.c_str() << "\"" << std::endl;
exit(EXIT_FAILURE);
}
const int buffer_size = 4*1024*1024;
buffered_binary_stream_transform(input, output, Double2FloatConverter(), buffer_size);
}
Есть ли смысл передавать элементарные типы данных по константной ссылке, как это делается в Double2FloatConverter::operator() ?
Функтор наследует от unary_function и этого типа требует шаблон и я не знаю хорошо это или плохо. Выходит, что с этим требованием в buffered_binary_stream_transform нельзя использовать функтор, который не наследует от unary_function.
Здравствуйте, mezhaka, Вы писали:
M>Был бы рад, если бы кто-нибудь покритиковал эту маленькую программку.
M>Нужно было сконвертировать большой файл состоящий из double в файл состоящий из float.
Я бы просто смапил оба файла в память, благо размер входа и выхода известен, и написал бы алгоритм целиком в терминах памяти, причем каким-нть tbb::parallel_for, чтоб она в несколько потоков это делала по непересекающимся страницам памяти.
А работу с буферизацией файлов оставил бы целиком ядру.
Программа бы на пять строчек получилась
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, mezhaka, Вы писали:
M>>Был бы рад, если бы кто-нибудь покритиковал эту маленькую программку.
M>>Нужно было сконвертировать большой файл состоящий из double в файл состоящий из float.
J>Я бы просто смапил оба файла в память, благо размер входа и выхода известен, и написал бы алгоритм целиком в терминах памяти, причем каким-нть tbb::parallel_for, чтоб она в несколько потоков это делала по непересекающимся страницам памяти.
J>А работу с буферизацией файлов оставил бы целиком ядру.
J>Программа бы на пять строчек получилась
Класс, спасибо за совет.
А может что-нибудь порекоммендуете для memory maped файлов (в смысле библиотеку какую-то)? И какой они дают интерфейс потом? Т.е. будет ли это например как будто у меня два больших буфера или это будет какой-то поток из которого надо будет читать?
Работает ли tbb без интеловского компилятора?
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, mezhaka, Вы писали:
M>>>Был бы рад, если бы кто-нибудь покритиковал эту маленькую программку.
M>>>Нужно было сконвертировать большой файл состоящий из double в файл состоящий из float.
J>>Я бы просто смапил оба файла в память, благо размер входа и выхода известен, и написал бы алгоритм целиком в терминах памяти, причем каким-нть tbb::parallel_for, чтоб она в несколько потоков это делала по непересекающимся страницам памяти.
J>>А работу с буферизацией файлов оставил бы целиком ядру.
J>>Программа бы на пять строчек получилась
А>Класс, спасибо за совет.
А>А может что-нибудь порекоммендуете для memory maped файлов (в смысле библиотеку какую-то)? И какой они дают интерфейс потом? Т.е. будет ли это например как будто у меня два больших буфера или это будет какой-то поток из которого надо будет читать?
А>Работает ли tbb без интеловского компилятора?
есть еще OpenMP, работает на VC, gcc
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, jazzer, Вы писали:
А>Класс, спасибо за совет.
А>А может что-нибудь порекоммендуете для memory maped файлов (в смысле библиотеку какую-то)?
man mmap
http://opennet.ru/man.shtml?topic=mmap&category=2&russian=0
А>И какой они дают интерфейс потом?
А>Т.е. будет ли это например как будто у меня два больших буфера или это будет какой-то поток из которого надо будет читать?
Просто два больших буфера: каждый mmap вернет указатель, используй один как double* для чтения, а другой — как float* для записи:
const size_t n = ...;
double* pd = (double*)mmap( ..., n*sizeof(double), ... );
float* pf = (float*)mmap( ..., n*sizeof(double), ... );
for (size_t i = 0; i<n; i++)
pf[i] = pd[i];
munmap( pd, n*sizeof(double) );
munmap( pf, n*sizeof(float) );
Это весь код, если делать без tbb
А>Работает ли tbb без интеловского компилятора?
да
Здравствуйте, jazzer, Вы писали:
J>Просто два больших буфера: каждый mmap вернет указатель, используй один как double* для чтения, а другой — как float* для записи:
J>J>const size_t n = ...;
J>double* pd = (double*)mmap( ..., n*sizeof(double), ... );
J>float* pf = (float*)mmap( ..., n*sizeof(double), ... );
J>for (size_t i = 0; i<n; i++)
J> pf[i] = pd[i];
J>munmap( pd, n*sizeof(double) );
J>munmap( pf, n*sizeof(float) );
J>
J>Это весь код, если делать без tbb
Этот код не будет работать на 32 разрядных системах, если размер файла будет гигабайтным. Причина — исчерпание виртуальной памяти процесса. В это случае придется мапировать файл кусками. Т.е. код несколько усложнится.