#include "nmod.h"
#include "nmod_mat.h"
#include "profiler.h"
#include "cblas.h"
#define BITS 20
#define N 64
void
nmod_mmul_ulong(mp_ptr res, mp_srcptr a, mp_srcptr b, nmod_t mod)
{
slong i, j, k;
ulong s0, s1, s2, s3;
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++)
{
s0 = s1 = s2 = s3 = 0;
for (k = 0; k < N; k += 4)
{
s0 += a[N * i + k + 0] * b[N * j + k + 0];
s1 += a[N * i + k + 1] * b[N * j + k + 1];
s2 += a[N * i + k + 2] * b[N * j + k + 2];
s3 += a[N * i + k + 3] * b[N * j + k + 3];
}
#if 0
s0 += s1;
s2 += s3;
s0 += s2;
res[N * i + j] = nmod_set_ui(s0, mod);
#else
/* if we want more bits */
add_ssaaaa(s1, s0, 0, s0, 0, s1);
add_ssaaaa(s3, s2, 0, s2, 0, s3);
add_ssaaaa(s1, s0, s1, s0, s3, s2);
NMOD2_RED2(s0, s1, s0, mod);
res[N * i + j] = s0;
#endif
}
}
}
void
nmod_mmul_double(mp_ptr res, const double * a, const double * b, nmod_t mod)
{
slong i, j, k;
double s0, s1, s2, s3;
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++)
{
s0 = s1 = s2 = s3 = 0;
for (k = 0; k < N; k += 4)
{
s0 += a[N * i + k + 0] * b[N * j + k + 0];
s1 += a[N * i + k + 1] * b[N * j + k + 1];
s2 += a[N * i + k + 2] * b[N * j + k + 2];
s3 += a[N * i + k + 3] * b[N * j + k + 3];
}
s0 += s1;
s2 += s3;
s0 += s2;
res[N * i + j] = nmod_set_ui((ulong) s0, mod);
}
}
}
int main()
{
mp_ptr A, B, C;
double * D, *E, *F;
ulong p = (UWORD(1) << BITS) - 1;
nmod_t mod;
nmod_init(&mod, p);
nmod_mat_t X, Y, Z;
flint_printf("bits = %wd, N = %wd\n\n", BITS, N);
nmod_mat_init(X, N, N, mod.n);
nmod_mat_init(Y, N, N, mod.n);
nmod_mat_init(Z, N, N, mod.n);
A = flint_malloc(sizeof(mp_limb_t) * N * N);
B = flint_malloc(sizeof(mp_limb_t) * N * N);
C = flint_malloc(sizeof(mp_limb_t) * N * N);
D = flint_malloc(sizeof(mp_limb_t) * N * N);
E = flint_malloc(sizeof(mp_limb_t) * N * N);
F = flint_malloc(sizeof(mp_limb_t) * N * N);
slong i, j;
flint_rand_t state;
flint_randinit(state);
nmod_mat_randfull(X, state);
nmod_mat_randfull(Y, state);
nmod_mat_randfull(Z, state);
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
{
A[i * N + j] = X->rows[i][j];
B[j * N + i] = Y->rows[i][j];
D[i * N + j] = X->rows[i][j];
E[j * N + i] = Y->rows[i][j];
}
flint_printf("\nnmod_mat_mul:\n");
TIMEIT_START
nmod_mat_mul(Z, X, Y);
TIMEIT_STOP
flint_printf("\nulong:\n");
TIMEIT_START
nmod_mmul_ulong(C, A, B, mod);
TIMEIT_STOP
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (Z->rows[i][j] != C[i * N + j])
{ flint_printf("WRONG RESULT\n"); goto cl0; }
cl0:
flint_printf("\ndouble:\n");
TIMEIT_START
nmod_mmul_double(C, D, E, mod);
TIMEIT_STOP
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (Z->rows[i][j] != C[i * N + j])
{ flint_printf("WRONG RESULT\n"); goto cl1; }
cl1:
flint_printf("\nBLAS:\n");
TIMEIT_START
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasTrans, N, N, N, 1.0, D, N, E, N, 0.0, F, N);
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
C[N * i + j] = nmod_set_ui((ulong) F[i * N + j], mod);
TIMEIT_STOP
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (Z->rows[i][j] != C[i * N + j])
{ flint_printf("WRONG RESULT\n"); goto cl2; }
cl2:
}
Sample results on my machine: at least in some cases, we can get close to a factor 2 speedup with vectorized ulong math, and a factor 4 speedup with double. This ignores conversion and transposition costs, which may not be negligible.
bits = 20, N = 64
nmod_mat_mul:
cpu/wall(s): 0.000104 0.000105
ulong:
cpu/wall(s): 7.28e-05 7.29e-05
double:
cpu/wall(s): 2.48e-05 2.47e-05
BLAS:
cpu/wall(s): 1.89e-05 1.88e-05
bits = 30, N = 64
nmod_mat_mul:
cpu/wall(s): 0.000129 0.000129
ulong:
cpu/wall(s): 7.23e-05 7.23e-05
The basecases for functions like
nmod_mat_mul,fmpz_mat_mul,nmod_poly_mulandfmpz_poly_mulcan be sped up significantly for half-word-size entries (and maybe bigger entries with more effort) by writing vectorization-friendly code and compiling with-march=native -O3.Some naive code just for illustration:
Sample results on my machine: at least in some cases, we can get close to a factor 2 speedup with vectorized
ulongmath, and a factor 4 speedup withdouble. This ignores conversion and transposition costs, which may not be negligible.