例外処理のtry-catchは遅い

例外の処理は非常に遅い処理です。使い方に気を付けないと深刻なパフォーマンス低下を引き起こすことがあります。

避けるべき使い方

極端に悪い例として、ループを抜けるための処理をtry-catchを用いて実装したものを示します。

C++
/* 自然数の数列の和を計算する */

#include <iostream>
#include <numeric>
#include <vector>

int main(void){
    // 1から10000の自然数の数列を生成
    std::vector<int> vec(10000);
    std::iota(vec.begin(), vec.end(), 1);

    int i = 0;
    long sum = 0;

    while(1) {
        try { 
            sum += vec.at(i);
            i++;
        } catch (std::out_of_range) {
            // 範囲外例外を捕捉してループを抜ける
            break;
        }
    }
    
    std::cout << sum << std::endl;
    return 0;
}

このコードは一応動作しますが、問題は範囲外例外(std::out_of_range)が必ず起こるということです。例外をcatchしループを抜ける処理はかなり遅いため、この場合は必ず重いcatch処理が発生することになります。

仮にこの処理が繰り返し呼ばれるような場合、致命的なパフォーマンス低下となることがあります。

この場合当然ですが無限ループではなく通常のfor文や範囲for文で書くべきです。

C++
// 通常for文
for(int i = 0; i < vec.size(); i++) {
    sum += vec.at(i);
}

// 範囲for文(C++11以降)
for(auto val : vec) {
    sum += val;
}

for文は内部的にはループごとに条件式が成立するかを判定しており、例外の捕捉とは仕組みが異なります。

注意すべき点

ソフトの開発方針や言語、環境にもよりますが、例外処理機構を使うこと自体は間違っていないと思います。ただし重い処理であることを認識し、以下のような注意が必要です。

  • 必ず(あるいは頻繁に)起こるような事象を例外で処理しようとしない
  • ifやforなどで処理できるなら例外処理機構を使わない
  • コーディングや設計によりそもそも例外の発生回数を抑える工夫をする
  • 繰り返し呼ばれる処理で例外処理機構を使う場合は必要性やパフォーマンスを検証する

積極的に使いたい場面

むしろ例外処理機構を積極的に使うべきと思われる場面もあります。

例えばファイルシステム関連の例外を捕捉する場合です。このような例外はファイルがロックされている、アクセス権限がない、不正なパスなど原因が多岐に渡るうえ、プログラム自体の問題ではなくOSなど外部が要因になる可能性もあります。

当然それらを全て自前でチェックするのは面倒ですし、抜けも考えられます。このような場合には積極的に例外処理機構を使用したほうが良いと思います。

C++
#include <iostream>
#include <filesystem>

int main() {
    try {
        std::filesystem::path file = "test.txt";
        auto size = std::filesystem::file_size(file);
    }
    catch (const std::filesystem::filesystem_error& e) {
        // 例外処理(メッセージ表示等)
    }
    return 0;
}

まとめ

例外処理機構は使いどころが結構難しく「なるべく使わない方が良い」という意見もあるようですが、適切な方法であれば使用自体は問題ないと考えています。

むしろ例で紹介したC++は標準ライブラリでも普通に例外処理機構が使われているため、例外があることを前提にソフトウェアの全体設計を行わないと思わぬ不具合を引き起こします。

「例外処理の時間的コスト」と「使うべきタイミング」を見極めることが大切だと思います。

コメント

タイトルとURLをコピーしました