Для проекта, над которым я работаю, мне нужно подсчитать количество установленных битов на столбец в разорванных PDF
данных изображения.
Я пытаюсь получить общее количество битов набора для каждого столбца во всем задании PDF
(все страницы).
Данные после копирования сохраняются в MemoryMappedFile
без резервного файла (в памяти).
Размеры страницы PDF составляют 13952 x 15125 пикселей. Общий размер результирующих скопированных данных можно рассчитать, умножив длину (высоту) PDF
в пикселях на ширину в байтах. Скопированные данные: 1 bit == 1 pixel
. Таким образом, размер скопированной страницы в байтах равен (13952 / 8) * 15125
.
Обратите внимание, что ширина всегда кратна 64 bits
.
Мне придется подсчитать установленные биты для каждого столбца на каждой странице PDF
(а это могут быть десятки тысяч страниц) после копирования.
Сначала я подошел к проблеме с базовым решением, состоящим в простом переборе каждого байта, подсчете количества установленных битов и размещении результатов в vector
. С тех пор я сократил алгоритм до того, что показано ниже. Я увеличил время выполнения с ~ 350 мс до ~ 120 мс.
static void count_dots( )
{
using namespace diag;
using namespace std::chrono;
std::vector<std::size_t> dot_counts( 13952, 0 );
uint64_t* ptr_dot_counts{ dot_counts.data( ) };
std::vector<uint64_t> ripped_pdf_data( 3297250, 0xFFFFFFFFFFFFFFFFUL );
const uint64_t* ptr_data{ ripped_pdf_data.data( ) };
std::size_t line_count{ 0 };
std::size_t counter{ ripped_pdf_data.size( ) };
stopwatch sw;
sw.start( );
while( counter > 0 )
{
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 7 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 6 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 5 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 4 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 3 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 2 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 1 ) & 0x0000000000000001UL ) >> 0;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0100000000000000UL ) >> 56;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0001000000000000UL ) >> 48;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000010000000000UL ) >> 40;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000000100000000UL ) >> 32;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000000001000000UL ) >> 24;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000000000010000UL ) >> 16;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000000000000100UL ) >> 8;
*ptr_dot_counts++ += ( ( *ptr_data >> 0 ) & 0x0000000000000001UL ) >> 0;
++ptr_data;
--counter;
if( ++line_count >= 218 )
{
ptr_dot_counts = dot_counts.data( );
line_count = 0;
}
}
sw.stop( );
std::cout << sw.elapsed<milliseconds>( ) << "ms\n";
}
К сожалению, это все еще добавит много дополнительного времени обработки, что неприемлемо.
Приведенный выше код уродлив и не выиграет ни одного конкурса красоты, но он помог сократить время выполнения. Начиная с оригинальной версии, которую я написал, я сделал следующее:
- Используйте
pointers
вместоindexers
- Обрабатывайте данные порциями по
uint64
вместоuint8
- Вручную разверните цикл
for
для обхода каждогоbit
в каждомbyte
изuint64
- Используйте конечный
bit shift
вместо__popcnt64
для подсчета набораbit
после маскирования
Для этого теста я генерирую фальшивые скопированные данные, где каждому bit
присвоено значение 1
. dot_counts
vector
должно содержать 15125
для каждого element
после завершения теста.
Я надеюсь, что некоторые люди здесь могут помочь мне получить среднее время выполнения алгоритмов ниже 100 мс. Я не забочусь о переносимости здесь.
- ЦП целевой машины:
Xeon E5-2680 v4 - Intel
- Компилятор:
MSVC++ 14.23
- OS:
Windows 10
- Версия С++:
C++17
- Флаги компилятора:
/O2
/arch:AVX2
Очень похожий вопрос был задан примерно 8 лет назад: of-ints-on-sandy-bridge">Как быстро посчитать биты в отдельные ячейки в серии целых чисел на Sandy Bridge?
(Примечание редактора: возможно, вы пропустили Подсчитайте каждую битовую позицию отдельно по многим 64-битным битовым маскам с AVX, но не AVX2, у которого есть несколько более поздних более быстрых ответов, по крайней мере, для перехода вниз по столбцу, а не по строке в непрерывной памяти. , Может быть, вы можете использовать 1 или 2 строки кэша в ширину вниз по столбцу, чтобы вы могли поддерживать свои счетчики в SIMD-регистрах горячими.)
Когда я сравниваю то, что у меня есть до сих пор, с принятым ответом, я довольно близок. Я уже обрабатывал кусками uint64
вместо uint8
. Мне просто интересно, могу ли я сделать что-то еще, будь то встроенные функции, сборка или что-то простое, например изменение используемых структур данных.
*ptr_data >> 7
и его повторное использование? - person WBuck   schedule 21.10.2019uint64
или векторuint16
тоже подойдет? И можете ли вы сделать какие-либо предположения о выравнивании? - person chtz   schedule 21.10.2019contiguous
(массив1D
), поэтомуrow-major
. Длина (высота) является динамической, но всегда будет кратной5
. В настоящее время я используюuint64
для хранения счетчиков, потому что я собираю общее количество точек для всей работы (все страницы PDF). Хотя это поведение можно изменить. Я мог бы просто сохранить общее количество для каждой страницы, а затем объединить полученныеvectors
в конце - person WBuck   schedule 21.10.2019bit shift
, но без изменений. Я подозреваю, что компилятор увидел, что я делаю, иcached
результат для меня в переменной. Я вставлю код в Compiler Explorer и посмотрю, что делает компилятор. - person WBuck   schedule 21.10.2019( ( *ptr_data >> 7 ) & 0x0100000000000000UL ) >> 56
может быть!!(*ptr_data & 0x80'00'00'00'00'00'00'00UL)
, а( ( *ptr_data >> 7 ) & 0x0001000000000000UL ) >> 48
может быть!!(*ptr_data & 0x40'00'00'00'00'00'00'00UL)
и так далее. - person Jarod42   schedule 21.10.2019*ptr_data
в локальной переменной. - person Jarod42   schedule 21.10.2019implicitly
преобразовать вbool
, но мне все еще былоbit-shifting
. Сейчас попробую ваши предложения. - person WBuck   schedule 21.10.2019!!
кажется не очень хорошим, так как msvc создает прыжок, но я хочу сделать только одну смену:(*ptr_data & 0x80'00'00'00'00'00'00'00UL) >> 63
или даже(*ptr_data >> 63) & 0x01
- person Jarod42   schedule 21.10.2019