なぜC++で書いたライブラリは遅いのでしょう。
Pythonのコードは、
for i in range(H): for j in range(W): C[i][j] = int(sum(A[i][k] * B[k][j] for k in range(M)) % D) return C
C++のコードは、
for(size_t i = 0U; i < H; ++i) { for(size_t j = 0U; j < W; ++j) { for(size_t k = 0U; k < M; ++k) C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % D; } }
Pythonのコードは総和を取ったあとにDの剰余を取っていますが、C++のコードは掛け算して足し算するたびにDの剰余を取っています。
なぜこうなっているかというと、競技プログラミングではDが32ビットに近い数が選ばれるため、すぐに64ビットではオーバーフローしてしまうからです。一方、Pythonでは多倍長整数に移行するため、オーバーフローを考慮しなくてよく、また多倍長整数の加算は64ビット整数の除算よりずっと速いです。
C++では64ビットより長い整数を使えばよいですが、幸いにしてGCCには__int128という128ビット整数があり、これを利用すれば速くなります。
PyObject *mul(PyObject *self, PyObject *args) { ... Matrix C = mul_core128(A, B, D); return make_python_matrix(C); } Matrix mul_core128(const Matrix& A, const Matrix& B, long D) { const size_t H = A.size(); const size_t M = B.size(); const size_t W = B.front().size(); Matrix C(H, Vec(W)); for(size_t i = 0U; i < H; ++i) { for(size_t j = 0U; j < W; ++j) { __int128 e = 0; for(size_t k = 0U; k < M; ++k) e += A[i][k] * B[k][j]; C[i][j] = (long)(e % D); } } return C; }
実行してみましょう。
1134 256330968 30.565128803253174
変更前に対して6.8倍速くなりました。
速度アップの方法がもう一つあります。