Истинно асинхронный файловый ввод-вывод в C ++ - PullRequest
2 голосов
/ 09 января 2020

У меня супер быстрый привод M.2. Как быстро это? Это не имеет значения, потому что я все равно не могу использовать эту скорость. Вот почему я задаю этот вопрос.

У меня есть приложение, которое требует очень много памяти. Настолько, что он не поместится в оперативной памяти. К счастью, это не нужно все сразу. Вместо этого он используется для сохранения промежуточных результатов вычислений.

К сожалению, приложение не может записывать и считывать эти данные достаточно быстро. Я попытался использовать несколько потоков чтения и записи, но это только ухудшило ситуацию (позже я прочитал, что это из-за this ).

Поэтому мой вопрос: возможно ли иметь действительно асинхронный файл ввода-вывода в C ++, чтобы полностью использовать эти объявленные гигабайты в секунду? Если это так, то как (кроссплатформенным способом)?

Вы также можете порекомендовать библиотеку, которая хороша для таких задач, если вы ее знаете, потому что я считаю, что нет смысла заново изобретать колесо.

Edit:

Вот код, который показывает, как я делаю IO файла в моей программе. Это не из упомянутой программы, потому что это не будет настолько минимальным. Это, тем не менее, иллюстрирует проблему. Не против Windows.h. Он используется только для установки сходства потоков. В реальной программе я также установил сходство, поэтому я включил его.

#include <fstream>
#include <thread>
#include <memory>
#include <string>

#include <Windows.h> // for SetThreadAffinityMask()

void stress_write(unsigned bytes, int num)
{
    std::ofstream out("temp" + std::to_string(num));
    for (unsigned i = 0; i < bytes; ++i)
    {
        out << char(i);
    }
}

void lock_thread(unsigned core_idx)
{
    SetThreadAffinityMask(GetCurrentThread(), 1LL << core_idx);
}

int main()
{
    std::ios_base::sync_with_stdio(false);
    lock_thread(0);

    auto worker_count = std::thread::hardware_concurrency() - 1;

    std::unique_ptr<std::thread[]> threads = std::make_unique<std::thread[]>(worker_count); // faster than std::vector

    for (int i = 0; i < worker_count; ++i)
    {
        threads[i] = std::thread(
            [](unsigned idx) {
                lock_thread(idx);
                stress_write(1'000'000'000, idx);
            },
            i + 1
        );
    }
    stress_write(1'000'000'000, 0);

    for (int i = 0; i < worker_count; ++i)
    {
        threads[i].join();
    }
}

Как вы можете видеть, это просто старый fstream. На моей машине это использует 100% ЦП, но только 7-9% диска (около 190 МБ / с). Мне интересно, можно ли его увеличить.

1 Ответ

2 голосов
/ 10 января 2020

Самый простой способ получить (до) 10-кратное ускорение - это изменить это:

void stress_write(unsigned bytes, int num)
{
  std::ofstream out("temp" + std::to_string(num));
  for (unsigned i = 0; i < bytes; ++i)
  {
    out << char(i);
  }
}

на это:

void stress_write(unsigned bytes, int num)
{
  constexpr auto chunk_size = (1u << 12u); // tune as needed
  std::ofstream out("temp" + std::to_string(num));
  for (unsigned chunk = 0; chunk < (bytes+chunk_size-1)/chunk_size; ++chunk)
  {
    char chunk_buff[chunk_size];
    auto count = (std::min)( bytes - chunk_size*chunk, chunk_size );
    for (unsigned j = 0; j < count; ++j)
    {
      unsigned i = j + chunk_size*chunk;
      chunk_buff[j] = char(i); // processing
    }
    out.write( chunk_buff, count );
  }
}

, где мы группируем записи до 4096 байт перед отправкой на стандартный поток.

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

Путем разбиения данных на более крупные фрагменты мы делаем поиск в vtable достаточно редким, чтобы они больше не доминировали.

См. этот пост SO для более подробной информации о причинах.


Чтобы получить последние данные о производительности, вам, возможно, придется использовать что-то вроде boost.asio или получить доступ к своим библиотекам raw asyn c file io.

Но когда вы работаете с <10% от Пропускная способность диска при загрузке процессора, в первую очередь стремитесь к низко висящим фруктам. </p>

...