読者です 読者をやめる 読者になる 読者になる

muranoyaのブログ

https://github.com/muranoya

パフォーマンスカウンタ

とあるAMDのマシン上で性能評価を行っていたら,最適化オプションをつけないほうが,O2最適化のバイナリよりも2倍程度早い,という事象に出くわして原因を探ろうとしていたが,よくわからない.

普段どこがボトルネックになってるとかっていう情報はprintfでgettimeofdayとか,なんらかのウォールクロック関数などを使って調べることが多くて,もっと詳細な調べ方,というのを今まで気にしてこなかった.

調べると,linuxではperfコマンドが良いらしい.早速使ってみる.

perfの使い方

perfコマンド,というよりもパフォーマンスカウンタは凄い.ありとあらゆる情報がわかるではないか.これは凄いぞ.普段,メモリリークを調査するためにvalgrindは使うが,perfは使ったことがなかった.これからは必須ツールの仲間入りだろう.

ところで,標準ライブラリにあるsqrt関数を使うと,sqrt関数呼び出しをsqrt命令に置き換える最適化をGCCは行う.次のCコードが,

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int
main(int argc, char *argv[])
{
    printf("%lf\n", sqrt(atof(argv[1])));
    return 0;
}

GCCでO2最適化を入れてコンパイルすると,

main:
.LFB41:
	.cfi_startproc
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movq	8(%rsi), %rdi
	xorl	%esi, %esi
	call	strtod@PLT
	sqrtsd	%xmm0, %xmm1
	pxor	%xmm2, %xmm2
	ucomisd	%xmm0, %xmm2
	jbe	.L2
	movsd	%xmm1, 8(%rsp)
	call	sqrt@PLT
	movsd	8(%rsp), %xmm1
.L2:
	movapd	%xmm1, %xmm0
	leaq	.LC1(%rip), %rsi
	movl	$1, %edi
	movl	$1, %eax
	call	__printf_chk@PLT
	xorl	%eax, %eax
	addq	$24, %rsp
	.cfi_def_cfa_offset 8
	ret

のようになる.
面白いと思ったのは,sqrt関数を呼び出す命令も生成している点で,これはsqrt命令で計算を行った後(xmm0レジスタの中身をsqrtし,結果はxmm1レジスタに入る.xmmはSSEで使われる128bit長のレジスタ.ちなみにAVXでは256bitのYMMレジスタ,AVX-512では512bitのZMMレジスタがある),ucomisd命令で0とsqrt前の値の比較を行っている.
ucomisd命令直後のjbe命令でジャンプするのは,フラグレジスタのCFかZFが立っているときで,ucomisd命令によりそのフラグが立つのは,比較する2つの値が比較できないか,src1 >= src2のときである.
つまり,負数やNaNなどでsqrtしたときにはsqrt関数を呼び出すようになっている.
pxor命令で同じレジスタ同士をxorしているのは,ゼロクリアのよくある方法.