なぜ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倍速くなりました。
速度アップの方法がもう一つあります。