загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 04:24
Оценка:
Здравствуйте !

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

Спасибо.
Re: загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 04:43
Оценка:
Здравствуйте, Вы писали:

А>Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер, а также минимизируя кол-во распределений памяти аллокатором std::string.

std::string read_str( const std::string& filename )
{
    std::string result;
    std::ifstream file( filename.c_str() );
    int filesize;

    if ( !file.is_open() ) return result;
    file.seekg(0, std::ios_base::end);
    filesize = (int)file.tellg();
    if (filesize <= 0) return result;
    file.seekg(0, std::ios_base::beg);
    result.resize(filesize);
    file.read( (char*)result.data(), filesize );
    return result;
}


Исправлено форматирование. Пожалуйста, не забывайте пользоваться тегами [c] ... [/c], [code] ... [/code] и т.п. для выделения фрагментов кода. -- ПК.
Re[2]: загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 04:59
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Вы писали:


А>>Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер, а также минимизируя кол-во распределений памяти аллокатором std::string.

А>std::string read_str( const std::string& filename )
А>{
А>    std::string result;
А>    std::ifstream file( filename.c_str() );
А>    int filesize;

А>    if ( !file.is_open() ) return result;
А>    file.seekg(0, std::ios_base::end);
А>    filesize = (int)file.tellg();
А>    if (filesize <= 0) return result;
А>    file.seekg(0, std::ios_base::beg);
А>    result.resize(filesize);
А>    file.read( (char*)result.data(), filesize );
А>    return result;
А>}


Но string::resize заполняет память нулем. А это есть излишняя операция, поскольку строка сразу же заполняется файлом. Операция загрузки файла вызыватся в цикле, поэтому хотелось бы не выполнять не нужных действий.
Re: загрузить файл в std::string не используя буфер
От: Odi$$ey Россия http://malgarr.blogspot.com/
Дата: 03.06.04 05:06
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер,


можно и сразу, но это как раз будет неоптимально, см. здесь
Автор:
Дата: 22.08.03
Re[3]: загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 05:17
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Но string::resize заполняет память нулем. А это есть излишняя операция, поскольку строка сразу же заполняется файлом. Операция загрузки файла вызыватся в цикле, поэтому хотелось бы не выполнять не нужных действий.


В таком случае тебе придется вибирать из двух зол:
— либо заполнение нулями при resize;
— либо использовать конструктор string( buffer, buffer_size ),
при этом тебе придется выделять память под buffer, и копировать туда данные из файла.

Других вариантов у тебя нет.
Я бы на твоем месте остановился на первом варианте, или искал бы другие места для оптимизации.

Например, если эта операция выполняется в цикле,
то надо писать общую процедуру для загрузки нескольких строк, используя общий буфер.
Или вообще отказаться от STL строки, раз тебе так важна производительность.
Re[2]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 05:42
Оценка: 1 (1) +3
Здравствуйте, <Аноним>, Вы писали:

А>std::string read_str( const std::string& filename )

А>{
А>...

Так как нет гарантии, что данные внутри буфера std::string хранятся в одном непрерывном куске памяти, то этот вариант работать не обязан. Лучше сделать так:
  std::string text;
  std::ifstream file;
  std::size_t filesize;
  
  file.open("d:\\link.txt", std::ios::in | std::ios::binary);
  
  file.seekg(0, std::ios_base::end);
  filesize = (int)file.tellg();
  file.seekg(0, std::ios_base::beg);
  
  text.reserve(filesize);
  
  std::copy
  (
    std::istreambuf_iterator<char>(file), 
    std::istreambuf_iterator<char>(),
    std::back_inserter(text)
  );
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[3]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 06:10
Оценка:
Здравствуйте, ArtDenis, Вы писали:

AD> filesize = (int)file.tellg();


Этот (int) нужно убрать.

PS: Copy-Paste must die!
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[3]: загрузить файл в std::string не используя буфер
От: folk Россия  
Дата: 03.06.04 09:27
Оценка: +1
Здравствуйте, ArtDenis, Вы писали:

AD>Здравствуйте, <Аноним>, Вы писали:


А>>std::string read_str( const std::string& filename )

А>>{
А>>...

AD>Так как нет гарантии, что данные внутри буфера std::string хранятся в одном непрерывном куске памяти, то этот вариант работать не обязан. Лучше сделать так:

AD>
AD>  std::string text;
AD>  std::ifstream file;
AD>  std::size_t filesize;
  
AD>  file.open("d:\\link.txt", std::ios::in | std::ios::binary);
  
AD>  file.seekg(0, std::ios_base::end);
AD>  filesize = (int)file.tellg();
AD>  file.seekg(0, std::ios_base::beg);
  
AD>  text.reserve(filesize);
  
AD>  std::copy
AD>  (
AD>    std::istreambuf_iterator<char>(file), 
AD>    std::istreambuf_iterator<char>(),
AD>    std::back_inserter(text)
AD>  );

AD>


Имхо, если последний copy заменить на insert, то может быть немного ускоримся:
  text.insert
  (
    text.begin(), 
    std::istreambuf_iterator<char>(file), 
    std::istreambuf_iterator<char>()
  );
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[4]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 09:40
Оценка:
Здравствуйте, folk, Вы писали:

F>Имхо, если последний copy заменить на insert, то может быть немного ускоримся:

F>
F>  text.insert
F>  (
F>    text.begin(), 
F>    std::istreambuf_iterator<char>(file), 
F>    std::istreambuf_iterator<char>()
F>  );
F>


Правильно. В этом случае можно убрать выяснение размера файла и reserve.
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[5]: загрузить файл в std::string не используя буфер
От: folk Россия  
Дата: 03.06.04 09:55
Оценка:
Здравствуйте, ArtDenis, Вы писали:

AD>Здравствуйте, folk, Вы писали:


F>>Имхо, если последний copy заменить на insert, то может быть немного ускоримся:

F>>
F>>  text.insert
F>>  (
F>>    text.begin(), 
F>>    std::istreambuf_iterator<char>(file), 
F>>    std::istreambuf_iterator<char>()
F>>  );
F>>


AD>Правильно. В этом случае можно убрать выяснение размера файла и reserve.


Но если убрать reserve, то возможны переаллокации, а ведь хотелось обойтись без них
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[6]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 10:21
Оценка:
Здравствуйте, folk, Вы писали:

F>Но если убрать reserve, то возможны переаллокации, а ведь хотелось обойтись без них


Вероятность того, что insert в какой-то реализации STL будет вставлять данные по одному символу, по мере надобности перераспределяя память, ничтожно мала. Скорее всего, память будет перераспределена один раз до фактической вставки символов. Хотя, для перестраховки можно сделать и reserve.

А что на этот счёт говорит стандарт?
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[3]: загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 10:30
Оценка:
Здравствуйте, ArtDenis, Вы писали:

Это все правильно, но человека интересовал вопрос:

"Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер,
а также минимизируя кол-во распределений памяти аллокатором std::string."

поэтому такой способ считывания ему вряд ли подойдет:

>>std::copy(

>> std::istreambuf_iterator<char>(file),
>> std::istreambuf_iterator<char>(),
>> std::back_inserter(text)
>> );

скорость выполнения такого кода будет далека от оптимальной.
Re[4]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 10:35
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Это все правильно, но человека интересовал вопрос:

А> "Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер,
А> а также минимизируя кол-во распределений памяти аллокатором std::string."

А где ты видишь промежуточный буфер и большое количество перераспределений памяти?
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[5]: загрузить файл в std::string не используя буфер
От: Аноним  
Дата: 03.06.04 10:45
Оценка:
Здравствуйте, ArtDenis, Вы писали:

AD>Здравствуйте, <Аноним>, Вы писали:


А>>Это все правильно, но человека интересовал вопрос:

А>> "Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер,
А>> а также минимизируя кол-во распределений памяти аллокатором std::string."

AD>А где ты видишь промежуточный буфер и большое количество перераспределений памяти?


Дело не в промежуточном буфере и не в перераспределении памяти,
дело в том что методы "copy" или "insert" производят посимвольное копирование,
вот это и является не оптимальным.

посмотри ссылку на которую указал Odi$$ey http://www.rsdn.ru/Forum/Message.aspx?mid=361479.
Re[6]: загрузить файл в std::string не используя буфер
От: folk Россия  
Дата: 03.06.04 12:14
Оценка:
Здравствуйте, Аноним, Вы писали:

А>>>Это все правильно, но человека интересовал вопрос:

А>>> "Как оптимальнее загрузить текстовый файл в строку std::string не используя промежуточный буфер,
А>>> а также минимизируя кол-во распределений памяти аллокатором std::string."

AD>>А где ты видишь промежуточный буфер и большое количество перераспределений памяти?


А>Дело не в промежуточном буфере и не в перераспределении памяти,

А>дело в том что методы "copy" или "insert" производят посимвольное копирование,
А>вот это и является не оптимальным.

Вижу только критику, а позитивная программа будет?

А>посмотри ссылку на которую указал Odi$$ey http://www.rsdn.ru/Forum/Message.aspx?mid=361479.


Не понимаю. Что конкретно там нужно увидеть?
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[6]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 12:32
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Дело не в промежуточном буфере и не в перераспределении памяти,

А>дело в том что методы "copy" или "insert" производят посимвольное копирование,
А>вот это и является не оптимальным.
В случае с std::string выбирать не приходиться. Это тебе не std::vector<char>.

А>посмотри ссылку на которую указал Odi$$ey http://www.rsdn.ru/Forum/Message.aspx?mid=361479.

Там как раз речь идёт о std::vector<char>
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[7]: загрузить файл в std::string не используя буфер
От: folk Россия  
Дата: 03.06.04 12:58
Оценка:
Здравствуйте, ArtDenis, Вы писали:


F>>Но если убрать reserve, то возможны переаллокации, а ведь хотелось обойтись без них


AD>Вероятность того, что insert в какой-то реализации STL будет вставлять данные по одному символу, по мере надобности перераспределяя память, ничтожно мала. Скорее всего, память будет перераспределена один раз до фактической вставки символов. Хотя, для перестраховки можно сделать и reserve.


А как узнать длину последовательности из пары input iterator'ов? А поскольку длина неизвестна, то и неизвестно сколько нужно выделить памяти, соответственно можем получить множественные переаллокации.

Сейчас померял все три варианта на VC7.1/Dinkumware, cамым быстрым оказался reserve/insert, затем reserve/copy, затем просто insert. Но разница ничтожная.

AD>А что на этот счёт говорит стандарт?


Не заглядывал, имхо и так все ясно.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[8]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 03.06.04 13:22
Оценка:
Здравствуйте, folk, Вы писали:

F>А как узнать длину последовательности из пары input iterator'ов? А поскольку длина неизвестна, то и неизвестно сколько нужно выделить памяти, соответственно можем получить множественные переаллокации.


Правильно. Я забыл, что итераторы потоков — это не random access итераторы.
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[7]: загрузить файл в std::string не используя буфер
От: _Vladimir_ Россия  
Дата: 04.06.04 06:49
Оценка: 4 (1)
Здравствуйте, ArtDenis, Вы писали:

AD>Здравствуйте, <Аноним>, Вы писали:


А>>Дело не в промежуточном буфере и не в перераспределении памяти,

А>>дело в том что методы "copy" или "insert" производят посимвольное копирование,
А>>вот это и является не оптимальным.
AD>В случае с std::string выбирать не приходиться. Это тебе не std::vector<char>.

А>>посмотри ссылку на которую указал Odi$$ey http://www.rsdn.ru/Forum/Message.aspx?mid=361479.

AD>Там как раз речь идёт о std::vector<char>

Выбор всегда есть, в этом и заключается прелесть программирования!
(Предыдущие сообщения написаны мной, я решил зарегистрироваться)

В сообщении на которое указал Odi$$ey, надо было обратить внимание на время выполнения тестов,
они ясно показывают что использование методов "copy" или "insert" является не оптимальным вариантом.

Я написал свой тест, он показывает что ситуация с string аналогичная vector<char>.

В своем тесте я написал несколько вариантов загрузки файла в строку.
null — пустышка, открывает файл и вычислет его размер (для чистоты эксперимента);
system — использует функции Window для работы с файлом;
system shared buffer — то же что и system, только использует общий буфер;
остальные функции работают с файлом через stl:
buffer — считывает сторку через буфер;
string.resize — использует прямую запись в данные string (опасный вариант!);
string.insert и std::copy — это ваш вариант.

Результаты теста:

test file "D:/temp/test/test1.txt", size 320, count 100000
null — 2023 ms
system — 2053 ms
system shared buffer — 2043 ms
buffer — 5367 ms
string.resize — 4637 ms
string.insert — 5568 ms
std::copy — 5238 ms

test file "D:/temp/test/test2.txt", size 19047, count 10000
null — 200 ms
system — 1031 ms
system shared buffer — 1032 ms
buffer — 3084 ms
string.resize — 3145 ms
string.insert — 10004 ms
std::copy — 8292 ms

test file "D:/temp/test/test3.txt", size 750330, count 1000
null — 20 ms
system — 10595 ms
system shared buffer — 9885 ms
buffer — 15252 ms
string.resize — 14440 ms
string.insert — 40589 ms
std::copy — 33868 ms


Программа собрана на C++Builder 6, с включенной оптимизацией.

Текст программы:

#include <string>
#include <fstream>
#include <algorithm>
#include <streambuf>
#include <iostream>

#include <windows.h>

class t_buffer
{
public:
    void* data;
    int size;

    inline t_buffer( int s = 0 ) { size = s; data = (s > 0) ? malloc(s) : 0; }
    inline ~t_buffer() { size = 0; if (data) { free(data); data = 0; } }

    inline void grow( int s ) { if (size < s) data = realloc(data, size = s); }
};

using namespace std;

int file_size( const string& filename )
{
    ifstream file( filename.c_str(), ios::in | ios::binary );

    file.seekg(0, ios_base::end);
    return file.tellg();
}

//------------------------------------------------------------------------------

string read_str_NULL( const string& filename )
{
    string result;
    ifstream file( filename.c_str(), ios::in | ios::binary );
    size_t filesize;

    file.seekg(0, ios_base::end);
    filesize = file.tellg();
    if (filesize == 0) return result;
    file.seekg(0, ios_base::beg);
    return result;
}

string read_str_SYSTEM( const string& filename )
{
    HANDLE fh;
    string result;
    DWORD filesize;

    fh = ::CreateFile( filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0 );
    if (fh == INVALID_HANDLE_VALUE) return result;
    filesize = ::GetFileSize(fh, 0);
    if (filesize > 0)
    {
        DWORD readsize = 0;
        t_buffer buffer(filesize);

        if ( ::ReadFile(fh, buffer.data, filesize, &readsize, 0) != 0 )
            result = string((char*)buffer.data, readsize);
    }
    ::CloseHandle(fh);
    return result;
}

string read_str_SYSTEM_SHARED_BUFFER( const string& filename )
{
    static t_buffer buffer;

    HANDLE fh;
    string result;
    DWORD filesize;

    fh = ::CreateFile( filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0 );
    if (fh == INVALID_HANDLE_VALUE) return result;
    filesize = ::GetFileSize(fh, 0);
    if (filesize > 0)
    {
        DWORD readsize = 0;

        buffer.grow(filesize);
        if ( ::ReadFile(fh, buffer.data, filesize, &readsize, 0) != 0 )
            result = string((char*)buffer.data, readsize);
    }
    ::CloseHandle(fh);
    return result;
}

string read_str_BUFFER( const string& filename )
{
    ifstream file( filename.c_str(), ios::in | ios::binary );
    size_t filesize;

    file.seekg(0, ios_base::end);
    filesize = file.tellg();
    if (filesize == 0) return string();
    file.seekg(0, ios_base::beg);

    t_buffer buffer(filesize);

    file.read((char*)buffer.data, filesize);
    string result((char*)buffer.data, filesize);
    return result;
}

string read_str_RESIZE( const string& filename )
{
    string result;
    ifstream file( filename.c_str(), ios::in | ios::binary );
    size_t filesize;

    file.seekg(0, ios_base::end);
    filesize = file.tellg();
    if (filesize == 0) return result;
    file.seekg(0, ios_base::beg);
    result.resize(filesize);
    file.read( (char*)result.data(), filesize );
    return result;
}

string read_str_INSERT( const string& filename )
{
    string text;
    ifstream file;
    size_t filesize;

    file.open( filename.c_str(), ios::in | ios::binary );

    file.seekg(0, ios_base::end);
    filesize = file.tellg();
    file.seekg(0, ios_base::beg);

    text.reserve(filesize);

    text.insert( text.begin(), istreambuf_iterator<char>(file), istreambuf_iterator<char>() );
    return text;
}

string read_str_COPY( const string& filename )
{
    string text;
    ifstream file;
    size_t filesize;

    file.open( filename.c_str(), ios::in | ios::binary );

    file.seekg(0, ios_base::end);
    filesize = file.tellg();
    file.seekg(0, ios_base::beg);

    text.reserve(filesize);

    copy( istreambuf_iterator<char>(file),
        istreambuf_iterator<char>(), std::back_inserter(text) );
    return text;
}

//------------------------------------------------------------------------------

typedef string (*t_read_method)( const string& );

struct t_method_item
{
    const char* name;
    t_read_method method;
};

static const t_method_item c_methods[] =
{
    { "null", read_str_NULL },
    { "system", read_str_SYSTEM },
    { "system shared buffer", read_str_SYSTEM_SHARED_BUFFER },
    { "buffer", read_str_BUFFER },
    { "string.resize", read_str_RESIZE },
    { "string.insert", read_str_INSERT },
    { "std::copy", read_str_COPY },
    { 0, 0 }
};

void test_methods( int count, const string& filename )
{
    const t_method_item* i;
    int c;
    unsigned int time;

    std::cout << std::endl << "test file \"" << filename << "\", size " << file_size(filename) << ", count " << count << std::endl;
    for ( i = c_methods; i->name; i++ )
    {
        time = ::GetTickCount();
        for ( c = count; c > 0; c-- )
            i->method(filename);
        time = ::GetTickCount() - time;
        std::cout << i->name << " - " << time << " ms" << std::endl;
    }
}

void main()
{
    char t;

    test_methods(100000, "D:/temp/test/test1.txt"); // 320 b
    test_methods( 10000, "D:/temp/test/test2.txt"); // 18,6 Kb
    test_methods(  1000, "D:/temp/test/test3.txt"); // 732 Kb

    std::cin >> t;
}
Re[8]: загрузить файл в std::string не используя буфер
От: ArtDenis Россия  
Дата: 04.06.04 07:50
Оценка:
Здравствуйте, _Vladimir_, Вы писали:

_V_>Я написал свой тест, он показывает что ситуация с string аналогичная vector<char>.

_V_>В своем тесте я написал несколько вариантов загрузки файла в строку.
_V_>null — пустышка, открывает файл и вычислет его размер (для чистоты эксперимента);
_V_>system — использует функции Window для работы с файлом;
_V_>system shared buffer — то же что и system, только использует общий буфер;
_V_>остальные функции работают с файлом через stl:
_V_>buffer — считывает сторку через буфер;
_V_>string.resize — использует прямую запись в данные string (опасный вариант!);
_V_>string.insert и std::copy — это ваш вариант.

IMHO, std::fstream — не самый быстрый вариант. Например, если писать под MS Windows, то можно сделать класс-обёртку над файлами, проецируемыми в память. К тому-же, если ты не собираешься изменять данные, которые считываешь из файла, то можно сделать обращение к этому классу обёртке как к std::string
... << Rsdn@Home 1.1.4 beta 1 >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.