Преобразование массива байтов (uint8_t) в массив слов (uint16_t) и наоборот - PullRequest
0 голосов
/ 04 июля 2018

У меня есть очень критичный ко времени кусок кода для оптимизации, который конвертирует массивы байтов в массивы слов и наоборот. Эта операция используется для преобразования 8–16-битных данных изображения.

Массив выровнен по qword и достаточно большой, чтобы сохранить результат.

Преобразование из байта в слово требует умножения на 257 (поэтому 0 преобразуется в 0, а 255 получает 65535)

Простое решение может быть

void simpleBytesToWords(void *ptr, int pixelCount)
{
    for (int i = pixelCount - 1; i >= 0; --i)
        reinterpret_cast<uint16_t*>(ptr)[i] = reinterpret_cast<uint8_t*>(ptr)[i] * 0x101;
}

Я также пытался увеличить производительность, конвертируя 4 байта за раз, чтобы использовать 64-битные регистры:

void bytesToWords(void *ptr, int pixelCount)
{
    const auto fastCount = pixelCount / 4;

    if (fastCount > 0)
    {
        for (int f = fastCount-1; f >= 0; --f)
        {
            auto bytes = uint64_t{ reinterpret_cast<const uint32_t*>(ptr)[f] };

            auto r2 = uint64_t{ bytes & 0xFF };
            bytes <<= 8;
            r2 |= bytes & 0xFF0000;
            bytes <<= 8;
            r2 |= bytes & 0xFF00000000ull;
            bytes <<= 8;
            r2 |= bytes & 0xFF000000000000ull;

            r2 *= 0x101;

            reinterpret_cast<uint64_t*>(ptr)[f] = r2; 
        }
    }

    if (pixelCount % 4)
    {
        auto source = reinterpret_cast<const uint8_t*>(ptr);
        auto target = reinterpret_cast<uint16_t*>(ptr);

        for (int i = fastCount * 4; i < pixelCount; ++i)
        {
            target[i] = (source[i] << 8) | source[i];
        }
    }

}

Это работает, и это немного быстрее, чем простое решение.

Другое направление (слова в байты) выполняется с помощью этого кода:

for (int i = 0; i < pixelCount; ++i)
    reinterpret_cast<uint8_t*>(bufferPtr)[i] = reinterpret_cast<uint16_t*>(bufferPtr)[i] / 256;

Я искал встроенные функции компилятора, чтобы ускорить это преобразование, но не нашел ничего полезного. Есть ли другие способы улучшить производительность этого преобразования?

1 Ответ

0 голосов
/ 04 июля 2018

Я попробовал две вещи после компиляции вашего кода (я просто переименовал bytesToWords(), который теперь groupedBytesToWords() ниже):

  • Проверка ваших двух функций: они не дают одинаковых результатов. С simpleBytesToWords() я получаю заполненный нулями массив. С groupedBytesToWords() я получаю череду действительных результатов и нулей.

  • Не изменяя их, предполагая, что исправление не изменит их сложности, я попытался написать третью, написанную мной, в которой используется предварительно вычисленная таблица uint8_t -> uint16_t, которую необходимо построить изначально:

Вот эта таблица. Он маленький, поскольку в нем всего 255 записей, по одной на каждую uint8_t:

// Build a precalculation table for each possible uint8_t -> uint16_t conversion 
const size_t sizeTable(std::numeric_limits<uint8_t>::max());

uint16_t * precalc_table = new uint16_t[sizeTable];

for (uint16_t i = 0; i < sizeTable; ++i)
{
    precalc_table[i] = i * 0x101;
}

Третья функция, которую я попробовал, приведена ниже:

void hopefullyFastBytesToWords(uint16_t *ptr, size_t pixelCount, uint16_t const * precalc_table)
{
    for (size_t i = 0; i < pixelCount; ++i)
    {
        ptr[i] = precalc_table[ptr[i]];
    }
}

Я проверял это, конечно, и результаты, которые он производит, выглядят в соответствии с описанием, которое вы сделали в своем оригинальном посте. Эта функция вызывается путем передачи тех же параметров, что и для двух других функций, а также таблицы предварительно рассчитанных преобразований:

hopefullyFastBytesToWords(buffer, sizeBuf, precalc_table);

Затем я провел несколько сравнений, используя массив длиной 500000000 uint16_t, изначально заполненный случайными значениями uint8_t. Вот пример использования simpleBytesToWords(), который вы написали:

fillBuffer(buffer, sizeBuf);
begin = clock();
simpleBytesToWords(buffer, sizeBuf);
end = clock();
std::cout << "simpleBytesToWords(): " << (double(end - begin) / CLOCKS_PER_SEC) << std::endl;

Я получил следующие результаты (вы увидите, что я использовал маленький и медленный ноутбук). Вот три примера, но все они постоянно производят значения одинаковой величины:

$ Sandbox.exe
simpleBytesToWords(): 0.681
groupedBytesToWords(): 1.2
hopefullyFastBytesToWords(): 0.461

$ Sandbox.exe
simpleBytesToWords(): 0.737
groupedBytesToWords(): 1.251
hopefullyFastBytesToWords(): 0.414

$ Sandbox.exe
simpleBytesToWords(): 0.582
groupedBytesToWords(): 1.173
hopefullyFastBytesToWords(): 0.436

Конечно, это не настоящий реальный и действительный тест, но он показывает, что ваша «сгруппированная» функция медленнее на моей машине, что не соответствует полученным вами результатам. Это также показывает, что немного помогает предварительный расчет умножения вместо приведения / умножения на лету.

...