【Xcode15/GPUプロファイリング】Instrumentsで計測したGPUカウンタをテキスト出力する
開発環境
- Unity 2022.3.21f1
- Xcode 15.3
- macOS Sonoma バージョン14.4.1
- Python 3.9.6
はじめに
Instrumentsで計測したGPUカウンターの値を、スプレッドシートなど外部ツールで分析したいことがよくあります。
しかし、InstrumentsのGUI操作では、計測値をエクスポートすることができません。
本記事では、Instrumentsで生成されたtraceファイルから、
コマンドラインを用いてGPUカウンターのデータを抽出する方法について詳しく解説します。
GPU Counter
エクスポートの流れ
-
xctrace
コマンドを使用して、GPUカウンターの値をXMLファイルへ出力 - Pythonを利用して、必要な数値だけを抽出してテキスト形式へ整形
xctraceコマンドを使用して、GPUトレースをXML出力
xctrace
コマンドを使用することで、traceデータから指定のデータを抽出することができます。
xctrace export [options]
コマンドの使い方の確認 (helpオプションの指定)
helpオプションを指定すると、コマンドの使い方が確認できます。
xctrace help export
usage: xctrace export [<options>] [--toc | --xpath expression]
description:
Export given .trace using supplied query to the XML file format that can be later read and post-processed
options:
--input <file> Export data from the given .trace file
--output <path> Command output is written to the given path, if specified
--toc Present entities to export in the table of contents form
--xpath <expression> Choose elements to export using specified XPath expression
--har Export data as the HTTP Archive file
global options:
--quiet Make terminal ouput less verbose
notes:
If output path is not specified, the export operation output will be written to the standard output.
Table of Contents and XPath query are two separate modes and they cannot be specified together.
examples:
xctrace export --input input.trace --toc
xctrace export --input input.trace --toc --output table_of_contents.xml
xctrace export --input input.trace --xpath '/trace-toc/run[@number="1"]/data/table[@schema="my-table-schema"]'
手順1. テーブル一覧の抽出
以下のコマンドを叩き、input.trace
ファイルからtableの一覧を抽出します。
xctrace export --input input.trace --toc --output table_of_contents.xml
テーブル一覧の確認
table_of_contents.xml
を開くと、さまざまなテーブルがずらっと並んでいるかと思います。
Instrumentsで計測したあらゆるデータがここに並んでいます。
table_of_contents.xml
今回欲しいのはGPUカウンタですので、ファイル内検索でgpu-counter
で絞り込んでみましょう。
以下の二つのテーブルがヒットするかと思います。
- schema="gpu-counter-info"
- schema="gpu-counter-value"
<table shader-profiler="0" counter-profile="3" schema="gpu-counter-info" counter-device="xxx"/>
<table schema="gpu-counter-value"/>
gpu-counter-info
には、GPUカウンターの情報が格納されています。
gpu-counter-value
には、GPUカウンターの計測値が格納されています。
gpu-counter-infoの抽出
以下のコマンドを叩くと、gpu-counter-info
テーブルを取り出すことができます。
取り出したテーブルは gpu-counter-info.xml
へ保存されます。
xctrace export --input input.trace --xpath '/trace-toc/run[@number="1"]/data/table[@schema="gpu-counter-info"]' > gpu-counter-info.xml
schemaの確認
schema要素を見ると、各列の情報が確認できます。
2番目の列に Counter ID が、 3番目の列には Name が入っています。
このschemaはどのrow要素に何の値が入るかを定義したものとなっています。
<schema name="gpu-counter-info">
<col>
<mnemonic>timestamp</mnemonic>
<name>Timestamp</name>
<engineering-type>event-time</engineering-type>
</col>
<col>
<mnemonic>counter-id</mnemonic>
<name>Counter ID</name>
<engineering-type>uint32</engineering-type>
</col>
<col>
<mnemonic>name</mnemonic>
<name>Name</name>
<engineering-type>gpu-counter-name</engineering-type>
</col>
<col>
<mnemonic>max-value</mnemonic>
<name>Max Value</name>
<engineering-type>uint64</engineering-type>
</col>
<col>
<mnemonic>accelerator-id</mnemonic>
<name>Accelerator ID</name>
<engineering-type>uint64</engineering-type>
</col>
<col>
<mnemonic>description</mnemonic>
<name>Description</name>
<engineering-type>string</engineering-type>
</col>
<col>
<mnemonic>group-index</mnemonic>
<name>Group Index</name>
<engineering-type>uint32</engineering-type>
</col>
<col>
<mnemonic>type</mnemonic>
<name>Type</name>
<engineering-type>string</engineering-type>
</col>
<col>
<mnemonic>ring-buffer-count</mnemonic>
<name>Ring Buffer Count</name>
<engineering-type>uint32</engineering-type>
</col>
<col>
<mnemonic>require-weighted-accumulation</mnemonic>
<name>Require Weighted Accumulation</name>
<engineering-type>boolean</engineering-type>
</col>
<col>
<mnemonic>sample-interval</mnemonic>
<name>Sample Interval</name>
<engineering-type>uint32</engineering-type>
</col>
</schema>
rowの確認
row要素は20個ほど並んでおり、それぞれがGPUカウンターを表しています。
例えば、以下のrow要素を見てみましょう。
これはBuffer Write LimiterというGPUカウンターを表しており、
2番目の要素にはCounter ID (7) が格納されていることがわかります。
<row>
<event-time ref="1"/>
<uint32 id="27" fmt="7">7</uint32>
<gpu-counter-name id="28" fmt="Buffer Write Limiter">Buffer Write Limiter</gpu-counter-name>
<uint64 ref="4"/>
<uint64 ref="5"/>
<string id="29" fmt="Measures the time during which buffer stores are attempted to execute as a percentage of peak buffer load performance.">Measures the time during which buffer stores are attempted to execute as a percentage of peak buffer load performance.</string>
<uint32 ref="2"/>
<string ref="7"/>
<uint32 ref="8"/>
<boolean ref="9"/>
<uint32 ref="2"/>
</row>
カウンター名とカウンターIDは1:1に対応しており、これを表にまとめると以下のようになります。
Counter ID | Counter Name |
---|---|
0 | F32 Utilization |
1 | F16 Utilization |
2 | Texture Sample Limiter |
3 | Texture Filtering Limiter |
4 | Texture Cache Limiter |
5 | Texture Write Limiter |
6 | Buffer Read Limiter |
7 | Buffer Write Limiter |
8 | Threadgroup/Imageblock Load Limiter |
9 | Threadgroup/Imageblock Store Limiter |
10 | Fragment Input Interpolation Limiter |
11 | GPU Last Level Cache Limiter |
12 | Vertex Occupancy |
13 | Fragment Occupancy |
14 | Compute Occupancy |
15 | GPU Read Bandwidth |
16 | GPU Write Bandwidth |
17 | MMU Limiter |
18 | MMU Utilization |
19 | Partial Renders Count |
手順2. gpu-counter-valueの抽出
以下のコマンドを叩くと、gpu-counter-value
テーブルを取り出すことができます。
取り出したテーブルは gpu-counter-value.xml
へ保存されます。
(出力されるxmlの行数は数十万 ~ 数千万行と、とてつもなく長いです)
xctrace export --input input.trace --xpath '/trace-toc/run[@number="1"]/data/table[@schema="gpu-counter-value"]' > gpu-counter-value.xml
row要素の中の値は、以下のような値が格納されています。
row | 概要 | 備考 |
---|---|---|
1番目 | タイムスタンプ | 時間の単位はnsfmt にはInstruments上で確認できる値が入っている |
2番目 | Counter ID | Counterの名前はgpu-counter-value に格納されている |
3番目 | GPUカウンターの計測値 |
<?xml version="1.0"?>
<trace-query-result>
<node xpath='//trace-toc[1]/run[1]/data[1]/table[94]'><schema name="gpu-counter-value"><col><mnemonic>timestamp</mnemonic><name>Timestamp</name><engineering-type>event-time</engineering-type></col><col><mnemonic>counter-id</mnemonic><name>Counter ID</name><engineering-type>uint32</engineering-type></col><col><mnemonic>value</mnemonic><name>Value</name><engineering-type>fixed-decimal</engineering-type></col><col><mnemonic>accelerator-id</mnemonic><name>Accelerator ID</name><engineering-type>uint64</engineering-type></col><col><mnemonic>sample-index</mnemonic><name>Sample ID</name><engineering-type>uint32</engineering-type></col><col><mnemonic>ring-buffer-index</mnemonic><name>Ring Buffer Index</name><engineering-type>uint32</engineering-type></col></schema>
<row><event-time id="1" fmt="00:00.091.975">91975333</event-time><uint32 id="2" fmt="0">0</uint32><fixed-decimal id="3" fmt="0.0000">0.000000000</fixed-decimal><uint64 id="4" fmt="4,294,967,919">4294967919</uint64><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="5" fmt="1">1</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="6" fmt="2">2</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="7" fmt="3">3</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="8" fmt="4">4</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="9" fmt="5">5</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="10" fmt="6">6</uint32><fixed-decimal id="11" fmt="0.2058">0.205807956</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="12" fmt="7">7</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="13" fmt="8">8</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="14" fmt="9">9</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="15" fmt="10">10</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="16" fmt="11">11</uint32><fixed-decimal id="17" fmt="29.4304">29.430373509</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="18" fmt="12">12</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="19" fmt="13">13</uint32><fixed-decimal id="20" fmt="0.1162">0.116167340</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="21" fmt="14">14</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="22" fmt="15">15</uint32><fixed-decimal id="23" fmt="0.0034">0.003368421</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="24" fmt="16">16</uint32><fixed-decimal id="25" fmt="1.2061">1.206063157</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="26" fmt="17">17</uint32><fixed-decimal id="27" fmt="30.7648">30.764758056</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="28" fmt="18">18</uint32><fixed-decimal id="29" fmt="24.7452">24.745244899</fixed-decimal><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time ref="1"/><uint32 id="30" fmt="19">19</uint32><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="2"/><uint32 ref="2"/></row>
<row><event-time id="31" fmt="00:00.092.131">92131833</event-time><uint32 ref="2"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="5"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="6"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="7"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="8"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="9"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="10"/><fixed-decimal id="32" fmt="0.2085">0.208497375</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="12"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="13"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="14"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="15"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="16"/><fixed-decimal id="33" fmt="20.0945">20.094488188</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="18"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="19"/><fixed-decimal id="34" fmt="0.1172">0.117197752</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="21"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="22"/><fixed-decimal id="35" fmt="0.0283">0.028330392</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="24"/><fixed-decimal id="36" fmt="8.3427">8.342684843</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="26"/><fixed-decimal id="37" fmt="20.0472">20.047244094</fixed-decimal><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="28"/><fixed-decimal ref="37"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
<row><event-time ref="31"/><uint32 ref="30"/><fixed-decimal ref="3"/><uint64 ref="4"/><uint32 ref="5"/><uint32 ref="2"/></row>
手順3. GPUカウンターの値をテキストファイルへ出力する
ここからが本題になりますが、GPUカウンターの値を外部へ持っていくためにはXMLを自分で解析して出力してあげる必要があります。
(GPUカウンターの値をXML以外の形式で出力する方法を見つけることができませんでした)
今回は、Python3を用いてXMLを解析してテキストファイルとして出力することにしました。
以下のpyファイルを、二つのxmlファイル(gpu-counter-info.xml
, gpu-counter-value.xml
)と同じ階層に配置します。
import xml.etree.ElementTree as ET
counters = {}
counterInfoRoot = ET.parse('gpu-counter-info.xml').getroot()
# GPUカウンタの名前を取得
rows = counterInfoRoot.findall(".//row")
counter_names={}
for i in range(0, len(rows)):
row = rows[i]
row_children = list(row)
counter_id = i
counter_name = row_children[2].text
counter_names[counter_id]=counter_name
# 集計用のデータを初期化
row_count=0
for i in range(0, len(counter_names)):
counters[i] = {
"name":counter_names[i],
"id":i,
"sum":0.0,
"max":0.0,
"count":0,
"values":[]
}
counterValueRoot = ET.parse('gpu-counter-value.xml').getroot()
# schemeからCOUNTER ID を取り出す
timestamp_value=None
counter_value=None
counter_id_refs = {}
counter_id_values = {} # Counter ID の値
for row in counterValueRoot.findall(".//row"):
row_children = list(row)
timestamp_cell = row_children[0] # 時間
counter_id_cell = row_children[1] # GPUカウンター ID
value_cell = row_children[2] # GPUカウンターの値
if 'fmt' in counter_id_cell.attrib:
counter_id_ref = int(counter_id_cell.attrib['id']) # 0 ~ 30
counter_id_value = int(counter_id_cell.attrib['fmt']) # 0 ~ 19
counter_id_values[counter_id_value] = counter_id_value
counter_id_refs[counter_id_ref] = int(counter_id_value)
else:
# Timestamp
if 'fmt' in timestamp_cell.attrib:
timestamp_value = timestamp_cell.attrib['fmt']
row_count+=1
# Counter ID
if 'ref' in counter_id_cell.attrib:
counter_id_ref = int(counter_id_cell.attrib['ref'])
counter_id_value = counter_id_refs[counter_id_ref]
# Counter Value
if 'fmt' in value_cell.attrib:
counter_value = float(value_cell.attrib['fmt'])
counters[counter_id_value]["values"].append(counter_value)
counters[counter_id_value]["sum"] += counter_value
counters[counter_id_value]["count"] += 1
if ("max" in counters[counter_id_value]):
counters[counter_id_value]["max"] = max(counter_value, counters[counter_id_value]["max"])
else:
counters[counter_id_value]["max"] = counter_value
# 出力
print(f'\tAVG\tMAX') # ヘッダー
for i in counter_id_values:
counter = counters[i]
counter_name = counter["name"]
sum = counter["sum"]
count = counter["count"]
values = counter["values"]
max = counter["max"]
if (count == 0):
avg = 0
else:
avg = sum / row_count
print(f'{counter_name}\t{avg}\t{max}')
コマンドラインからpythonファイルを実行し、txtファイルへ出力します。
python3 collect_counters.py > result.txt
出力結果
コピー&ペーストでGoogleスプレッドシートに貼り付けられることができます。
AVG MAX
F32 Utilization 14.15747796208535 22.5599
F16 Utilization 0.026617969984202204 1.9656
Texture Sample Limiter 78.23507310426538 99.8415
Texture Filtering Limiter 28.303441232227502 45.0571
Texture Cache Limiter 79.08311180884662 99.995
Texture Write Limiter 11.465882424960522 25.7937
Buffer Read Limiter 0.02923882306477092 0.2086
Buffer Write Limiter 0.0016479462875197458 0.0379
Threadgroup/Imageblock Load Limiter 0.013851658767772513 1.1633
Threadgroup/Imageblock Store Limiter 5.018091311216418 9.0984
Fragment Input Interpolation Limiter 7.076718404423376 11.1025
GPU Last Level Cache Limiter 68.80119206161135 88.168
Vertex Occupancy 0.0004071484992101104 0.0135
Fragment Occupancy 79.75370173775647 99.9522
Compute Occupancy 4.739336492890995e-07 0.0005
GPU Read Bandwidth 13.1058484992101 35.6089
GPU Write Bandwidth 2.6124018562401297 12.5913
MMU Limiter 84.83115106635047 99.9209
MMU Utilization 38.195075947867245 58.0024
Partial Renders Count 0 0.0
Instrument上で確認できる数値と比較すると、値が同じであることが確認できます。
関連記事
Discussion