OpenCV の dispatch機能について

# はじめに
 - 本記事は[OpenCV Advent Calendar 2022](https://qiita.com/advent-calendar/2022/opencv) の9日目の記事です。
    - [8日目の記事](https://qiita.com/ChaoticActivity/items/650b4b18dfb8c239e7ce)は[@ChaoticActivity](https://qiita.com/ChaoticActivity)先生による[cv::VideoCaptureで動画から読んだRGB画素値の誤差について](https://qiita.com/ChaoticActivity/items/650b4b18dfb8c239e7ce)です。是非お読み下さい。
    - その他の記事は[目次](https://qiita.com/advent-calendar/2022/opencv)をご覧ください。
 - 自分のアドベントカレンダーの記事を見返していて、数年単位で「dispatch機能について解説する」という詐欺みたいな行為[^1][^2]を働いていたのでいい加減書くことにした。

[^1]:[4年前に書いた記事その1](https://qiita.com/tomoaki_teshima/items/a395703284d30fa6e660)
[^2]:[4年前に書いた記事その2](https://qiita.com/tomoaki_teshima/items/7c0f181e243e3170112e)で「別記事で解説しようと思います」と書いてました

# dispatch機能とは
 - OpenCV 3.3ぐらいから入った機能で、簡単に言うと拡張命令を正しく選択して実行する機能です
 - dispatch機能がなかった時代のOpenCV
 - OpenCVにはSSEを始めとしたSIMD命令を利用した実装が多数存在しました
     - 更にはむかし、SSEが提供されないCPUというのも存在したので、拡張命令(SIMD)を使う場合は基本的にCPUの拡張命令が提供されているかのフラグをチェックする必要がありました
 - さて、時代は流れてAVXがやってきました
     - AVXはSSEの後継なのでSSE5とでも呼ぶべき位置ですが、ベクトル幅が256bitに拡張されたので、あたらしくAVXというブランド名を与えられました。
 - AVXをVisual Studioでコンパイルする場合、明示的にプロジェクトごとに「フラグを設定する」必要がありました

![Screenshot 2022-12-01 18.02.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/f0516874-0a69-b811-972f-e58b55604166.png)

 - 図中の`Enable Enhanced Instruction Set`がそれに該当します
 - こいつはファイル全体にわたって有効にされ、それより小さい単位では管理できません
 - ここに、オートベクタライザの弊害が現れます
     - オートベクタライザは主に`for`ループで同じ処理を繰り返し行っていることをコンパイラが検知した場合、当該ループをSIMD化してくれる便利な機能です
 - 前述したようにAVX命令の利用はプロジェクト単位/ファイル単位で有効化されます
 - ファイル内では拡張命令を使う直前に拡張命令が使えるかのチェックが入ってはいますが、オートベクトライザのおかげでそれ以外のところにもAVXの命令が生成される可能性があります
     - `for`ループの中でなくても、例えば`double`4つの構造体を同じ値を設定する場合でも、オートベクタライザによりAVXの命令が生成される可能性があります
 - こうすると、AVX命令に対応してないCPUでプログラムを実行した場合、実行時エラーが発生します
 - 一方で、画像処理の速度の観点から行って、SIMD命令は外すことのできない重要なテクニックです
 - ここで速度と安全性とポータビリティの観点からジレンマが発生します
     - **速度第一**で考えた場合、AVX命令は有効にするべきだが、その場合当該プログラムが他のマシンで動くためにはAVX命令必須となり、**安全性が失われる**
     - **安全第一**で考えた場合、AVX命令は無効にするべきだが、その場合すべての実装でAVX命令が無効化され、CPUの**パフォーマンスを発揮できない**
     - OpenCVを利用するマシン上で、対応する命令を有効にしてOpenCVをビルドすれば速度も安全性も担保できるが、デモ用のマシンなど、Visual Studioをインストールできない状況も十二分に考えられる。
 - と、かなり悩ましい状況に陥りいます（ジレンマは2点の間で揺れ動くことなので、この場合はトリレンマが正しい？)
 - というわけでdispatch機能がOpenCVに実装されます[^reason]

[^reason]:さもdispatch機能が実装された理由がAVXだけ、かのように書きましたが、実際のところはNEONを始めとするx86_64アーキテクチャ以外のSIMD対応が進んでいた時期であり、一概に「AVXがために」というわけではなさそうです。ただし、[筆者が見てきた範囲](https://github.com/opencv/opencv/issues/6990)だとAVXがdispatch機能実装の理由の一つであることは間違いなさそうです。

# Dispatch機能を使うために
- さて、AVX命令を使い、かつ他の部分でAVX命令を使わないためには、解決策は1つしかありません。
- AVXを使う部分をプロジェクトとして切り出し、そのプロジェクトでだけAVXを有効にすることです
- 具体的には以下の手順でdispatch機能を実装します
    1. 関数内の最内ループでSIMD化できる箇所だけを関数として独立させる
    1. 独立させた関数を、命名規則に則った別ファイルとして独立させる
    1. 必要なヘッダや宣言を埋め込む
    1. CMakeに当該ファイルはdispatch対応と記述する
- とりあえず意味不明ですね。もう少し具体例を踏まえて見ていきましょう
    - 現行の最新版である 4.6.0 をVisual Studio でビルドしようとすると、以下のように複数のプロジェクトが生成されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/2fb72ea1-26dd-42cc-2a86-9ba29ca1a38c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/a654fb19-f27f-926f-f2f7-540d5bdc9cae.png)

    - このうち、
        - `opencv_core_AVX`、`opencv_claib3d_AVX2` など末尾にSIMD拡張命令の名前がついてるやつがdispatch用プロジェクト
        - `opencv_core`、`opencv_calib3d`など末尾に何もつかないやつがライブラリとしてビルドされるプロジェクトです
    - 各プロジェクトの設定項目をみると、たしかに項目名に対応したSIMD命令有効フラグが使われています
    - 以下は`opencv_core`プロジェクトの設定。前述の"Enable Enhanced Instruction Set"項目が"Not Set"になっています

![Screenshot 2022-12-01 18.02.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/abe7e98e-702a-ec7e-8fc2-885fe0e50eef.png)

:::note info
本文中ではWindowsのVisual Studioのスクリーンショットを使いましたが、Linux+Makeでも状況は同じで、 CMakeで記述/コンフィグした通りに `opencv_xxx_AVX` といった命名規則に則ったターゲットが生成されます。
:::

- ではだんだん中を見ていきましょう。
- opencv_core_AVX2プロジェクトにはAVX2命令で実装された関数がファイルごとに羅列されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/247781f9-6373-2151-efa2-95d52abc418c.png)

- `arithm.avx2.cpp` から `sum.avx2.cpp` まで合計11ファイルが登録されています。
    - 試しにファイルを覗いてみましょう。


```arithm.avx2.cpp
#include "C:/opencv/modules/core/src/precomp.hpp"
#include "C:/opencv/modules/core/src/arithm.simd.hpp"
```

- 衝撃の短さです
    - [以前紹介したtest_main.cpp](https://qiita.com/tomoaki_teshima/items/ec1f62def95c2e96ba58)並の短さですが、こちらはわずか2行です。
    - 6年ぶりの記録更新です
- というわけで、大体大事なものは実は`arithm.simd.hpp`に入っています。
    - ちなみに、各命令セットごとに内容は全く同じですが、ファイルの実体はそれぞれ別物です。

```arithm.avx.cpp
#include "C:/opencv/modules/core/src/precomp.hpp"
#include "C:/opencv/modules/core/src/arithm.simd.hpp"
```

```arithm.sse4_1.cpp
#include "C:/opencv/modules/core/src/precomp.hpp"
#include "C:/opencv/modules/core/src/arithm.simd.hpp"
```

:::note info
dispatch機能対応するために、コンパイルフラグの影響を受ける実装はすべてxxxx.simd.hppというファイルに詰め込みましょう
:::

## 呼ぶ方の実装
- 呼ぶ方の実装を追ってみましょう
- ここでは単純に和の`add`を追ってみましょう
- 要素ごとの`add`はたどると、以下の型別の関数に到着します。

```arithm.cpp
static BinaryFuncC* getAddTab()
{
    static BinaryFuncC addTab[] =
    {
        (BinaryFuncC)GET_OPTIMIZED(cv::hal::add8u), (BinaryFuncC)GET_OPTIMIZED(cv::hal::add8s),
        (BinaryFuncC)GET_OPTIMIZED(cv::hal::add16u), (BinaryFuncC)GET_OPTIMIZED(cv::hal::add16s),
        (BinaryFuncC)GET_OPTIMIZED(cv::hal::add32s),
        (BinaryFuncC)GET_OPTIMIZED(cv::hal::add32f), (BinaryFuncC)cv::hal::add64f,
        0
    };

    return addTab;
}
```

```arithm.simd.hpp
DEFINE_SIMD_ALL(add, op_add)
```

 - プリプロセスにより以下のように展開されます(一部抜粋)

```cpp
namespace cv { namespace hal {

void add8u(...)
{
    if ((cv::checkHardwareSupport(11)))
        return (opt_AVX2::add8u (src1, step1, src2, step2, dst, step, width, height));
    if ((cv::checkHardwareSupport(6)))
        return (opt_SSE4_1::add8u (src1, step1, src2, step2, dst, step, width, height));
    return (cpu_baseline::add8u (src1, step1, src2, step2, dst, step, width, height));
}
```

 - この`add8u`は`DEFINE_SIMD_ALL(add, op_add)`がマクロ展開され宣言/定義されます
 - `checkHardwareSupport`で実際にプログラムが走ってるCPUの拡張機能を調べ、拡張命令が提供されている場合、対応する名前空間のコードを呼びます
   - AVX2命令が提供されている場合、`opt_AVX2::add8u`を
   - SSE4.1命令が提供されている場合、`opt_SSE4_1::add8u`を
   - どちらも提供されない場合、`cpu_baseline::add8u`をそれぞれ呼びます

 - 実際に呼ばれるコードは、Universal Intrinsicと同じく、アーキテクチャの違いをヘッダファイルに押し込むことで、呼んでる方はへんてこな`ifdef`は必要ありません。

```cpp
namespace cv { namespace hal {
namespace opt_AVX2 {

void add8u(...);
```
 - `add8u`は`bin_loop`を呼び、`bin_loop`までたどると、だんだん`ifdef`が効いてくるのが見えます

```.cpp
static void bin_loop(const T1* src1, size_t step1, const T1* src2, size_t step2, T1* dst, size_t step, int width, int height)
{
(中略)
    for (; height--; src1 += step1, src2 += step2, dst += step)
    {
        int x = 0;

    #if CV_SIMD
        #if !CV_NEON && !CV_MSA
        if (is_aligned(src1, src2, dst))
        {
            for (; x <= width - wide_step_l; x += wide_step_l)
            {
                ldr::la(src1 + x, src2 + x, dst + x);
                #if CV_SIMD_WIDTH == 16
                ldr::la(src1 + x + wide_step, src2 + x + wide_step, dst + x + wide_step);
                #endif
            }
        }
        else
        #endif
            for (; x <= width - wide_step_l; x += wide_step_l)
            {
                ldr::l(src1 + x, src2 + x, dst + x);
                #if !CV_NEON && CV_SIMD_WIDTH == 16
                ldr::l(src1 + x + wide_step, src2 + x + wide_step, dst + x + wide_step);
                #endif
            }
```

 - 流石にこのレベルまで来ると`ifdef`の嵐で流石にみにくくはありますが、ポイントは、保持するコードは1つのファイルでよい、という点です。
 - 実際、dispatch機能は、ソースコードの途中で`CV_CPU_DISPATCH`というマクロに突き当たります。
```.cpp
#undef DISPATCH_SIMD_FUN
#define DISPATCH_SIMD_FUN(fun, _T1, _Tvec, _OP)                           \
    void fun(BIN_ARGS(_T1), void*)                                        \
    {                                                                     \
        CV_INSTRUMENT_REGION();                                           \
        CALL_HAL(fun, __CV_CAT(cv_hal_, fun), BIN_ARGS_PASS)              \
        ARITHM_CALL_IPP(__CV_CAT(arithm_ipp_, fun), BIN_ARGS_PASS)        \
        CV_CPU_DISPATCH(fun, (BIN_ARGS_PASS), CV_CPU_DISPATCH_MODES_ALL); \
    }
```
 - `arithm.simd.hpp`ではマクロに囲われているので見えませんが、このマクロが前述のカスケードした関数コールにたどり着きます

```
dispatch機能を使うと、いずれCV_CPU_DISPATCHマクロにたどり着き、こいつが名前空間の違う実装を順番に試してコールする
```
:::note info
dispatch機能を使う際は、CV_CPU_DISPATCHマクロを使いましょう。
マクロによって違う名前空間の違う実装を順番に試してコールしてくれます
:::

## CMake部分
 - すごいのはここからです 
 - dispatch機能を実現するためには、複数のステップが必要です
   - `CV_CPU_DISPATCH`マクロを必要な関数コールに展開する
   - 基本的な実装を詰め込んだ`arithm.simd.hpp`ファイルに対して、拡張命令ごとに複数のソースから適切なマクロを定義したあとにincludeする
## `CV_CPU_DISPATCH`
 - まずはここから行きましょう
 - このマクロは`cv_cpu_dispatch.h`で定義されています

```cv_cpu_dispatch.h
#define __CV_CPU_DISPATCH_CHAIN_END(fn, args, mode, ...)  /* done */
#define __CV_CPU_DISPATCH(fn, args, mode, ...) __CV_EXPAND(__CV_CPU_DISPATCH_CHAIN_ ## mode(fn, args, __VA_ARGS__))
#define __CV_CPU_DISPATCH_EXPAND(fn, args, ...) __CV_EXPAND(__CV_CPU_DISPATCH(fn, args, __VA_ARGS__))
#define CV_CPU_DISPATCH(fn, args, ...) __CV_CPU_DISPATCH_EXPAND(fn, args, __VA_ARGS__, END) // expand macros
```
 - これは任意の長さのパラメータを受け取るマクロを展開するときの方法なので、ここまでは割りと普通です
 - この`CV_CPU_DISPATCH`を`arithm.dispatch.cpp`で呼ぶ時に、マクロを呼びます

```arithm.simd.hpp
CV_CPU_DISPATCH(fun, (BIN_ARGS_PASS), CV_CPU_DISPATCH_MODES_ALL);
```

 - この`CV_CPU_DISPATCH_MODES_ALL`はどこからくるのか？
 - たどると、`arithm.simd_declarations.hpp`に定義があります

```arithm.simd_declarations.hpp
#define CV_CPU_DISPATCH_MODES_ALL AVX2, SSE4_1, BASELINE
```

 - では、この`arithm.simd_declarations.hpp`はどこに存在するのか？
     - このファイルはgitリポジトリ内では管理されていません
 - これがCMakeでコンフィグするときに生成されます

:::note info
コンフィグ結果に依存するヘッダファイル `xxxx.simd_declarations.hpp`はCMakeでコンフィグ時に生成されます
:::

## 拡張命令ごとに複数のソースを用意する
 - 現状のarithmではAVX2版、SSE4.1版、そしてSSSE3までを使ったベースラインの実装が必要になります
 - SSSE3までの実装は`arithm.dispatch.cpp`に埋め込まれます。
 - AVX2版とSSE4.1版はそれぞれ`arithm.avx2.cpp`と`arithm.sse4_1.cpp`が生成されます
 - こいつもcmakeが生成します

```CMake
# ここでarithm.xxx.cppの内容を__codestrに代入します
macro(__ocv_add_dispatched_file filename target_src_var src_directory dst_directory precomp_hpp optimizations_var)
  if(NOT OPENCV_INITIAL_PASS)
    set(__codestr "
#include \"${src_directory}/${precomp_hpp}\"
#include \"${src_directory}/${filename}.simd.hpp\"
")

# 中略
# ここでファイル名を決めます
      set(__file "${CMAKE_CURRENT_BINARY_DIR}/${dst_directory}${filename}.${OPT_LOWER}.cpp")
# 中略
# 実際のファイルをここで生成します
        file(WRITE "${__file}" "${__codestr}")

# 前述の arithm.simd_declarations.hpp もここで生成されます
        set(__declarations_str "${__declarations_str}
#define CV_CPU_DISPATCH_MODE ${OPT}
#include \"opencv2/core/private/cv_cpu_include_simd_declarations.hpp\"
")
# 中略
    set(__declarations_str "${__declarations_str}
#define CV_CPU_DISPATCH_MODES_ALL ${__dispatch_modes}

#undef CV_CPU_SIMD_FILENAME
")
# 中略
      file(WRITE "${__file}" "${__declarations_str}")

```

:::note info
dispatchされるソースコード`xxxx.avx.cpp` などは、`xxxx.simd_declarations.hpp`同様、CMakeでコンフィグ時に生成されます
:::


# 実装を追加する場合
 - 今年、[NEONの内積命令をOpenCVに追加しました](https://github.com/opencv/opencv/pull/22271) 
 - その際の手順の備忘録です

## 名前を決める
 - そもそも、利用する拡張命令の一覧は、OpenCV内部に情報として保存されます
 - x86向けのビルドでは`SSSE3`がベースライン、`AVX`、`AVX2`などがdispatchとして利用されます
     - いずれもCMakeでコンフィグする際に変更可能です
 - 今回、NEONの内積命令はLinuxの`cpuinfo`では`asimddp`で表示されます。
 - 最初は`DOTPROD`というキーワードで提案しましたが、レビューの結果`NEON_DOTPROD`に落ち着きました。
 - この`NEON_DOTPROD`をまず、`OpenCVCompilerOptimizations.cmake` に追記します

```diff
-list(APPEND CPU_ALL_OPTIMIZATIONS NEON VFPV3 FP16)
+list(APPEND CPU_ALL_OPTIMIZATIONS NEON VFPV3 FP16 NEON_DOTPROD)
```

 - この時点でビルドすると、勝手に`cv_cpu_helper.h`が更新されます

```diff
+#if !defined cv_disable_optimization && defined cv_enable_intrinsics && defined cv_cpu_compile_neon_dotprod
+#  define cv_try_neon_dotprod 1
+#  define cv_cpu_force_neon_dotprod 1
+#  define cv_cpu_has_support_neon_dotprod 1
+#  define cv_cpu_call_neon_dotprod(fn, args) return (cpu_baseline::fn args)
+#  define cv_cpu_call_neon_dotprod_(fn, args) return (opt_neon_dotprod::fn args)
+#elif !defined cv_disable_optimization && defined cv_enable_intrinsics && defined cv_cpu_dispatch_compile_neon_dotprod
+#  define cv_try_neon_dotprod 1
+#  define cv_cpu_force_neon_dotprod 0
+#  define cv_cpu_has_support_neon_dotprod (cv::checkhardwaresupport(cv_cpu_neon_dotprod))
+#  define cv_cpu_call_neon_dotprod(fn, args) if (cv_cpu_has_support_neon_dotprod) return (opt_neon_dotprod::fn args)
+#  define cv_cpu_call_neon_dotprod_(fn, args) if (cv_cpu_has_support_neon_dotprod) return (opt_neon_dotprod::fn args)
+#else
+#  define cv_try_neon_dotprod 0
+#  define cv_cpu_force_neon_dotprod 0
+#  define cv_cpu_has_support_neon_dotprod 0
+#  define cv_cpu_call_neon_dotprod(fn, args)
+#  define cv_cpu_call_neon_dotprod_(fn, args)
+#endif
+#define __cv_cpu_dispatch_chain_neon_dotprod(fn, args, mode, ...)  cv_cpu_call_neon_dotprod(fn, args); __cv_expand(__cv_cpu_dispatch_chain_ ## mode(fn, args, __va_args__))
```
 - この`cv_cpu_helper.h`はgitで管理されているファイルですが、実はCMakeによってコンフィグされる際に毎回上書きされています
 - この定型処理はヒューマンエラーが入らないようにCMakeで実現されています。かしこいですね！

```cmake
macro(ocv_compiler_optimization_fill_cpu_config)
# 中略
  foreach(OPT ${CPU_ALL_OPTIMIZATIONS})    if(NOT DEFINED CPU_${OPT}_FEATURE_ALIAS OR NOT "x${CPU_${OPT}_FEATURE_ALIAS}" STREQUAL "x")
      set(OPENCV_CPU_CONTROL_DEFINITIONS_CONFIGMAKE "${OPENCV_CPU_CONTROL_DEFINITIONS_CONFIGMAKE}
#if !defined CV_DISABLE_OPTIMIZATION && defined CV_ENABLE_INTRINSICS && defined CV_CPU_COMPILE_${OPT}
#  define CV_TRY_${OPT} 1
#  define CV_CPU_FORCE_${OPT} 1
#  define CV_CPU_HAS_SUPPORT_${OPT} 1
#  define CV_CPU_CALL_${OPT}(fn, args) return (cpu_baseline::fn args)
#  define CV_CPU_CALL_${OPT}_(fn, args) return (opt_${OPT}::fn args)
#elif !defined CV_DISABLE_OPTIMIZATION && defined CV_ENABLE_INTRINSICS && defined CV_CPU_DISPATCH_COMPILE_${OPT}
#  define CV_TRY_${OPT} 1
#  define CV_CPU_FORCE_${OPT} 0
#  define CV_CPU_HAS_SUPPORT_${OPT} (cv::checkHardwareSupport(CV_CPU_${OPT}))
#  define CV_CPU_CALL_${OPT}(fn, args) if (CV_CPU_HAS_SUPPORT_${OPT}) return (opt_${OPT}::fn args)
#  define CV_CPU_CALL_${OPT}_(fn, args) if (CV_CPU_HAS_SUPPORT_${OPT}) return (opt_${OPT}::fn args)
#else
#  define CV_TRY_${OPT} 0
#  define CV_CPU_FORCE_${OPT} 0
#  define CV_CPU_HAS_SUPPORT_${OPT} 0
#  define CV_CPU_CALL_${OPT}(fn, args)
#  define CV_CPU_CALL_${OPT}_(fn, args)
#endif
#define __CV_CPU_DISPATCH_CHAIN_${OPT}(fn, args, mode, ...)  CV_CPU_CALL_${OPT}(fn, args); __CV_EXPAND(__CV_CPU_DISPATCH_CHAIN_ ## mode(fn, args, __VA_ARGS__))
")
    endif()
  endforeach()

```

:::note info
ヒューマンエラーを避けるためにも、定型処理は必ずCMakeに任せましょう
:::

## 実装する
 - つづいては実装部分です。
 - 飛び込む前に、拡張命令を単純にOpenCVの関数内部にベタ書きすると、残念ながらPRは承認されません。
    -  [実際にNEONベタ書きしたPRに対するOpenCV管理者 Vadim Pisarevsky さんからのコメント](https://github.com/opencv/opencv/pull/20204#issuecomment-854728037)

> we do not accept native optimizations any longer. 
It would be just impossible for our tiny team to maintain all those branches. 
Please, rewrite the native NEON code using our universal intrinsics.

 - というわけでいきなりintrinsicをベタ書きするのでなく、Universal Intrinsicに`dot`命令を追記していくことになります

```cpp
#ifdef CV_NEON_DOT
#define OPENCV_HAL_IMPL_NEON_DOT_PRODUCT_OP(_Tpvec1, _Tpvec2, suffix) \
inline _Tpvec1 v_dotprod_expand(const _Tpvec2& a, const _Tpvec2& b)   \
{ \
    return _Tpvec1(vdotq_##suffix(vdupq_n_##suffix(0), a.val, b.val));\
} \
inline _Tpvec1 v_dotprod_expand(const _Tpvec2& a, const _Tpvec2& b, const _Tpvec1& c) \
{ \
    return _Tpvec1(vdotq_##suffix(c.val, a.val, b.val)); \
}

OPENCV_HAL_IMPL_NEON_DOT_PRODUCT_OP(v_uint32x4, v_uint8x16, u32)
OPENCV_HAL_IMPL_NEON_DOT_PRODUCT_OP(v_int32x4,  v_int8x16,  s32)
#else
```

 - ここではマクロを使ってまとめましたが、内部では`vdotq`というキーワードが見えます。

:::note info
実装にintrinsicベタ書きは実質認められない。Universal Intrinsicを使いましょう。
:::

## CMakeの更新
 - 最後に、対応する関数を使いたいモジュールのCMakeLists.txtファイルに`NEON_DOTPROD`を追記します
```diff
-ocv_add_dispatched_file(matmul SSE2 SSE4_1 AVX2 AVX512_SKX)
+ocv_add_dispatched_file(matmul SSE2 SSE4_1 AVX2 AVX512_SKX NEON_DOTPROD)
```
 - もともと内積命令は`matmul.cpp`内で使われており、このファイルはすでにdispatch対応されていました。
 - これの選択肢に`NEON_DOTPROD`を追加してだいたいおしまいです。
 - dispatch機能がすでに実装されているおかげで、「同じ作業」は「なるだけCMake」にまとめられています
 - いいですね！

## その他
 - ビルド時及び実行時に拡張命令対応を調べるテスコード、及び拡張命令にユニークなIDと名前を記述するステップがありますが、ここでは割愛します

# おわりに
 - 長くなりましたが、OpenCVではdispatch機能を実装しており、ビルド時、実行時にどう振る舞うかを紹介しました
 - 具体的には実装の大部分はヘッダに実装を押し込み、cmakeで自動生成する機能に頼っています
 - [DRYの原則](https://ja.wikipedia.org/wiki/Don%27t_repeat_yourself)に則った、美しい実装だと思います
 - ところで、OpenCVは最近5.0のリリースに注力している模様で、4系列のリリースも半年以上ありません。
     - 例年通りクリスマスシーズンにリリースされるのかもしれませんが、今のところ不明です
     - 件のPRは半年近く前にマージされたのに、まだ3系、4系、いずれの正式リリースにも含まれてないのはぐぬぬな感じです
 - 明日は[@Kazuma_Kikuya](https://qiita.com/Kazuma_Kikuya)さんの投稿でタイトルは「[CLIP STUDIOのベクターレイヤをOpenCVとCatmull-Romスプライン曲線で実装してみた話](https://qiita.com/Kazuma_Kikuya/items/154f4c86ce7a864b637a)」です。お楽しみに！

# 参考資料
 - https://github.com/alalek/opencv/wiki/CPU-optimizations-build-options : OpenCV 公式Wikiによる解説
 - https://github.com/opencv/opencv/pull/7462 : dispatch機能を追加した最初のPR