JavaScriptスレッドのパフォーマンスの調査
スレッド状態の視覚化により、アプリのライフサイクル中のスレッドの状態を視覚化できます。これは、JavaScript(JS)アプリのパフォーマンスのトラブルシューティングと最適化に役立ちます。
スレッド状態の視覚化を効果的に使用するには、このページのガイダンスに従ってください。
スレッドの状態について
スレッドには、Running、Runnable、Runnable (Preempted)、Sleeping、Uninterruptible Sleep、Stopped、Idle、Exitなど、さまざまな状態があります。各状態の正確な定義は、アプリパフォーマンスのモニタリングと記録の「スレッドの状態」を参照してください。
パフォーマンスに関する問題の原因の特定
スレッド状態の視覚化によって、レイテンシの増加、応答不能、クラッシュ、一貫性のない動作など、アプリのパフォーマンスに関する問題を把握できます。次の表は、スレッド状態の情報を使用して特定できるパフォーマンスに関する問題の原因の一部を示しています。
| 原因 | 説明 | 関連するスレッドの状態 |
|---|---|---|
| 長時間実行中のスレッド | スレッドが長期間「Running」状態のままである場合は、処理の効率が悪いか、操作が長時間実行されている可能性があり、最適化により改善される場合があります。 | Running |
| CPU使用率が高い | 多くのスレッドがアクティブに「Running」状態にある場合は、アルゴリズムの効率が悪いか、コンテキストが過剰に切り替わり、リソースに負荷がかかっている可能性があります。 | Running、Runnable |
| CPUの競合 | 複数のスレッドが限られたCPUリソースをめぐって競合すると、高レベルの競合が発生します。これにより、スレッドがプリエンプトされたり、共有リソースへのアクセスを待機したりする可能性があります。 | Runnable(Preempted) |
Vega Studio Performance Extensionの使用
Vega Studio Performance Extension(VSPE)を使用すると、Activity MonitorやMemory MonitorのRecording Viewでスレッド状態を視覚化できます。JSアプリのパフォーマンスの問題をトラブルシューティングするには、次の手順を実行します。
- Activity Monitorで記録を開始します。
- 問題のある動作をアプリ内で再現します。
- 記録を停止してRecording Viewを生成します。これには、スレッド状態の情報を含むTracesビューが含まれます。
スレッド状態データの分析
アプリのパフォーマンスの問題を特定するには、次の手順を実行します。
- Tracesビューでスレッド状態データを分析します。
- スレッド状態のデータが、スレッドの実行時間が長い、CPU使用率が高い、CPUの競合など、前述の原因のいずれかに対応しているかどうかを調べます。
ユースケース
次の例は、スレッド状態の視覚化がアプリのパフォーマンスのトラブルシューティングと最適化にどのように役立つかを示しています。
ユースケース1: 非効率的なアルゴリズムが原因で実行時間が長いスレッドのトラブルシューティング
VegaVideoAppのテスト中に、ホーム画面や詳細ページを表示したときに、応答性が低下する問題が発生しました。
問題を特定するために、問題のある動作をアプリで再現しながら、Activity Monitorで記録を開始しました。記録を停止すると、Activity Monitorによって、スレッド状態の情報を示すTracesビューを含むRecording Viewが生成されました。Tracesを調べたところ、JSスレッドが最大5秒間実行されていたことがわかりました。これは、非効率的なアルゴリズムが原因でスレッドの実行時間が長くなっていることを示しています。
CPUプロファイラーのフレームグラフを調べたところ、getClassicsメソッドが原因でDetailsScreenの実行に5秒かかっていました。
アプリのコードを見直してみると、データの並べ替えやフィルタリングを行う際のコードが非効率的であり、パフォーマンス遅延の問題が発生していることに気付きました。
export const getClassics = (): TitleData[] => {
const classics = [
{
id: '169327',
title: 'Ethereal Echos',
description: 'Lorem ipsum dolor sit amet',
mediaType: 'video',
mediaSourceType: 'url',
duration: 300,
thumbnail: tile09,
posterUrl: tile09,
videoUrl:
'https://edge-vod-media.cdn01.net/encoded/0000169/0169313/video_1880k/T7J66Z106.mp4?source=firetv&channelID=13454',
categories: ['Hits'],
channelID: '13455',
rating: '5',
releaseDate: '09/20',
format: 'MP4',
uhd: false,
secure: false,
rentAmount: '200',
},
...
...
];
// 非効率的な並べ替え
定数ソートクラシックス = (配列: TitleData[]): TitleData[] => {
const n = array.length;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - i - 1; j++) {
for (let k = 0; k < n; k++) {
if (array[k].title === 'NA') {
continue;
}
}
if (array[j].title.localeCompare(array[j + 1].title) > 0) {
// 現在の要素が次の要素より大きい場合はスワップ
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
};
// 入れ子になったループを使用した非効率的なフィルタリング
const filterClassics = (array: TitleData[], category: string): TitleData[] => {
const result: TitleData[] = [];
for (let i = 0; i < array.length; i++) {
let found = false;
for (let j = 0; j < array.length; j++) {
if (array[i].categories.includes(category) && array[i] === array[j]) {
found = true;
break;
}
}
}
return result;
};
const sortedClassics = sortClassics(classics);
const filteredClassics = filterClassics(sortedClassics, 'Hits');
return filteredClassics;
};
この問題を解決するには、不要な入れ子になったループを削除します。次に、Tracesビューに再度アクセスして、アプリのパフォーマンスが向上するかどうかを確認します。
ユースケース2: スクロールアプリのCPU使用率が高い場合のトラブルシューティング
React Native Flatlistコンポーネントを使用して、200行を異なる色で表示するシンプルなスクロールアプリを作成しました。Activity Monitorで確認すると、スクロール中にThread State Viewに「Running」の状態が頻繁に表示され、CPU使用率が高いことが示されました。CPUプロファイラーのフレームグラフでも高いアクティビティが示されました。これは、Flatlistコンポーネントが効率的に機能していないことを示唆しています。
次のコード例は、Flatlistコンポーネントがアプリにどのように追加されたかを示しています。
const PerfFlatListApp = () => {
// https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
const renderItem = useCallback(
({ item, index }: { item: FlatListItem; index: number }) => (
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-basic-components
<Item
key={item.title}
buttonStyle={[
styles.item,
{
backgroundColor: calculateBackgroundColor(index)
}
]}
hasTVPreferredFocus={item.id === 0}
/>
),
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-keyextractor-or-key
const keyExtractor = useCallback(
(item: FlatListItem, index: number) => item.title,
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-getitemlayout
const getItemLayout = useCallback(
(
_data: any,
index: number
): { length: number; offset: number; index: number } => ({
index,
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index
}),
[]
);
useEffect(() => {
console.log('reporting fully_drawn');
// @ts-ignore
global.performance.reportFullyDrawn();
}, []);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
// https://reactnative.dev/docs/optimizing-flatlist-configuration#initialnumtorender
// 明示的に10に設定しているのは、画面に表示できる項目数の初期値であるためです
initialNumToRender={10}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
// https://reactnative.dev/docs/optimizing-flatlist-configuration#windowsize,
// TVプロファイルよりもMMでのウィンドウサイズを大きく設定しているのは、クイックtouch
// スワイプした場合、D-Padを長押しするよりもフラットリストをすばやくスクロールできるためです。ウィンドウサイズが
// 大きいほどより多くのメモリを消費しますが、スクロール時に表示される空白領域は少なくなります。
windowSize={Platform.isTV ? 5 : 13}
/>
</View>
);
};
このパフォーマンスの問題は、Flatlistコンポーネントを、スクロールシナリオのパフォーマンスを向上させるように設計された、ShopifyのFlashlistコンポーネントに置き換えます。
const PerfFlashListApp = () => {
// https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
const renderItem = useCallback(
({ item, index }: { item: FlashListItem; index: number }) => (
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-basic-components
<Item
buttonStyle={[
styles.item,
{
backgroundColor: calculateBackgroundColor(index)
}
]}
hasTVPreferredFocus={item.id === 0}
/>
),
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-keyextractor-or-key
const keyExtractor = useCallback(
(item: FlashListItem, index: number) => item.title,
[]
);
useEffect(() => {
console.log('reporting fully_drawn');
// @ts-ignore
global.performance.reportFullyDrawn();
}, []);
return (
<View style={styles.container}>
<FlashList
estimatedItemSize={ITEM_HEIGHT}
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
</View>
);
};
アプリのパフォーマンスを再テストし、Thread State Viewの「Running」状態の数が減少しているかどうかを確認します。CPUプロファイラーのフレームグラフにも、アクティビティの減少が示されます。
FlashlistがFlatlistより優れている点については、ベストプラクティスを参照してください。
関連トピック
Last updated: 2025年9月30日

