メモリ間コピーをXbyakを使ってAVXで書き直してみた。結果から言うとパフォーマンス的にはあまり変わらなかった。もともとSIMDに適した部分ではないのでしょうがないが、勉強にはなった。
// Xbyakでのメモリコピーコード
video_writer::copy_image::copy_image(uint32_t width, uint32_t height, uint32_t pitch)
{
uint32_t w_byte = width * 4 / 16;
uint32_t index = 0;
std::function<void()> funcs[3] =
{
[this] { mov16(); },
[this] { mov32(); },
[this] { mov64(); }
};
for (; index < 2; ++index){
if ((w_byte & 1) == 1)
{
break;
}
else {
w_byte = w_byte / 2;
}
}
mov(r8, w_byte);
mov(r9, height);
mov(r10, pitch * 2);
L("L2");
mov(rax, r8);
L("L1");
funcs[index]();
sub(rax, 1);
jnz("L1");
sub(rcx, r10);
sub(r9, 1);
jnz("L2");
vzeroupper();
ret();
}
void video_writer::copy_image::mov16()
{
vmovdqa(xmm0, ptr[rcx]);
vmovdqa(ptr[rdx], xmm0);
add(rcx, 16);
add(rdx, 16);
}
void video_writer::copy_image::mov32()
{
vmovdqa(xmm0, ptr[rcx]);
vmovdqa(xmm1, ptr[rcx + 16]);
vmovdqa(ptr[rdx], xmm0);
vmovdqa(ptr[rdx + 16], xmm1);
add(rcx, 32);
add(rdx, 32);
}
void video_writer::copy_image::mov64()
{
vmovdqa(xmm0, ptr[rcx]);
vmovdqa(xmm1, ptr[rcx + 16]);
vmovdqa(ptr[rdx], xmm0);
vmovdqa(xmm2, ptr[rcx + 32]);
vmovdqa(ptr[rdx + 16], xmm1);
vmovdqa(xmm3, ptr[rcx + 48]);
vmovdqa(ptr[rdx + 32], xmm2);
add(rcx, 64);
vmovdqa(ptr[rdx + 48], xmm3);
add(rdx, 64);
}
(中略)
// video_writerコンストラクタ内のコード
// video_writer::copy_imageの生成とアセンブラコードの取得
sf::map<> map(context,texture, 0, D3D11_MAP_READ, 0);
copy_image_.reset(new video_writer::copy_image(width_, height_, map.row_pitch()));
copy_func_ = (copy_func_t)copy_image_->getCode();
//
(中略)
#define SF_AVX
// テクスチャをメディアバッファに書き込む
void video_writer::set_texture_to_sample()
{
// タイムスタンプの設定
CHK(sample_->SetSampleTime(video_sample_time_));
// 書き込み先バッファのロック
sf::media_buffer_lock<> buffer(buffer_);
// 読み込みテクスチャをマップ
sf::map<> map(context_, texture_, 0, D3D11_MAP_READ, 0);
//CHK(MFCopyImage(pbBuffer, width_ * 4, reinterpret_cast<BYTE*>(mapped.pData), mapped.RowPitch, width_ * 4, height_));
#ifndef SF_AVX
// Xbyak で書く前のコード
DWORD *dest = (DWORD*) buffer.buffer();
const UINT pitch = map.row_pitch() / sizeof(uint32_t);
DWORD *src = (DWORD*) map.data() + (height_ - 1) * pitch;
for (UINT i = 0; i < height_; ++i){
for (UINT j = 0; j < width_; ++j){
*dest++ = *src++;
}
src -= pitch * 2;
}
#else
// Xbyakで書き換えた後のコード
void * src = (uint8_t*)map.data() + (height_ - 1) * map.row_pitch();
(copy_func_)(src,buffer.buffer());
#endif
video_sample_time_ += hnsSampleDuration;
}
上記はコードの抜粋である。メモリコピー部分をXbyakで生成した関数呼び出しに置き換えている。Xbyak内ではメモリコピーのループ回数を減らすためにXMMレジスタを使って最大1回のループあたり64バイト単位のコピーとなるようにしている。
実行した結果はわずかにAVX版のほうが速いかな?といったレベルである。
【AVX】 Encoding Time: 20.8708 sec Encoding Time: 21.0718 sec Encoding Time: 20.9125 sec Encoding Time: 21.1056 sec Encoding Time: 20.9764 sec 【単なるメモリコピー】 Encoding Time: 21.1463 sec Encoding Time: 21.012 sec Encoding Time: 21.2331 sec Encoding Time: 21.5062 sec Encoding Time: 20.9966 sec
FFTとかレンダリング、MP4ファイルへの書き込みのほうが時間がかかるのだからメモリコピーを最適化したことによる効果は少ないと思っていたけども、もうちょっと速くなってくれると嬉しかったのだが。
だいたいAVXについては試行錯誤できるくらいの知識はついたように思うので、実際処理時間がかかっていてかつ効果のありそうなところをXbyakを使って書き直してみようかなと思っている。