C++ で `std::string in` に対する `in == "http" or in "https"` 的なパターンとコストの比較

# 概要

`std::string` オブジェクトの文字列が "`http`" または `"https"` かチェックしたい時、次のコードが最初に脳内に浮かぶでしょうか:

```cpp
in == "http" or in == "https"
```

それとも、 `find` を使う？あるいは `substr` と `==`？もしかしたら `boost::xpressive` を使おうと思ったかもしれません。

何が最適なソースコードの答えとなるかは状況によりますが、実行時のレイテンシー、低負荷を優先したい場合は？

この記事では単純に、実験的にその答えを求めてみます。

- note: この記事はAまたはBかという厳密な文字列の判定の実装ではなく、「例として HTTP 系の url-scheme （つまり `"http"` または `"https"` の何れか）である事さえ判定できればよい」という状況に対して書いたものです。この点については評価例の†や‡および‡を追記する切っ掛けとなったコメントも参考にして下さい。

# 実験

## 測定方法

```cpp
  auto f =
  [] ( auto g )
  { auto t0 = std::chrono::steady_clock::now();
    g();
    std::cout << ( std::chrono::steady_clock::now() - t0 ).count() << ' ';
  };
```

引数で受け取るファンクターの実行時間を `std::chrono::steady_clock` で計測します。

note: 今回 `boost::timer::cpu_timer` は wandbox で動作しなかったので実行例を示す都合使いません。

## 比較対象

```cpp
      // for reference
      f( [ ]{ return true; } );
      // for reference
      f( [&]{ return in == "http"; } );
      // 1. simple eq-or pattern
      f( [&]{ return in == "http" or in =="https"; } );
      // 2. substr-eq pattern
      f( [&]{ return in.substr( 0, 4 ) == "http"; } );
      // 3. size-strcmp pattern
      f( [&]{ return in.size() > 3 and std::strcmp( "http", in.c_str() ); } );
      // 4. find pattern
      f( [&]{ return in.find( "http" ) == 0; } );
      // 5. boost::xpressive pattern
      f( [&]{ using namespace boost::xpressive; smatch r; sregex p = bos >> as_xpr( "http" ) >> ( ! as_xpr( 's' ) ) >> eos; return regex_match( in, r, p ); } );
```

note: `for reference` の2件は参考値としてこのようなコードがどの程度の速度で処理される事になるのか観測するために用意します。

## 評価用ソースコード例

```cpp
#include <iostream>
#include <string>
#include <chrono>
#include <boost/xpressive/xpressive.hpp>

auto main() -> int
{
  auto f =
  [] ( auto g )
  { auto t0 = std::chrono::steady_clock::now();
    g();
    std::cout << ( std::chrono::steady_clock::now() - t0 ).count() << ' ';
  };
  
  using namespace std::literals::string_literals;
  
  for ( const auto& in : { "https"s, "https@https@https@https@https@https@https@https@https@https@https@https@https@https@https@https@"s } )
  {
    for ( auto n = 0; n < 8; ++n )
    {
      // for reference
      f( [ ]{ return true; } );
      // for reference
      f( [&]{ return in == "http"; } );
      // 1. simple eq-or pattern
      f( [&]{ return in == "http" or in =="https"; } );
      // 2. substr-eq pattern
      f( [&]{ return in.substr( 0, 4 ) == "http"; } );
      // 3. size-strcmp pattern
      f( [&]{ return in.size() > 3 and std::strcmp( "http", in.c_str() ); } );
      // 4. find pattern
      f( [&]{ return in.find( "http" ) == 0; } );
      // 5. boost::xpressive pattern
      f( [&]{ using namespace boost::xpressive; smatch r; sregex p = bos >> as_xpr( "http" ) >> ( ! as_xpr( 's' ) ) >> eos; return regex_match( in, r, p ); } );
      std::cout << '\n';
    }
    std::cout << '\n';
  }
}
```

note: 通常はこのような処理をする前提で `in` には比較的短い url-scheme などが入っていると想定されますが、 `in` が予想よりも長ったらしい文字列を格納していた場合のコストも一応評価します。

### wandbox

- http://melpon.org/wandbox/permlink/WZqVeQFNdFPJZnLj

# 結果例

```
593 5884 3070 507 190 5990 71745 
190 365 268 277 190 260 7513 
177 220 262 258 190 228 5702 
178 220 262 250 190 227 5447 
178 220 262 250 190 227 5318 
175 223 263 257 190 227 5275 
175 220 263 257 190 228 5272 
175 220 262 258 190 227 5300 

178 220 262 273 187 228 5325 
178 220 262 250 190 228 5272 
175 220 263 250 190 228 5237 
175 220 262 250 190 228 5245 
175 220 262 250 190 228 5252 
177 220 265 253 187 227 5238 
175 220 263 250 190 227 5240 
178 220 263 252 190 228 5282 
```

- note: 実用的な実行時の性能を手っ取り早く評価したい目的のため、コンパイラーによる最適化は有効として計測します。
- note: 測定方法が正確さを欠く、環境依存しますが、少なくともこの環境（wandbox）では各種法の負荷比較は十分にできる結果が得られます。

# 評価例

今回の目的と実験結果からは `3. size-strcmp pattern` が良好な結果と言える。

1. `simple eq-or pattern` : 単純明快なソースの記述は可読性も良いが、鎬を削る実行速度優先には甘さが多い。
2. `substr-eq pattern` : オブジェクト生成コストで(1)より遅くなるかと思ったがコンパイラーがいい仕事をしたようだ。†‡
3. `size-strcmp pattern` : ある意味でお約束のチョットCPUの気持ちで考えたコード。結果もお約束通り高速。†‡
4. `find pattern` : (1)より若干ソースも実行コストもクレバー。†‡
5. `boost::xpressive pattern` : このような比較的単純な状況では向かない。

- note: †は厳密には他の方法と等価な判定にはもう少々コスト増となる。文字列の先頭からのマッチングしか行っていないので。実用上それが問題となる場合には注意が必要。
- note: ‡コメントより追記: `"https"` である可能性がある場合に厳密に最後の文字 `s` まで判定を行っていないので、 例えば `"http-hogeraccho"` のような文字列にもこれら判定結果は `true` を返します。これが問題となる状況では、必要に応じて判定を厳密にする注意が必要です。

# だそく

- 「例」として提示したものは環境依存性があるなど同様の結果が得られるとは限りません。
- 本当に「鎬を削る」必要があるのかはソフトウェアの対象処理全体のボトルネックや求められるレイテンシーのオーダーによります。鎬を削る必要の無い場合では一般には可読性を最優先とするのが良いでしょう。
- まあほら、まいど実装詳細やら逆アセンブル解析やら理詰めしたりしないでも実用上の選択肢のアタリを付けたい場合はこんなテキトーな評価でもぱぱっとできたらそれはそれでいい事にしてください＠ｗ＠