windows — c ++ Эффективное чтение первых нескольких байтов из каждого байта X

Я хочу прочитать первые 16 байтов из каждого X * 16 байтов файла. Код, который я написал, работает, но довольно медленный из-за множества вызовов функций.

std::vector<Vertex> readFile(int maxVertexCount) {
std::ifstream in = std::ifstream(fileName, std::ios::binary);
in.seekg(0, in.end);
int fileLength = in.tellg();
int vertexCount = fileLength / 16;

int stepSize = std::max(1, vertexCount / maxVertexCount);

std::vector<Vertex> vertices;
vertices.reserve(vertexCount / stepSize);
for (int i = 0; i < vertexCount; i += stepSize) {
in.seekg(i * 16, in.beg);
char bytes[16];
in.read(bytes, 16);
vertices.push_back(Vertex(bytes));
}
in.close();
}

Может ли кто-нибудь дать мне несколько советов по повышению производительности этого кода?

3

Решение

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

(Профилирование должно показать, является ли доступ к диску узким местом. Если бы это было «много вызовов функций», процессор был бы.)

Итак, в первую очередь, Вы можете изменить это требование?
Это во всех сценариях самый простой выход.

Не могли бы вы рассеять меньше? Например. вместо чтения вершин 0, 20, 40, …, 1000, читать вершины 0,1,2,3,4, 100, 101, 102, 103, 104, 200, 201, 202, 203, 204, .. — одинаковое количество вершин из «всех частей» файла.

Во-вторых, Оптимизация под конкретные ОС.
Нет портативного способа управления кэшированием на уровне ОС.

Одним из решений является отображение файла в памяти (CreaterFileMapping в Windows, mmap в системах Linuxy), как предложено @Nim. Это может опустить одну копию памяти, но все равно будет прочитан весь файл.

С Linux ничего не поделаешь, но в Windows у вас есть параметры для CreateFile:

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

  • FILE_FLAG_SEQUENTIAL_SCAN который просто telsl кеш, чтобы не хранить старые данные

Ни один из них не решит проблему с вашим шаблоном доступа, но первый может несколько опосредовать его — особенно если ваши шаги больше секторов диска, а второй может потребовать давления от подсистемы.

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

Снимок может быть просто результатом вашей работы, для определенного maxVertexCount, Или несколько снимков, например, mipmapping. Идея состоит в том, чтобы заменить разбросанное чтение последовательным чтением.

В качестве альтернативы, снимок может хранить данные в чередующемся порядке. Для 128 вершин вы можете хранить вершины в таком порядке (примерно, остерегайтесь<один, эффекты на основе нуля против одного и псевдонимы, и мои ошибки):

64, 32, 96, 16, 48, 80, 112 8, 24, 40, 56, 72, 88, 104, 120 ...

Читаете ли вы первые 3 или первые 7, или первые 15, или первые 31 … значения, примеры одинаково распределены по файлу, как в исходном коде. Перестановка их в памяти будет намного быстрее — особенно если это всего лишь небольшое подмножество.

Примечание: вам нужен надежный алгоритм для определения того, что ваш снимок устарел, независимо от множества забавных вещей, которые происходят с «датой последней записи» в разных файловых системах. «Счетчик изменений» в главном файле будет самым безопасным (хотя это приведет к увеличению стоимости изменений).

В-четвертых, Изменить формат файла

(Если вы можете это контролировать)
Предложенное выше чередованное хранилище может использоваться для всего файла. Тем не менее, это имеет большое значение для обработки — особенно если вам нужно восстановить «первоначальный» порядок в какой-то момент.

Элегантный вариант будет иметь такое чередованное подмножество как часть файла, а также полный список вершин в оригинальном порядке. Есть отсечка stepSize где это больше не помогает, вероятно, около 2 * сектор / размер блока диска. Таким образом, размер файла увеличится только на несколько процентов. Однако записи станут немного дороже, изменения в количестве вершин значительно ухудшатся.


Псевдоним предупреждение

Если это предназначено для получения «статистической» или «визуально достаточной» выборки, фиксированная stepSize может быть проблематичным, так как он может создавать эффекты совмещения (например, шаблоны Муара) с любыми шаблонами, присутствующими в данных.

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

2

Другие решения

Не использовать seek, Я мог бы mmap весь этот файл, а затем просто считывать байты в нужных местах.

Я не собираюсь писать код для вас, но он должен быть таким:

  1. Откройте файл, рассчитайте размер файла
  2. mmap весь файл
  3. Итерируйте по размеру вашего шага, вычисляя адрес каждого блока
  4. Построить каждый Vertex на основе блока и нажмите в векторе
  5. вернуть вектор.
7

… и если по какой-то причине вы не можете использовать map, прочитайте файл в буфер в «больших больших глотках» … размер буфера, кратный X байт. Продолжить чтение в этот буфер (стараясь заметить, сколько байтов мы читать). пока не дойдете до конца файла.

Что вы конкретно пытаетесь избежать это целая куча физическое Операции ввода / вывода: перемещение механизма чтения / записи диска. По этой причине операционная система любит буферизовать, но может только Угадай при чем ваш приложение пытается сделать, и это может угадать неправильно. После того, как диск установил головку чтения / записи на нужную дорожку («время поиска»), он может извлечь данные всей дорожки за один оборот. Но «время поиска» сравнительно медленно.

Сопоставление файла, а затем чтение данных в сопоставленном файле неслучайный, явно самая выгодная стратегия, потому что теперь операционная система знает именно то, что происходит.

1

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

И вы можете использовать низкий уровень pread() читать без необходимости искать:

void readFile( size_t maxVertexCount, std::vector<Vertex> &vertices )
{
struct stat sb;
int fd = std::open( fileName, O_RDONLY );
std::fstat( fd, &sb );

size_t vertexCount = sb.st_size / 16;

size_t stepSize = std::max( 1, vertexCount / maxVertexCount );

vertices.reserve( vertexCount / stepSize );

for ( off_t i = 0; i < vertexCount; i += stepSize)
{
char bytes[ 16 ];
std::pread( fd, bytes, 16, 16 * i );
vertices.push_back( Vertex( bytes ) );
}

std::close( fd );
}

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

Это использует преимущества кэша страниц ядра и, вероятно, опережающего чтения. В зависимости от вашей ОС и конфигурации, другие методы, такие как mmap() или чтение всего файла может быть или не быть быстрее.

1