The Daily Newsstand · Free, Always
Tuesday, September 15, 2026

Маленькие тонкости большого дела

Translate

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

И причина простая как 2х2. Весь обмен информацией теперь шифруется и подписывается. А это, как ни крути, умножение и сложение больших чисел

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

Ну и поскольку самый наш любимый размер это 256 битовые числа, то попробуем кое-что тут посмотреть интересного.

В это статье будем рассматривать умножение и сложение только буквально "в столбик", именно как в школе учили.

Алгоритм великого А.А.Карацубы рассматривать не будем сейчас.

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

В качестве железа для тестов возьмем видеокарту NVIDIA и все вычисления будем делать на С и PTX ассемблере. Просто такие у автора есть, было бы другое железо, то пример и основа были бы другими.

Вот обычная типичная реализация на ассемблере PTX. (это псевдо ассемблер, который еще раз переводит ассемблерный код в реально ассемблерный код). На этом PTX пишут все эти ИИ библиотеки, если хотят что-бы было быстро.


__device__ inline void cu_32_mul_add_to_288_b(uint32_t *res, const uint32_t *a, const uint32_t b) {
    uint64_t mul2 = 0;
    uint32_t l32 = 0, h32 = 0, buf[9];
    asm volatile(
        "mul.wide.u32 %9, %12, %20;\n\t" // %9 = a[0]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32   %0, 0, %10;\n\t" // buf[0] += %lo32
        "addc.cc.u32  %1, 0, %11;\n\t" // buf[1] += %hi32
        "addc.u32     %2, 0, 0;\n\t"

        "mul.wide.u32 %9, %13, %20;\n\t" // %mul1 = a[1]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32   %1, %1, %10;\n\t" // buf[1] += %lo32
        "addc.cc.u32  %2, %2, %11;\n\t" // buf[2] += %hi32
        "addc.u32     %3,  0, 0;\n\t"

        "mul.wide.u32 %9, %14, %20;\n\t" // %mul1 = a[2]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %2, %2, %10;\n\t"  // buf[2] += %lo32
        "addc.cc.u32  %3, %3, %11;\n\t"  // buf[3] += %hi32
        "addc.u32     %4,  0, 0;\n\t"

        "mul.wide.u32 %9, %15, %20;\n\t" // %mul1 = a[3]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %3, %3, %10;\n\t"  // buf[3] += %lo32
        "addc.cc.u32  %4, %4, %11;\n\t"  // buf[4] += %hi32
        "addc.u32     %5,  0, 0;\n\t"

        "mul.wide.u32 %9, %16, %20;\n\t" // %mul1 = a[4]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %4, %4, %10;\n\t"  // buf[4] += %lo32
        "addc.cc.u32  %5, %5, %11;\n\t"  // buf[5] += %hi32
        "addc.u32     %6,  0, 0;\n\t"

        "mul.wide.u32 %9, %17, %20;\n\t" // %mul1 = a[5]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %5, %5, %10;\n\t"  // buf[5] += %lo32
        "addc.cc.u32  %6, %6, %11;\n\t"  // buf[6] += %hi32
        "addc.u32     %7,  0, 0;\n\t"

        "mul.wide.u32 %9, %18, %20;\n\t" // %mul1 = a[6]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %6, %6, %10;\n\t"  // buf[6] += %lo32
        "addc.cc.u32  %7, %7, %11;\n\t"  // buf[7] += %hi32
        "addc.u32     %8,  0, 0;\n\t"    // 

        "mul.wide.u32 %9, %19, %20;\n\t" // %mul1 = a[7]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32c}
        "addc.cc.u32  %7, %7, %10;\n\t"  // buf[7] += %lo32
        "addc.cc.u32  %8, %8, %11;\n\t"  // buf[8] += %hi32

        : "+r"(buf[0]), "+r"(buf[1]), "+r"(buf[2]), "+r"(buf[3]),
        "+r"(buf[4]), "+r"(buf[5]), "+r"(buf[6]), "+r"(buf[7]), "+r"(buf[8]),
        "=l"(mul2), "+r"(l32), "+r"(h32)
        : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]),
        "r"(a[4]), "r"(a[5]), "r"(a[6]), "r"(a[7]),
        "r"(b)
    );
    asm volatile(
        "add.cc.u32   %0, %0, %9;\n\t" // res[0] += %lo32
        "addc.cc.u32   %1, %1, %10;\n\t" // res[0] += %lo32
        "addc.cc.u32   %2, %2, %11;\n\t" // res[0] += %lo32
        "addc.cc.u32   %3, %3, %12;\n\t" // res[0] += %lo32
        "addc.cc.u32   %4, %4, %13;\n\t" // res[0] += %lo32
        "addc.cc.u32   %5, %5, %14;\n\t" // res[0] += %lo32
        "addc.cc.u32   %6, %6, %15;\n\t" // res[0] += %lo32
        "addc.cc.u32   %7, %7, %16;\n\t" // res[0] += %lo32
        "addc.cc.u32   %8, %8, 0;\n\t" // res[0] += %lo32

        : "+r"(res[0]), "+r"(res[1]), "+r"(res[2]), "+r"(res[3]),
        "+r"(res[4]), "+r"(res[5]), "+r"(res[6]), "+r"(res[7]), "+r"(res[8])
        : "r"(buf[0]), "r"(buf[1]), "r"(buf[2]), "r"(buf[3]),
        "r"(buf[4]), "r"(buf[5]), "r"(buf[6]), "r"(buf[7])
    );


}

Код обычный, популярный, его напишет на несколько микросекунд любой ИИ.

Ну казалось бы что тут улучшить?! Повторю опять, что алгоритм А.А,Карацубы в данной ситуации не рассматриваем, просто ищем улучшение простого алгоритма умножение-с-накоплением реализованного в столбик.

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

__device__ inline void cu_32_mul_add_to_512a(uint32_t *res, const uint32_t *a, const uint32_t b) {
    uint64_t mul2 = 0;
    uint32_t l32 = 0, h32 = 0;
    asm volatile(

        // even part
        "mul.wide.u32 %9, %12, %20;\n\t" // %9 = a[0]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32 %0, %0, %10;\n\t" // res[0] += %lo32
        "addc.cc.u32 %1, %1, %11;\n\t" // res[1] += %hi32

        "mul.wide.u32 %9, %14, %20;\n\t" // %mul1 = a[2]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32 %2, %2, %10;\n\t" // res[2] += %lo32
        "addc.cc.u32 %3, %3, %11;\n\t" // res[3] += %hi32

        "mul.wide.u32 %9, %16, %20;\n\t" // %mul1 = a[4]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32 %4, %4, %10;\n\t" // res[4] += %lo32
        "addc.cc.u32 %5, %5, %11;\n\t" // res[5] += %hi32

        "mul.wide.u32 %9, %18, %20;\n\t" // %mul1 = a[6]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32 %6, %6, %10;\n\t" // res[6] += %lo32
        "addc.cc.u32 %7, %7, %11;\n\t" // res[7] += %hi32
        "addc.u32 %8, %8, 0;\n\t" // res[8] += carry

        //odd part

        "mul.wide.u32 %9, %13, %20;\n\t" // %mul1 = a[1]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32 %1, %1, %10;\n\t" // res[1] += %lo32
        "addc.cc.u32 %2, %2, %11;\n\t" // res[2] += %hi32

        "mul.wide.u32 %9, %15, %20;\n\t" // %mul1 = a[3]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32 %3, %3, %10;\n\t" // res[3] += %lo32
        "addc.cc.u32 %4, %4, %11;\n\t" // res[4] += %hi32

        "mul.wide.u32 %9, %17, %20;\n\t" // %mul1 = a[5]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32 %5, %5, %10;\n\t" // res[5] += %lo32
        "addc.cc.u32 %6, %6, %11;\n\t" // res[6] += %hi32

        "mul.wide.u32 %9, %19, %20;\n\t" // %mul1 = a[7]*b
        "mov.b64 {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32c}
        "addc.cc.u32 %7, %7, %10;\n\t" // res[7] += %lo32
        "addc.u32 %8, %8, %11;\n\t" // res[7] += %hi32


        : "+r"(res[0]), "+r"(res[1]), "+r"(res[2]), "+r"(res[3]),
        "+r"(res[4]), "+r"(res[5]), "+r"(res[6]), "+r"(res[7]), "+r"(res[8]),
        "=l"(mul2), "+r"(l32), "+r"(h32)
        : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]),
        "r"(a[4]), "r"(a[5]), "r"(a[6]), "r"(a[7]),
        "r"(b)
    );
}

Решение изящное, элегантное, экономит операции и регистры. Умножаются и прибавляются сначала четные слова, далее нечетные и перенос распространяется естественно.

Те. кому оно понравилось, могут вписать моё имя маленькими буквами рядом с именем Анатолия Александровича. Совсем маленькими буквами, но изящно и красиво.

Решение это хорошее, но на данном железе абсолютно бестолковое (об этом далее), увы. На другом не проверял, т.к. нет его у меня.

Проверим теперь быстродействие на реальном железе.

Проверялось на вот таком

Устройство 1
Compute Capability: 8.9
Имя: NVIDIA RTX 500 Ada Generation Laptop GPU
Общая глобальная память: -347209728
Разделяемая память на блок: 49152
Количество регистров на блок: 65536
Общая константная память: 65536
Максимальное количество нитей на блоке: 1024
Максимальное количество нитей в сетке: 1024 1024 64
Максимальный размер сетки: 2147483647 65535 65535
Размер wrap-a: 32
[Vector addition of 50000 elements]
CUDA kernel launch with 196 blocks of 256 threads
Test PASSED

Допишем умножение на скаляр до умножения двух больших 256 бит чисел по нашему любимому модулю P = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", это модуль биткоин, весьма популярный для вычислений. Алгоритм быстрого подсчета модуля обычный и расписан везде, где можно, не буду его объяснять.

Далее будет много кода разного. Умножение по модулю 256 бит чисел в базе 32 и 64 и два алгоритма. Итого четыре варианта программ. Еще программа тестирования их и замера скорости. В коде всё будет видно.

вариант Б, классический, с базой 32 бит

#include "habr_256.h"

__device__ inline void cu_u32_set(uint32_t *res, const uint32_t *a) {
#pragma unroll
    for (int i = 0; i < 8; i++) {
        res[i] = a[i];
    }
}

__device__ inline void cu_32_mul_add_to_512b(uint32_t *res, const uint32_t *a, const uint32_t b) {
    uint64_t mul2 = 0;
    uint32_t l32 = 0, h32 = 0, buf[9];
    asm volatile(
        "//mul to 288;\n\t"
        "mul.wide.u32 %9, %12, %20;\n\t" // %9 = a[0]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32   %0, 0, %10;\n\t" // buf[0] += %lo32
        "addc.cc.u32  %1, 0, %11;\n\t" // buf[1] += %hi32
        "addc.u32     %2, 0, 0;\n\t"

        "mul.wide.u32 %9, %13, %20;\n\t" // %mul1 = a[1]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "add.cc.u32   %1, %1, %10;\n\t" // buf[1] += %lo32
        "addc.cc.u32  %2, %2, %11;\n\t" // buf[2] += %hi32
        "addc.u32     %3,  0, 0;\n\t"

        "mul.wide.u32 %9, %14, %20;\n\t" // %mul1 = a[2]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %2, %2, %10;\n\t"  // buf[2] += %lo32
        "addc.cc.u32  %3, %3, %11;\n\t"  // buf[3] += %hi32
        "addc.u32     %4,  0, 0;\n\t"

        "mul.wide.u32 %9, %15, %20;\n\t" // %mul1 = a[3]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %3, %3, %10;\n\t"  // buf[3] += %lo32
        "addc.cc.u32  %4, %4, %11;\n\t"  // buf[4] += %hi32
        "addc.u32     %5,  0, 0;\n\t"

        "mul.wide.u32 %9, %16, %20;\n\t" // %mul1 = a[4]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %4, %4, %10;\n\t"  // buf[4] += %lo32
        "addc.cc.u32  %5, %5, %11;\n\t"  // buf[5] += %hi32
        "addc.u32     %6,  0, 0;\n\t"

        "mul.wide.u32 %9, %17, %20;\n\t" // %mul1 = a[5]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %5, %5, %10;\n\t"  // buf[5] += %lo32
        "addc.cc.u32  %6, %6, %11;\n\t"  // buf[6] += %hi32
        "addc.u32     %7,  0, 0;\n\t"

        "mul.wide.u32 %9, %18, %20;\n\t" // %mul1 = a[6]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32}
        "addc.cc.u32  %6, %6, %10;\n\t"  // buf[6] += %lo32
        "addc.cc.u32  %7, %7, %11;\n\t"  // buf[7] += %hi32
        "addc.u32     %8,  0, 0;\n\t"    //

        "mul.wide.u32 %9, %19, %20;\n\t" // %mul1 = a[7]*b
        "mov.b64    {%10, %11}, %9;\n\t" // %mul1 -> {%lo32, %hi32c}
        "addc.cc.u32  %7, %7, %10;\n\t"  // buf[7] += %lo32
        "addc.cc.u32  %8, %8, %11;\n\t"  // buf[8] += %hi32


        : "+r"(buf[0]), "+r"(buf[1]), "+r"(buf[2]), "+r"(buf[3]),
        "+r"(buf[4]), "+r"(buf[5]), "+r"(buf[6]), "+r"(buf[7]), "+r"(buf[8]),
        "=l"(mul2), "+r"(l32), "+r"(h32)
        : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]),
        "r"(a[4]), "r"(a[5]), "r"(a[6]), "r"(a[7]),
        "r"(b)
    );
    asm volatile(
        "add.cc.u32   %0, %0, %9;\n\t" // res[0] += buf
        "addc.cc.u32   %1, %1, %10;\n\t" // res[1] += buf
        "addc.cc.u32   %2, %2, %11;\n\t" // res[2] += buf
        "addc.cc.u32   %3, %3, %12;\n\t" // res[3] += buf
        "addc.cc.u32   %4, %4, %13;\n\t" // res[4] += buf
        "addc.cc.u32   %5, %5, %14;\n\t" // res[5] += buf
        "addc.cc.u32   %6, %6, %15;\n\t" // res[6] += buf
        "addc.cc.u32   %7, %7, %16;\n\t" // res[7] += buf
        "addc.cc.u32   %8, %8, 0;\n\t" // res[8] += buf


        : "+r"(res[0]), "+r"(res[1]), "+r"(res[2]), "+r"(res[3]),
        "+r"(res[4]), "+r"(res[5]), "+r"(res[6]), "+r"(res[7]), "+r"(res[8])
        : "r"(buf[0]), "r"(buf[1]), "r"(buf[2]), "r"(buf[3]),
        "r"(buf[4]), "r"(buf[5]), "r"(buf[6]), "r"(buf[7])
    );


}
__device__ inline void cu_32_mul_mod_pb(uint32_t *res, const uint32_t *a, const uint32_t *b) {
    uint32_t tmp[18] = {};

    cu_32_mul_add_to_512b(&tmp[0], a, b[0]);
    cu_32_mul_add_to_512b(&tmp[1], a, b[1]);
    cu_32_mul_add_to_512b(&tmp[2], a, b[2]);
    cu_32_mul_add_to_512b(&tmp[3], a, b[3]);
    cu_32_mul_add_to_512b(&tmp[4], a, b[4]);
    cu_32_mul_add_to_512b(&tmp[5], a, b[5]);
    cu_32_mul_add_to_512b(&tmp[6], a, b[6]);
    cu_32_mul_add_to_512b(&tmp[7], a, b[7]);

    uint64_t mul = 0;
    uint32_t l32 = 0, h32 = 0;
    uint32_t t8=0, t9=0, t10=0, t0=0, t1=0, t2=0, t3=0;

    // tmp[8-18] * 3d1 + tmp[0] -> tmp[0]
    asm volatile(
        "mul.wide.u32 %16, %8, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "add.cc.u32 %0, %0, %17;\n\t"
        "addc.cc.u32 %1, %1, %18;\n\t"

        "mul.wide.u32 %16, %10, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %2, %2, %17;\n\t"
        "addc.cc.u32 %3, %3, %18;\n\t"

        "mul.wide.u32 %16, %12, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %4, %4, %17;\n\t"
        "addc.cc.u32 %5, %5, %18;\n\t"

        "mul.wide.u32 %16, %14, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %6, %6, %17;\n\t"
        "addc.cc.u32 %7, %7, %18;\n\t"
        "addc.u32 %19, 0, 0;\n\t"
// -------------------------------------------------------
        "mul.wide.u32 %16, %9, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "add.cc.u32 %1, %1, %17;\n\t"
        "addc.cc.u32 %2, %2, %18;\n\t"

        "mul.wide.u32 %16, %11, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %3, %3, %17;\n\t"
        "addc.cc.u32 %4, %4, %18;\n\t"

        "mul.wide.u32 %16, %13, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %5, %5, %17;\n\t"
        "addc.cc.u32 %6, %6, %18;\n\t"

        "mul.wide.u32 %16, %15, 0x03d1;\n\t"
        "mov.b64 {%17, %18}, %16;\n\t"
        "addc.cc.u32 %7, %7, %17;\n\t"
        "addc.cc.u32 %19, %19, %18;\n\t"
        "addc.u32 %20, 0, 0;\n\t"


        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
          "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
          "+r"(tmp[8]), "+r"(tmp[9]), "+r"(tmp[10]), "+r"(tmp[11]),
          "+r"(tmp[12]), "+r"(tmp[13]), "+r"(tmp[14]), "+r"(tmp[15]),
          "+l"(mul), "+r"(l32), "+r"(h32),
          "+r"(t8), "+r"(t9), "+r"(t10)
    );

    // *+ hi part of 1000003d1
    // tmp[8-18] * 3d1 + tmp[0] -> tmp[0]
    uint32_t carry;
    asm volatile(
        "add.cc.u32 %1, %1, %8;\n\t"
        "addc.cc.u32 %2, %2, %9;\n\t"
        "addc.cc.u32 %3, %3, %10;\n\t"
        "addc.cc.u32 %4, %4, %11;\n\t"
        "addc.cc.u32 %5, %5, %12;\n\t"
        "addc.cc.u32 %6, %6, %13;\n\t"
        "addc.cc.u32 %7, %7, %14;\n\t"
        "addc.cc.u32 %19, %19, %15;\n\t"
        "addc.u32 %20, 0, 0;\n\t"


        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
          "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
          "+r"(tmp[8]), "+r"(tmp[9]), "+r"(tmp[10]), "+r"(tmp[11]),
          "+r"(tmp[12]), "+r"(tmp[13]), "+r"(tmp[14]), "+r"(tmp[15]),
          "+l"(mul), "+r"(l32), "+r"(h32),
          "+r"(t8), "+r"(t9), "+r"(t10)
    );

    asm volatile(
        // t8,t9,10 (buf[4]) нужно умножить на 100000000 + 000003d1 и добавить в tmp
        "mov.u32 %1, %7;\n\t"
        "mov.u32 %2, %8;\n\t"
        "mov.u32 %3, %9;\n\t"
        "mul.wide.u32 %4, %7, 0x000003d1;\n\t" // mul = %h32 * 3d1
        "mov.b64 {%5, %6}, %4;\n\t"
        "add.cc.u32 %0, %0, %5;\n\t"
        "addc.cc.u32 %1, %1, %6;\n\t"

        "mul.wide.u32 %4, %9, 0x000003d1;\n\t" // mul = %h32 * 3d1
        "mov.b64 {%5, %6}, %4;\n\t"
        "addc.cc.u32 %2, %2, %5;\n\t"
        "addc.u32 %3, %3, %6;\n\t"

        "mul.wide.u32 %4, %8, 0x000003d1;\n\t" // mul = %h32 * 3d1
        "mov.b64 {%5, %6}, %4;\n\t"
        "add.cc.u32 %1, %1, %5;\n\t"
        "addc.cc.u32 %2, %2, %6;\n\t"
        "addc.u32 %3, 0, 0;\n\t"

        : "+r"(t0), "+r"(t1), "=r"(t2), "=r"(t3),
        "+l"(mul), "+r"(l32), "+r"(h32)
        : "r"(t8), "r"(t9), "r"(t10)
    );

    asm volatile( //tmp += t0t1t2t3 (t8t9t10*100003d1)
        "add.cc.u32 %0, %0, %9;\n\t"
        "addc.cc.u32 %1, %1, %10;\n\t"
        "addc.cc.u32 %2, %2, %11;\n\t"
        "addc.cc.u32 %3, %3, %12;\n\t"
        "addc.cc.u32 %4, %4, 0;\n\t"
        "addc.cc.u32 %5, %5, 0;\n\t"
        "addc.cc.u32 %6, %6, 0;\n\t"
        "addc.cc.u32 %7, %7, 0;\n\t"
        "addc.u32 %8, 0, 0;\n\t"
        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
        "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
        "=r"(carry)
        : "r"(t0), "r"(t1), "r"(t2), "r"(t3)
    );

    if (carry != 0) {
        asm volatile(
            "add.cc.u32 %0, %9, 0x000003d1;\n\t"
            "addc.cc.u32 %1, %10, 0x1;\n\t"
            "addc.cc.u32 %2, %11, 0;\n\t"
            "addc.cc.u32 %3, %12, 0;\n\t"
            "addc.cc.u32 %4, %13, 0;\n\t"
            "addc.cc.u32 %5, %14, 0;\n\t"
            "addc.cc.u32 %6, %15, 0;\n\t"
            "addc.cc.u32 %7, %16, 0;\n\t"
            "addc.u32 %8, 0, 0;\n\t" // Add 0 + 0 + carry flag, write result to %1
            : "=r"(tmp[0]), "=r"(tmp[1]), "=r"(tmp[2]), "=r"(tmp[3]),
            "=r"(tmp[4]), "=r"(tmp[5]), "=r"(tmp[6]), "=r"(tmp[7]),
            "=r"(carry)
            : "r"(res[0]), "r"(res[1]), "r"(res[2]), "r"(res[3]),
            "r"(res[4]), "r"(res[5]), "r"(res[6]), "r"(res[7])
        );
    }
    asm volatile(
        "add.cc.u32 %0, %9, 0x000003d1;\n\t"
        "addc.cc.u32 %1, %10, 0x1;\n\t"
        "addc.cc.u32 %2, %11, 0;\n\t"
        "addc.cc.u32 %3, %12, 0;\n\t"
        "addc.cc.u32 %4, %13, 0;\n\t"
        "addc.cc.u32 %5, %14, 0;\n\t"
        "addc.cc.u32 %6, %15, 0;\n\t"
        "addc.cc.u32 %7, %16, 0;\n\t"
        "addc.u32 %8, 0, 0;\n\t" // Add 0 + 0 + carry flag, write result to %1
        : "=r"(tmp[8]), "=r"(tmp[9]), "=r"(tmp[10]), "=r"(tmp[11]),
        "=r"(tmp[12]), "=r"(tmp[13]), "=r"(tmp[14]), "=r"(tmp[15]),
        "=r"(carry)
        : "r"(tmp[0]), "r"(tmp[1]), "r"(tmp[2]), "r"(tmp[3]),
        "r"(tmp[4]), "r"(tmp[5]), "r"(tmp[6]), "r"(tmp[7])
    );
    if (carry == 0) {
#pragma unroll
        for (int i = 0; i < 8; ++i) {
            res[i] = tmp[i];
        }
    } else {
#pragma unroll
        for (int i = 0; i < 8; ++i) {
            res[i] = tmp[i+8];
        }
    }
}

__global__ void test_kernel_mul_mod_32b(Data * dev_r, Data * dev_a, Data * dev_b) {
    uint64_t tid = threadIdx.x + blockIdx.x * blockDim.x;
    if (tid >= CUDA_NUM_THREADS*CUDA_NUM_BLOCKS) return;
    uint32_t a[8],b[8],r[8];

    for (uint i = 0; i < CUDA_STRIDE; i++) {
        uint64_t idx = tid*CUDA_STRIDE+i;
        cu_u32_set(a, dev_a[idx].w32);
        cu_u32_set(b, dev_b[idx].w32);
        for (uint j = 0; j < T_SIZE; j++) {
            cu_32_mul_mod_pb(r, a, b);
            a[0] = a[0] ^ r[0] ;
        }
            cu_u32_set(dev_r[idx].w32, r);
    }
}

Комментарии не нужны, тут и так ясно, что вызывается cu_32_mul_mod_pb на всех точках и на каждой выполняется T_SIZE раз. Сама cu_32_mul_mod_pb просто умножает два 256 бит числа a и b по модулю P и прибавляет их к результату r. Data тут простой union для того, чтобы спокойно далее запустить перемножение в 64 бит базе тех же самых 256 бит чисел.

Строка a[0] = a[0] ^ r[0] ; нужна для того, что иногда оптимизатор выполняет весь код в цикле по j всего один раз и наверно для этого есть основания, т.к. данные не меняются.

Хедер
#ifndef HABR_256_HABR_256_H
#define HABR_256_HABR_256_H

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <sys/random.h>
#include <errno.h>
#include <string.h>
#include <cuda_runtime.h>
#include <gmp.h>
#include <chrono>

#define CUDA_NUM_THREADS 32
#define CUDA_NUM_BLOCKS 32
#define CUDA_STRIDE 16*256
#define N_SIZE CUDA_NUM_THREADS*CUDA_NUM_BLOCKS*CUDA_STRIDE
#define T_SIZE 1

#define CUDA_CHECK(ans) { gpuAssert((ans), __FILE__, __LINE__); }

union Data {
    uint64_t w64[4];
    uint32_t w32[8];
};

void mpz_64_to_uint256(const mpz_t x, uint64_t * res);
void mpz_32_to_uint256(const mpz_t x, uint32_t * res);
__global__ void test_kernel_add_32(Data * dev_r, Data * dev_a, Data * dev_b);
__global__ void test_kernel_add_64(Data * dev_r, Data * dev_a, Data * dev_b);
__global__ void test_kernel_mul_32(Data * dev_r, Data * dev_a, uint32_t * dev_b);
__global__ void test_kernel_mul_64(Data * dev_r, Data * dev_a, const uint64_t * dev_b);
__global__ void test_kernel_mul_mod_64_b(Data * dev_r, Data * dev_a, const Data * dev_b);
__global__ void test_kernel_mul_mod_64_a(Data * dev_r, Data * dev_a, const Data * dev_b);
__global__ void test_kernel_mul_mod_32a(Data * dev_r, Data * dev_a, Data * dev_b);
__global__ void test_kernel_mul_mod_32b(Data * dev_r, Data * dev_a, Data * dev_b);

__device__ void u64_512_printf(const char *txt, uint64_t *a);
__device__ inline void cu_u64_set(uint64_t *res, const uint64_t *a);
__device__ inline void cu_u32_set(uint32_t *res, const uint32_t *a);
__device__ inline void cu_u64_mul_mod_p(uint64_t *res, const uint64_t *a, const uint64_t *b);
__device__ inline void cu_u64_mul_mod_pa(uint64_t *res, const uint64_t *a, const uint64_t *b);
__device__ void cu_u64_printf(const char *txt, const uint64_t *a);
__device__ void cu_u32_printf(const char *txt, const uint32_t *a);
__device__ void u64_512_printf(char *txt, uint64_t *a);
__device__ void cu_u32_512_printf(char *txt, uint32_t *a);
__device__ inline void cu_32_mul_mod_pa(uint32_t *res, const uint32_t *a, const uint32_t *b);
__device__ inline uint64_t cu_u64_add_3d1_with_carry(uint64_t *res, const uint64_t *x);

void u64_printf(const char *txt, const uint64_t *a);
void u32_printf(const char *txt, const uint32_t *a);
void u64_to_mpz(const uint64_t * x, mpz_t y);
void u32_to_mpz(const uint32_t * x, mpz_t y);
void gpuAssert(cudaError_t code, const char *file, int line);


#endif //HABR_256_HABR_256_H

И все оставшиеся три варианта, два для 64 битовых слов и один для 32 бит

вариант А с базой 32 бит

#include "habr_256.h"

__device__ inline void cu_32_mul_add_to_512a(uint32_t *res, const uint32_t *a, const uint32_t b) {
    uint64_t mul2 = 0;
    uint32_t l32 = 0, h32 = 0;
    asm volatile(

        : "+r"(res[0]), "+r"(res[1]), "+r"(res[2]), "+r"(res[3]),
        "+r"(res[4]), "+r"(res[5]), "+r"(res[6]), "+r"(res[7]), "+r"(res[8]),
        "=l"(mul2), "+r"(l32), "+r"(h32)
        : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]),
        "r"(a[4]), "r"(a[5]), "r"(a[6]), "r"(a[7]),
        "r"(b)
    );
}
__device__ inline void cu_32_mul_mod_pa(uint32_t *res, const uint32_t *a, const uint32_t *b) {
    uint32_t tmp[18] = {};

    cu_32_mul_add_to_512a(&tmp[0], a, b[0]);
    cu_32_mul_add_to_512a(&tmp[1], a, b[1]);
    cu_32_mul_add_to_512a(&tmp[2], a, b[2]);
    cu_32_mul_add_to_512a(&tmp[3], a, b[3]);
    cu_32_mul_add_to_512a(&tmp[4], a, b[4]);
    cu_32_mul_add_to_512a(&tmp[5], a, b[5]);
    cu_32_mul_add_to_512a(&tmp[6], a, b[6]);
    cu_32_mul_add_to_512a(&tmp[7], a, b[7]);

    uint64_t mul = 0;
    uint32_t l32 = 0, h32 = 0;
    uint32_t t8=0, t9=0, t10=0, t0=0, t1=0, t2=0, t3=0;

    // tmp[8-18] * 3d1 + tmp[0] -> tmp[0]
    asm volatile(
        
        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
          "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
          "+r"(tmp[8]), "+r"(tmp[9]), "+r"(tmp[10]), "+r"(tmp[11]),
          "+r"(tmp[12]), "+r"(tmp[13]), "+r"(tmp[14]), "+r"(tmp[15]),
          "+l"(mul), "+r"(l32), "+r"(h32),
          "+r"(t8), "+r"(t9), "+r"(t10)
    );

    // *+ hi part of 1000003d1
    // tmp[8-18] * 3d1 + tmp[0] -> tmp[0]
    uint32_t carry;
    asm volatile(
        "add.cc.u32 %1, %1, %8;\n\t"
        "addc.cc.u32 %2, %2, %9;\n\t"
        "addc.cc.u32 %3, %3, %10;\n\t"
        "addc.cc.u32 %4, %4, %11;\n\t"
        "addc.cc.u32 %5, %5, %12;\n\t"
        "addc.cc.u32 %6, %6, %13;\n\t"
        "addc.cc.u32 %7, %7, %14;\n\t"
        "addc.cc.u32 %19, %19, %15;\n\t"
        "addc.u32 %20, 0, 0;\n\t"
        
        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
          "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
          "+r"(tmp[8]), "+r"(tmp[9]), "+r"(tmp[10]), "+r"(tmp[11]),
          "+r"(tmp[12]), "+r"(tmp[13]), "+r"(tmp[14]), "+r"(tmp[15]),
          "+l"(mul), "+r"(l32), "+r"(h32),
          "+r"(t8), "+r"(t9), "+r"(t10)
    );

    asm volatile(
        // t8,t9,10 (buf[4]) нужно умножить на 100000000 + 000003d1 и добавить в tmp
        "mov.u32 %1, %7;\n\t"
        "mov.u32 %2, %8;\n\t"
        "mov.u32 %3, %9;\n\t"
        "mul.wide.u32 %4, %7, 0x000003d1;\n\t" 
        "mov.b64 {%5, %6}, %4;\n\t"
        "add.cc.u32 %0, %0, %5;\n\t"
        "addc.cc.u32 %1, %1, %6;\n\t"

        "mul.wide.u32 %4, %9, 0x000003d1;\n\t"
        "mov.b64 {%5, %6}, %4;\n\t"
        "addc.cc.u32 %2, %2, %5;\n\t"
        "addc.u32 %3, %3, %6;\n\t"

        "mul.wide.u32 %4, %8, 0x000003d1;\n\t"
        "mov.b64 {%5, %6}, %4;\n\t"
        "add.cc.u32 %1, %1, %5;\n\t"
        "addc.cc.u32 %2, %2, %6;\n\t"
        "addc.u32 %3, 0, 0;\n\t"

        : "+r"(t0), "+r"(t1), "=r"(t2), "=r"(t3),
        "+l"(mul), "+r"(l32), "+r"(h32)
        : "r"(t8), "r"(t9), "r"(t10)
    );

    asm volatile( //tmp += t0t1t2t3 (t8t9t10*100003d1)
        "add.cc.u32 %0, %0, %9;\n\t"
        "addc.cc.u32 %1, %1, %10;\n\t"
        "addc.cc.u32 %2, %2, %11;\n\t"
        "addc.cc.u32 %3, %3, %12;\n\t"
        "addc.cc.u32 %4, %4, 0;\n\t"
        "addc.cc.u32 %5, %5, 0;\n\t"
        "addc.cc.u32 %6, %6, 0;\n\t"
        "addc.cc.u32 %7, %7, 0;\n\t"
        "addc.u32 %8, 0, 0;\n\t"
        : "+r"(tmp[0]), "+r"(tmp[1]), "+r"(tmp[2]), "+r"(tmp[3]),
        //           0,            1,            2,            3,
        "+r"(tmp[4]), "+r"(tmp[5]), "+r"(tmp[6]), "+r"(tmp[7]),
        //           4,            5,            6,            7,
        "=r"(carry)
        //          8,
        : "r"(t0), "r"(t1), "r"(t2), "r"(t3)
        //      9,      10,      11,      12
    );

    if (carry != 0) {
        asm volatile(
            "add.cc.u32 %0, %9, 0x000003d1;\n\t"
            "addc.cc.u32 %1, %10, 0x1;\n\t"
            "addc.cc.u32 %2, %11, 0;\n\t"
            "addc.cc.u32 %3, %12, 0;\n\t"
            "addc.cc.u32 %4, %13, 0;\n\t"
            "addc.cc.u32 %5, %14, 0;\n\t"
            "addc.cc.u32 %6, %15, 0;\n\t"
            "addc.cc.u32 %7, %16, 0;\n\t"
            "addc.u32 %8, 0, 0;\n\t"
            : "=r"(tmp[0]), "=r"(tmp[1]), "=r"(tmp[2]), "=r"(tmp[3]),
            "=r"(tmp[4]), "=r"(tmp[5]), "=r"(tmp[6]), "=r"(tmp[7]),
            "=r"(carry)
            : "r"(res[0]), "r"(res[1]), "r"(res[2]), "r"(res[3]),
            "r"(res[4]), "r"(res[5]), "r"(res[6]), "r"(res[7])
        );
    }

    asm volatile(
        "add.cc.u32 %0, %9, 0x000003d1;\n\t"
        "addc.cc.u32 %1, %10, 0x1;\n\t"
        "addc.cc.u32 %2, %11, 0;\n\t"
        "addc.cc.u32 %3, %12, 0;\n\t"
        "addc.cc.u32 %4, %13, 0;\n\t"
        "addc.cc.u32 %5, %14, 0;\n\t"
        "addc.cc.u32 %6, %15, 0;\n\t"
        "addc.cc.u32 %7, %16, 0;\n\t"
        "addc.u32 %8, 0, 0;\n\t" // Add 0 + 0 + carry flag, write result to %1
        : "=r"(tmp[8]), "=r"(tmp[9]), "=r"(tmp[10]), "=r"(tmp[11]),
        "=r"(tmp[12]), "=r"(tmp[13]), "=r"(tmp[14]), "=r"(tmp[15]),
        "=r"(carry)
        : "r"(tmp[0]), "r"(tmp[1]), "r"(tmp[2]), "r"(tmp[3]),
        "r"(tmp[4]), "r"(tmp[5]), "r"(tmp[6]), "r"(tmp[7])
    );
    if (carry == 0) {
#pragma unroll
        for (int i = 0; i < 8; ++i) {
            res[i] = tmp[i];
        }
    } else {
#pragma unroll
        for (int i = 0; i < 8; ++i) {
            res[i] = tmp[i+8];
        }
    }
}

__global__ void test_kernel_mul_mod_32a(Data * dev_r, Data * dev_a, Data * dev_b) {
    uint64_t tid = threadIdx.x + blockIdx.x * blockDim.x;
    if (tid >= CUDA_NUM_THREADS*CUDA_NUM_BLOCKS) return;
    uint32_t a[8],b[8],r[8];

    for (uint i = 0; i < CUDA_STRIDE; i++) {
        uint64_t idx = tid*CUDA_STRIDE+i;
        cu_u32_set(a, dev_a[idx].w32);
        cu_u32_set(b, dev_b[idx].w32);
        for (uint j = 0; j < T_SIZE; j++) {
            cu_32_mul_mod_pa(r, a, b);
            a[0] = a[0] ^ r[0];
        }
            cu_u32_set(dev_r[idx].w32, r);
    }
}

Для полноты еще есть варианты А и В для базы 64 бит. Команды ассемблерные умножения другие, но алгоритм тот же

вариант Б, классический, с базой 64 бит

#include "habr_256.h"

__device__ inline void cu_u64_set(uint64_t *res, const uint64_t *a) {
#pragma unroll
    for (int i = 0; i < 4; i++) {
        res[i] = a[i];
    }
}

__device__ inline uint64_t cu_u64_add_3d1_with_carry(uint64_t *res, const uint64_t *x) {
    uint64_t carry;
    asm volatile(
        "add.cc.u64 %0, %5, 0x1000003d1;\n\t"
        "addc.cc.u64 %1, %6, 0;\n\t"
        "addc.cc.u64 %2, %7, 0;\n\t"
        "addc.cc.u64 %3, %8, 0;\n\t"
        "addc.u64 %4, 0, 0;\n\t"
        : "=l"(res[0]), "=l"(res[1]), "=l"(res[2]), "=l"(res[3]),
        "=l"(carry)
        : "l"(x[0]), "l"(x[1]), "l"(x[2]), "l"(x[3])
    );
    return carry;
}
__device__ inline void cu_u64_mul_add_to_512(uint64_t *res, const uint64_t *a, const uint64_t b) {
    uint64_t buf[5] = {};
    asm volatile(
        "mul.lo.u64        %0, %5, %9;\n\t"
        "mul.hi.u64     %%rd0, %5, %9;\n\t"

        "// Накопление со сдвигом по цепочке\n\t"
        "mad.lo.cc.u64     %1, %6, %9, %%rd0;\n\t"
        "madc.hi.cc.u64 %%rd0, %6, %9, 0;\n\t"

        "madc.lo.cc.u64     %2, %7, %9, %%rd0;\n\t"
        "madc.hi.cc.u64 %%rd0, %7, %9, 0;\n\t"

        "madc.lo.cc.u64     %3, %8, %9, %%rd0;\n\t"
        "madc.hi.cc.u64    %4, %8, %9, 0;\n\t"

    : "=l"(buf[0]), "=l"(buf[1]), "=l"(buf[2]), "=l"(buf[3]), "=l"(buf[4])
    : "l"(a[0]), "l"(a[1]), "l"(a[2]), "l"(a[3]),
      "l"(b)
    );

    asm volatile(
        "add.cc.u64 %0, %0, %5;\n\t"
        "addc.cc.u64 %1, %1, %6;\n\t"
        "addc.cc.u64 %2, %2, %7;\n\t"
        "addc.cc.u64 %3, %3, %8;\n\t"
        "addc.cc.u64 %4, %4, %9;\n\t"
        : "+l"(res[0]), "+l"(res[1]), "+l"(res[2]), "+l"(res[3]), "+l"(res[4])
        : "l"(buf[0]), "l"(buf[1]), "l"(buf[2]), "l"(buf[3]), "l"(buf[4])
    );
}

__device__ inline void cu_u64_mul_mod_p(uint64_t *res, const uint64_t *a, const uint64_t *b) {
    uint64_t tmp[9] = {};
    uint64_t buf[5] = {};
    uint64_t carry = 0;

    cu_u64_mul_add_to_512(&tmp[0], a, b[0]);
    cu_u64_mul_add_to_512(&tmp[1], a, b[1]);
    cu_u64_mul_add_to_512(&tmp[2], a, b[2]);
    cu_u64_mul_add_to_512(&tmp[3], a, b[3]);

    asm volatile(
        "// tmp[4-7] * 10..03d1 -> buf[0-4]\n\t"
        "mul.lo.u64        %0, %5, 0x1000003d1;\n\t"
        "mul.hi.u64        %%rd0, %5, 0x1000003d1;\n\t"

        "mad.lo.cc.u64       %1, %6, 0x1000003d1, %%rd0;\n\t"
        "madc.hi.cc.u64   %%rd0, %6, 0x1000003d1, 0;\n\t"

        "madc.lo.cc.u64       %2, %7, 0x1000003d1, %%rd0;\n\t"
        "madc.hi.cc.u64   %%rd0, %7, 0x1000003d1, 0;\n\t"

        "madc.lo.cc.u64        %3, %8, 0x1000003d1, %%rd0;\n\t"
        "madc.hi.cc.u64       %4, %8, 0x1000003d1, 0;\n\t"

    : "=l"(buf[0]), "=l"(buf[1]), "=l"(buf[2]), "=l"(buf[3]), "=l"(buf[4])
    : "l"(tmp[4]), "l"(tmp[5]), "l"(tmp[6]), "l"(tmp[7])
    );
    asm volatile(
        "// buf[0-4] + tmp[0-3] -> buf[0-4] \n\t"
        "add.cc.u64 %0, %0, %5;\n\t"
        "addc.cc.u64 %1, %1, %6;\n\t"
        "addc.cc.u64 %2, %2, %7;\n\t"
        "addc.cc.u64 %3, %3, %8;\n\t"
        "addc.u64 %4, %4, 0;\n\t"
        : "+l"(buf[0]), "+l"(buf[1]), "+l"(buf[2]), "+l"(buf[3]), "+l"(buf[4])
        : "l"(tmp[0]), "l"(tmp[1]), "l"(tmp[2]), "l"(tmp[3]), "l"(tmp[4])
    );

    asm volatile(
        "mad.lo.cc.u64  %0, %4, 0x1000003d1, %0;\n\t"
        "madc.hi.cc.u64  %%rd0, %4, 0x1000003d1, 0;\n\t"
        "addc.cc.u64    %1, %1, %%rd0;\n\t"
        "addc.cc.u64    %2, %2, 0;\n\t"
        "addc.cc.u64    %3, %3, 0;\n\t"
        "addc.u64       %4,  0, 0;\n\t"
        : "+l"(buf[0]), "+l"(buf[1]), "+l"(buf[2]), "+l"(buf[3]), "+l"(buf[4])
    );
    if (buf[4] != 0)
        carry = cu_u64_add_3d1_with_carry(res, buf);
    carry = cu_u64_add_3d1_with_carry(res, buf);
    if (carry == 1) return;
    cu_u64_set(res, buf);
}

__global__ void test_kernel_mul_mod_64_b(Data * dev_r, Data * dev_a, const Data * dev_b) {
    uint_fast64_t tid = threadIdx.x + blockIdx.x * blockDim.x;
    if (tid >= CUDA_NUM_THREADS*CUDA_NUM_BLOCKS) return;
    uint64_t a[4],b[4],r[4];

    for (uint i = 0; i < CUDA_STRIDE; i++) {
        uint64_t idx = tid*CUDA_STRIDE+i;
        cu_u64_set(a, dev_a[idx].w64);
        cu_u64_set(b, dev_b[idx].w64);
        for (uint j = 0; j < T_SIZE; j++) {
            cu_u64_mul_mod_p(r, a, b);
            a[0] = a[0] ^ (r[0] & 0x00000000ffffffffULL);
        }
        cu_u64_set(dev_r[idx].w64, r);

    }
}

еще немного кода

Вариант А, модерновый, с базой 64 бита

#include "habr_256.h"


__device__ inline void cu_u64_set(uint64_t *res, const uint64_t *a) {
#pragma unroll
    for (int i = 0; i < 4; i++) {
        res[i] = a[i];
    }
}

__device__ inline uint64_t cu_u64_add_3d1_inplace(uint64_t *res) {
    uint64_t carry;
    asm volatile(
        "add.cc.u64 %0, %0, 0x1000003d1;\n\t"
        "addc.cc.u64 %1, %1, 0;\n\t"
        "addc.cc.u64 %2, %2, 0;\n\t"
        "addc.cc.u64 %3, %3, 0;\n\t"
        "addc.u64 %4, 0, 0;\n\t" // Add 0 + 0 + carry flag, write result to %1
        : "+l"(res[0]), "+l"(res[1]), "+l"(res[2]), "+l"(res[3]),
        "=l"(carry)
    );
    return carry;
}
__device__ inline uint64_t cu_u64_add_3d1_with_carry(uint64_t *res, const uint64_t *x) {
    uint64_t carry;
    asm volatile(
        "add.cc.u64 %0, %5, 0x1000003d1;\n\t"
        "addc.cc.u64 %1, %6, 0;\n\t"
        "addc.cc.u64 %2, %7, 0;\n\t"
        "addc.cc.u64 %3, %8, 0;\n\t"
        "addc.u64 %4, 0, 0;\n\t" // Add 0 + 0 + carry flag, write result to %1
        : "=l"(res[0]), "=l"(res[1]), "=l"(res[2]), "=l"(res[3]),
        "=l"(carry)
        : "l"(x[0]), "l"(x[1]), "l"(x[2]), "l"(x[3])
    );
    return carry;
}

__device__ __forceinline__ void cu_u64_mul_add_to_512a(uint64_t *res, const uint64_t *a, const uint64_t b) {
    asm volatile(
         "mad.lo.cc.u64     %0, %5, %9, %0;\n\t"
         "madc.hi.cc.u64    %1, %5, %9, %1;\n\t"

         "madc.lo.cc.u64    %2, %7, %9, %2;\n\t"
         "madc.hi.cc.u64    %3, %7, %9, %3;\n\t"
         "addc.u64          %4, 0,0;\n\t"

         "mad.lo.cc.u64     %1, %6, %9, %1;\n\t"
         "madc.hi.cc.u64    %2, %6, %9, %2;\n\t"

         "madc.lo.cc.u64    %3, %8, %9, %3;\n\t"
         "madc.hi.cc.u64    %4, %8, %9, %4;\n\t"

         : "+l"(res[0]), "+l"(res[1]), "+l"(res[2]), "+l"(res[3]), "+l"(res[4])
         : "l"(a[0]), "l"(a[1]), "l"(a[2]), "l"(a[3]),
           "l"(b)
     );
}
__device__ inline void cu_u64_mul_mod_pa(uint64_t *res, const uint64_t *a, const uint64_t *b) {
    uint64_t tmp[9] = {0};
    uint64_t carry = 0;
    cu_u64_mul_add_to_512a(&tmp[0], a, b[0]);
    cu_u64_mul_add_to_512a(&tmp[1], a, b[1]);
    cu_u64_mul_add_to_512a(&tmp[2], a, b[2]);
    cu_u64_mul_add_to_512a(&tmp[3], a, b[3]);

    asm volatile(
        "// tmp[4-7] * 10..03d1 +  tmp[0-4] -> tmp[0-4]\n\t"
        "mad.lo.cc.u64   %0, %4, 0x1000003d1, %0;\n\t"
        "madc.hi.cc.u64  %1, %4, 0x1000003d1, %1;\n\t"

        "// Накопление со сдвигом по цепочке\n\t"
        "madc.lo.cc.u64  %2, %6, 0x1000003d1, %2;\n\t"
        "madc.hi.cc.u64  %3, %6, 0x1000003d1, %3;\n\t"

        "madc.lo.cc.u64  %1, %5, 0x1000003d1, %1;\n\t"
        "madc.hi.cc.u64  %2, %5, 0x1000003d1, %2;\n\t"

        "madc.lo.cc.u64  %3, %7, 0x1000003d1, %3;\n\t"
        "madc.hi.cc.u64  %4, %7, 0x1000003d1, 0;\n\t"

    : "+l"(tmp[0]), "+l"(tmp[1]), "+l"(tmp[2]), "+l"(tmp[3]), "+l"(tmp[4])
    : "l"(tmp[5]), "l"(tmp[6]), "l"(tmp[7])
    );

    asm volatile(
        "// tmp[4]  нужно умножить на 1000003d1 и добавить в tmp\n\t"
        "mad.lo.cc.u64  %0, %4, 0x1000003d1, %0;\n\t"
        "madc.hi.cc.u64  %%rd0, %4, 0x1000003d1, 0;\n\t"
        "addc.cc.u64    %1, %1, %%rd0;\n\t"
        "addc.cc.u64    %2, %2, 0;\n\t"
        "addc.cc.u64    %3, %3, 0;\n\t"
        "addc.u64       %4,  0, 0;\n\t"
        : "+l"(tmp[0]), "+l"(tmp[1]), "+l"(tmp[2]), "+l"(tmp[3]), "+l"(tmp[4])
    );

    if (tmp[4] != 0)
        carry = cu_u64_add_3d1_inplace(tmp);

    carry = cu_u64_add_3d1_with_carry(res, tmp);
    if (carry == 1) {
        return;
    }
    cu_u64_set(res, tmp);
}

__global__ void test_kernel_mul_mod_64_a(Data * dev_r, Data * dev_a, const Data * dev_b) {
    uint_fast64_t tid = threadIdx.x + blockIdx.x * blockDim.x;
    if (tid >= CUDA_NUM_THREADS*CUDA_NUM_BLOCKS) return;
    uint64_t a[4],b[4],r[4];

    for (uint i = 0; i < CUDA_STRIDE; i++) {
        uint64_t idx = tid*CUDA_STRIDE+i;
        cu_u64_set(a, dev_a[idx].w64);
        cu_u64_set(b, dev_b[idx].w64);
        for (uint j = 0; j < T_SIZE; j++) {
            cu_u64_mul_mod_pa(r, a, b);
            a[0] = a[0] ^ (r[0] & 0x00000000ffffffffULL);
        }
        cu_u64_set(dev_r[idx].w64, r);
    }
}

Теперь еще для полноты картины main.cu и хедер

main.cu

void test_mul_mod() {

    timespec start{}, end{};

    cudaError_t code;

    long long start_ns;
    long long end_ns;
    long long diff_ns;

    gmp_randstate_t state;
    gmp_randinit_default(state);
    const unsigned long seed = std::chrono::system_clock::now().time_since_epoch().count();
    gmp_randseed_ui(state, seed);

    Data *a = (Data *)malloc(sizeof(Data) * N_SIZE);
    Data *b = (Data *)malloc(sizeof(Data) * N_SIZE);
    Data *r32a = (Data *)malloc(sizeof(Data) * N_SIZE);
    Data *r64a = (Data *)malloc(sizeof(Data) * N_SIZE);
    Data *r32b = (Data *)malloc(sizeof(Data) * N_SIZE);
    Data *r64b = (Data *)malloc(sizeof(Data) * N_SIZE);

    mpz_t gmp_a, gmp_b, gmp_r, gmp_t, P;
    mpz_inits(gmp_a, gmp_b, P, gmp_r, gmp_t, nullptr);

    for (int j = 0; j < N_SIZE; j++) {
        mpz_urandomm(gmp_a, state, P);
        mpz_urandomm(gmp_b, state, P);
        mpz_32_to_uint256(gmp_a, a[j].w32);
        mpz_32_to_uint256(gmp_b, b[j].w32);
    }

    // Выделяем память на устройстве
    Data *dev_a, *dev_r32a, *dev_r64a = nullptr, *dev_r32b, *dev_r64b= nullptr;
    Data *dev_b;

    CUDA_CHECK(cudaMalloc(&dev_a, N_SIZE * sizeof(Data)));
    CUDA_CHECK(cudaMalloc(&dev_b, N_SIZE * sizeof(Data)));
    CUDA_CHECK(cudaMalloc(&dev_r32a, N_SIZE * sizeof(Data)));
    CUDA_CHECK(cudaMalloc(&dev_r64a, N_SIZE * sizeof(Data)));
    CUDA_CHECK(cudaMalloc(&dev_r32b, N_SIZE * sizeof(Data)));
    CUDA_CHECK(cudaMalloc(&dev_r64b, N_SIZE * sizeof(Data)));

    for (int j = 0; j < N_SIZE; j++) {
        r32a[j].w64[0] = 0;
        r32a[j].w64[1] = 0;
        r32a[j].w64[2] = 0;
        r32a[j].w64[3] = 0;
        r32b[j].w64[0] = 0;
        r32b[j].w64[1] = 0;
        r32b[j].w64[2] = 0;
        r32b[j].w64[3] = 0;
        r64b[j].w64[0] = 0;
        r64a[j].w64[1] = 0;
        r64a[j].w64[2] = 0;
        r64a[j].w64[3] = 0;
        r64b[j].w64[0] = 0;
        r64b[j].w64[1] = 0;
        r64b[j].w64[2] = 0;
        r64b[j].w64[3] = 0;
    }

    CUDA_CHECK(cudaMemcpy(dev_a, a, N_SIZE * sizeof(Data), cudaMemcpyHostToDevice));
    CUDA_CHECK(cudaMemcpy(dev_b, b, N_SIZE * sizeof(Data), cudaMemcpyHostToDevice));

    // Запуск ядра 32a

    clock_gettime(CLOCK_MONOTONIC, &start);
    test_kernel_mul_mod_32a<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r32a, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра
    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 32a bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r32a, dev_r32a, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

// ~~~~~~~~~~~~~~ //

    // Запуск ядра 32b

    clock_gettime(CLOCK_MONOTONIC, &start);
    test_kernel_mul_mod_32b<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r32b, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра
    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 32b bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r32b, dev_r32b, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

    // ~~~~~~~~~~~~~~ //

    // Запуск ядра 64a
    clock_gettime(CLOCK_MONOTONIC, &start);

    test_kernel_mul_mod_64_a<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r64a, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра

    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 64a bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r64a, dev_r64a, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

    // ~~~~~~~~~~~~~~ //
    // Запуск ядра 64b

    clock_gettime(CLOCK_MONOTONIC, &start);
    test_kernel_mul_mod_64_b<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r64b, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра

    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 64b bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r64b, dev_r64b, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

    // ~~~~~~~~~~~~~~ //
    // Запуск ядра 32a
    clock_gettime(CLOCK_MONOTONIC, &start);

    test_kernel_mul_mod_32a<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r32a, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра
    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 32a bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r32a, dev_r32a, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

// ~~~~~~~~~~~~~~ //
// Запуск ядра 32b

    clock_gettime(CLOCK_MONOTONIC, &start);
    test_kernel_mul_mod_32b<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r32b, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра
    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 32b bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r32b, dev_r32b, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

// ~~~~~~~~~~~~~~ //
// Запуск ядра 64a
    clock_gettime(CLOCK_MONOTONIC, &start);

    test_kernel_mul_mod_64_a<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r64a, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра

    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 64a bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r64a, dev_r64a, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

// ~~~~~~~~~~~~~~ //
// Запуск ядра 64b

    clock_gettime(CLOCK_MONOTONIC, &start);
    test_kernel_mul_mod_64_b<<<CUDA_NUM_THREADS, CUDA_NUM_BLOCKS>>>(dev_r64b, dev_a, dev_b);
    code = cudaGetLastError();
    if (code != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s %s %d\n", cudaGetErrorString(code), __FILE__, __LINE__);
        exit(code);
    }
    CUDA_CHECK(cudaDeviceSynchronize()); // ждём и проверяем ошибку ядра

    clock_gettime(CLOCK_MONOTONIC, &end);
    start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
    end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
    diff_ns = end_ns - start_ns;
    printf("mul_mod 64b bit время GPU: %lld нс (%.6f мс)  точек %d\n", diff_ns, (diff_ns / 1e6),  N_SIZE);

    // Получение результата
    CUDA_CHECK(cudaMemcpy(r64b, dev_r64b, N_SIZE * sizeof(Data), cudaMemcpyDeviceToHost));

    // ~~~~~~~~~~~~~~ //

    for (uint i = 0; i < N_SIZE; i++) {

        if ((
            r64a[i].w64[3] ^ r64b[i].w64[3] |
            r64a[i].w64[2] ^ r64b[i].w64[2] |
            r64a[i].w64[1] ^ r64b[i].w64[1] |
            r64a[i].w64[0] ^ r64b[i].w64[0]) == 0
            ) {
            continue;
        }
        printf("Error \n");
    }
    for (uint i = 0; i < min(4, N_SIZE); i++) {
        u32_printf("r32a ", r32a[i].w32);
        u32_printf("r32b ", r32a[i].w32);
        u64_printf("r64a ", r64a[i].w64);
        u64_printf("r64b ", r64a[i].w64);
    }


    CUDA_CHECK(cudaFree(dev_a));
    CUDA_CHECK(cudaFree(dev_b));
    CUDA_CHECK(cudaFree(dev_r64a));
    CUDA_CHECK(cudaFree(dev_r32a));
    CUDA_CHECK(cudaFree(dev_r64b));
    CUDA_CHECK(cudaFree(dev_r32b));
}

int main() {
    test_mul_mod();
    return (0);
}

Результат получился совсем не тот, что ожидал. Дефолтный алгоритм с базой 64 бита оказался быстрее всех. Последние строки для визуально контроля результатов разных программ. Множители одинаковые и случайные для каждого экземпляра программы и грузятся один раз.

mul_mod 32a bit время GPU: 1784910771 нс (1784.910771 мс)  точек 4194304
mul_mod 32b bit время GPU: 2278021284 нс (2278.021284 мс)  точек 4194304
mul_mod 64a bit время GPU: 2635622032 нс (2635.622032 мс)  точек 4194304
mul_mod 64b bit время GPU: 1536215011 нс (1536.215011 мс)  точек 4194304
mul_mod 32a bit время GPU: 1739873265 нс (1739.873265 мс)  точек 4194304
mul_mod 32b bit время GPU: 2277945774 нс (2277.945774 мс)  точек 4194304
mul_mod 64a bit время GPU: 2635468058 нс (2635.468058 мс)  точек 4194304
mul_mod 64b bit время GPU: 1541266850 нс (1541.266850 мс)  точек 4194304
r32a 718620095f369811cbbbb4593c894bfc2adca86e5a50abb4a0dae1677b73aa60
r32b 718620095f369811cbbbb4593c894bfc2adca86e5a50abb4a0dae1677b73aa60
r64a 718620095f369811cbbbb4593c894bfc2adca86e5a50abb4a0dae1677b73aa60
r64b 718620095f369811cbbbb4593c894bfc2adca86e5a50abb4a0dae1677b73aa60
r32a 9580d7384bdd722df8d7614eb65fca9d51acb13aea79f1cbff8da83d19a5716c
r32b 9580d7384bdd722df8d7614eb65fca9d51acb13aea79f1cbff8da83d19a5716c
r64a 9580d7384bdd722df8d7614eb65fca9d51acb13aea79f1cbff8da83d19a5716c
r64b 9580d7384bdd722df8d7614eb65fca9d51acb13aea79f1cbff8da83d19a5716c
r32a ed452e82729b459144326bc75124b3088caaeca4ccfd806a1900c094a83c5613
r32b ed452e82729b459144326bc75124b3088caaeca4ccfd806a1900c094a83c5613
r64a ed452e82729b459144326bc75124b3088caaeca4ccfd806a1900c094a83c5613
r64b ed452e82729b459144326bc75124b3088caaeca4ccfd806a1900c094a83c5613
r32a bb4ee241c7154a1ec0e34e9ca29f05c0b394a800934ca85a8e58e782ccc5884f
r32b bb4ee241c7154a1ec0e34e9ca29f05c0b394a800934ca85a8e58e782ccc5884f
r64a bb4ee241c7154a1ec0e34e9ca29f05c0b394a800934ca85a8e58e782ccc5884f
r64b bb4ee241c7154a1ec0e34e9ca29f05c0b394a800934ca85a8e58e782ccc5884f

Удивительные тонкости мира CUDA.

Поведение NVIDIA RTX 500 Ada Generation Laptop GPU оказалось весьма хитрым и, наверно, чип обладает своим каким-то неведомым искусственным интеллектом. Если закомментировать строку // a[0] = a[0] ^ (r[0] & 0x00000000ffffffffULL); во всех вариантах, то с теми же размерностями результат такой

mul_mod 32a bit время GPU: 534502115 нс (534.502115 мс)  точек 4194304
mul_mod 32b bit время GPU: 179423263 нс (179.423263 мс)  точек 4194304
mul_mod 64a bit время GPU: 35610833 нс (35.610833 мс)  точек 4194304
mul_mod 64b bit время GPU: 8107617 нс (8.107617 мс)  точек 4194304
mul_mod 32a bit время GPU: 472195051 нс (472.195051 мс)  точек 4194304
mul_mod 32b bit время GPU: 179256781 нс (179.256781 мс)  точек 4194304
mul_mod 64a bit время GPU: 35555436 нс (35.555436 мс)  точек 4194304
mul_mod 64b bit время GPU: 8095543 нс (8.095543 мс)  точек 4194304

Строка эта не может использовать столько времени, это точно результат оптимизатора. Данные в том же формате, операций сложения/умножения столько же.

Обращений в файлах .ptx к глобальной памяти командой ld.global.u64 в 64 бит вариантах всего 8, в 32 бит всего 16. Т.е. изменение времени не из-за обращений к медленной глобальной памятию

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

Если сделать T_SIZE = 1, т.е. убрать цикл и повторять один раз, то картина совсем другая, они все выполняются приблизительно одинаково и вариант "а" быстрее немного.

mul_mod 32a bit время GPU: 8847056 нс (8.847056 мс)  точек 4194304
mul_mod 32b bit время GPU: 8656183 нс (8.656183 мс)  точек 4194304
mul_mod 64a bit время GPU: 8132406 нс (8.132406 мс)  точек 4194304
mul_mod 64b bit время GPU: 8190300 нс (8.190300 мс)  точек 4194304
mul_mod 32a bit время GPU: 8304138 нс (8.304138 мс)  точек 4194304
mul_mod 32b bit время GPU: 8612442 нс (8.612442 мс)  точек 4194304
mul_mod 64a bit время GPU: 8081219 нс (8.081219 мс)  точек 4194304
mul_mod 64b bit время GPU: 8102045 нс (8.102045 мс)  точек 4194304

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

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.