物理の駅 Physics station by 現役研究者

テクノロジーは共有されてこそ栄える

Windows: 自分のパソコンが拡張命令 (AVX-512等)に対応しているか確認する方法

マイクロソフトが提供しているフリーソフトで確認できます。次のプログラム Coreinfo v3.53 をダウンロード、展開して、Coreinfo.exe をコマンドプロンプトで実行しましょう。

docs.microsoft.com

実行例 (一部略)。 * はサポートしている、 -は未サポート。

Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
Intel64 Family 6 Model 126 Stepping 5, GenuineIntel
Microcode signature: 000000A8
HTT             *       Hyperthreading enabled
CET             -       Supports Control Flow Enforcement Technology
Kernel CET      -       Kernel-mode CET Enabled
User CET        -       User-mode CET Allowed
HYPERVISOR      *       Hypervisor is present
VMX             -       Supports Intel hardware-assisted virtualization
SVM             -       Supports AMD hardware-assisted virtualization
X64             *       Supports 64-bit mode

SSE             *       Supports Streaming SIMD Extensions
SSE2            *       Supports Streaming SIMD Extensions 2
SSE3            *       Supports Streaming SIMD Extensions 3
SSSE3           *       Supports Supplemental SIMD Extensions 3
SSE4a           -       Supports Streaming SIMDR Extensions 4a
SSE4.1          *       Supports Streaming SIMD Extensions 4.1
SSE4.2          *       Supports Streaming SIMD Extensions 4.2

AES             *       Supports AES extensions
AVX             *       Supports AVX instruction extensions
AVX2            *       Supports AVX2 instruction extensions
AVX-512-F       *       Supports AVX-512 Foundation instructions
AVX-512-DQ      *       Supports AVX-512 double and quadword instructions
AVX-512-IFAMA   *       Supports AVX-512 integer Fused multiply-add instructions
AVX-512-PF      -       Supports AVX-512 prefetch instructions
AVX-512-ER      -       Supports AVX-512 exponential and reciprocal instructions
AVX-512-CD      *       Supports AVX-512 conflict detection instructions
AVX-512-BW      *       Supports AVX-512 byte and word instructions
AVX-512-VL      *       Supports AVX-512 vector length instructions

筆者が作ったプログラムをあえて使いたい人は cpuid.exe または cpuid.zip をダウンロードして実行してください。ブラウザやアンチウイルスソフトの警告が出る場合は自己責任で解除してダウンロードや実行をしてください。ソースコードは プロジェクト ごと公開しています。

supported は対応している、not supported は未対応の意味です。以下の例だとAVXとAVX2には対応していて、AVX512には未対応ということです。

AVX supported
AVX2 supported
AVX512CD not supported

C++から判定したい人、Visual StudioでC++のコードをコンパイルできる人は、Microsoftの解説を参考に自分でコンパイルすることをおすすめします。

cpuid x86およびx64で使用可能な命令(訳注: 拡張命令含む)を生成します。この命令は、サポートされている機能とCPUタイプに関する情報をプロセッサに問い合わせます。 docs.microsoft.com

実行例 Surface Book 2

GenuineIntel
Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
3DNOW not supported
3DNOWEXT not supported
ABM not supported
ADX supported
AES supported
AVX supported
AVX2 supported
AVX512CD not supported
AVX512ER not supported
AVX512F not supported
AVX512PF not supported
BMI1 supported
BMI2 supported
CLFSH supported
CMPXCHG16B supported
CX8 supported
ERMS supported
F16C supported
FMA supported
FSGSBASE supported
FXSR supported
HLE supported
INVPCID supported
LAHF supported
LZCNT supported
MMX supported
MMXEXT not supported
MONITOR not supported
MOVBE supported
MSR supported
OSXSAVE supported
PCLMULQDQ supported
POPCNT supported
PREFETCHWT1 not supported
RDRAND supported
RDSEED supported
RDTSCP supported
RTM supported
SEP supported
SHA not supported
SSE supported
SSE2 supported
SSE3 supported
SSE4.1 supported
SSE4.2 supported
SSE4a not supported
SSSE3 supported
SYSCALL supported
TBM not supported
XOP not supported
XSAVE supported

実行例2 一部抜粋

GenuineIntel
Intel(R) Core(TM) i9-9960X CPU @ 3.10GHz
AVX2 supported
*AVX512CD supported*
AVX512ER not supported
*AVX512F supported*
AVX512PF not supported

実行例3 一部抜粋

GenuineIntel
Intel(R) Core(TM) i7-6900K CPU @ 3.20GHz
AVX2 supported
AVX512CD not supported
AVX512ER not supported
AVX512F not supported
AVX512PF not supported

実行例4 一部抜粋

AuthenticAMD
AMD Ryzen 9 3950X 16-Core Processor
AVX2 supported
AVX512CD not supported
AVX512ER not supported
AVX512F not supported
AVX512PF not supported

実行例5 一部抜粋 Surface Book 3

GenuineIntel
Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
AVX512CD supported
AVX512ER not supported
AVX512F supported
AVX512PF not supported

ThreadPoolの引数を取得する方法と、処理順序を制御できるcondition_variableの使い方

CPUは複数あるので並列化できるが、出力は並列にする意味がない。また、CPUを使った処理は順不同で良いが、出力は順に行いたい。こういう目的のためには、ThreadPoolと、std::condition_variableを使う。

以下の例では、友人が作ってくれた ThreadPool を使っている。使いたい場合は彼に問い合わせて欲しい。CheckSum.hpp phst::base::checksum も独自である。単にハッシュ値 CRC32を計算して、一致または不一致を確認している。

重い処理として、OpenCV内の関数の、画像のエンコード cv::imencodeを使っている。

std::futureget() は実行した時点で std::move するので、複数回呼ぶことはできない。 const std::vector<uchar>& buf = p.get(); の部分

#include <random>

#include <thread>
#include <future>
#include <opencv2/opencv.hpp>
#include <ADAPT/CUF/ThreadPool.h>
#include <CheckSum.hpp>

using namespace std;

TEST(TestStd, TestMyThreadPool) {

    //4Mピクセルの画像を50枚作って乱数を詰める
    int width = 2048;
    int height = 2048;
    std::uniform_int_distribution<> irand = std::uniform_int_distribution<>(0, 255);
    std::mt19937_64 mt;

    std::vector<cv::Mat> vmat;
    for (int j = 0; j < 50; j++) {
        cv::Mat mat = cv::Mat::zeros(cv::Size(width, height), CV_8UC1);
        for (int i = 0; i < width * height; i++) {
            mat.data[i] = irand(mt);
        }
        vmat.push_back(mat);
    }

    //シリアル実行
    {
        std::string filepath = "test1.png";
        std::ofstream ofs;
        ofs.exceptions(std::ios::failbit | std::ios::badbit);
        ofs.open(filepath, std::ios::binary);

        for (int i = 0; i < vmat.size(); i++) {
            std::vector<uchar> buf;
            cv::imencode(".png", vmat[i], buf);
            ofs.write((char*)buf.data(), buf.size()); //順番を守りたい
        }
        ofs.close();
    }

    //ThreadPoolを使ってエンコード部分だけ並列化
    {
        std::string filepath = "test2.png";
        std::vector < std::future<std::vector<uchar>>> vfuture;

        adapt::ThreadPool pool(6);

        for (int i = 0; i < vmat.size(); i++) {

            std::future f = pool.AddTask([](const cv::Mat& mat) {
                std::vector<uchar> buf;
                cv::imencode(".png", mat, buf);
                return buf;
                }, std::reference_wrapper(vmat[i]));
            vfuture.push_back(std::move(f));
        }
        pool.Join();
        std::ofstream ofs;
        ofs.exceptions(std::ios::failbit | std::ios::badbit);
        ofs.open(filepath, std::ios::binary);

        for (auto& p : vfuture) {
            const std::vector<uchar>& buf = p.get();
            ofs.write((char*)buf.data(), buf.size()); //順番を守りたい
        }

        ofs.close();
    }

    //ThreadPoolとcondition_variableを使って全体を並列化
    {
        std::string filepath = "test3.png";

        adapt::ThreadPool pool(6);

        std::ofstream ofs;
        ofs.exceptions(std::ios::failbit | std::ios::badbit);
        ofs.open(filepath, std::ios::binary);

        std::mutex mtx;             //xの管理用mutex
        std::condition_variable cv; //処理順序制御用
        int x = 0;                    //処理すべき関数の番号

        for (int i = 0; i < vmat.size(); i++) {

            std::future f = pool.AddTask([](int n, 
                    const cv::Mat& mat, std::ofstream& ofs, 
                    std::mutex& mtx, std::condition_variable& cv, int& x) {
                std::vector<uchar> buf;
                cv::imencode(".png", mat, buf);

                std::unique_lock<std::mutex> ul(mtx); //mutexをロック
                while (x != n) { cv.wait(ul); }       //他スレッドからnotifyが来るまで待機
                ofs.write((char*)buf.data(), buf.size());  //順番を守りたい
                x++;                                //次の処理へ
                cv.notify_all();                        //状態の変化を他のスレッドへ通知
                },
                i, std::reference_wrapper(vmat[i]), std::reference_wrapper(ofs),
                    std::reference_wrapper(mtx), std::reference_wrapper(cv), std::reference_wrapper(x));
        }
        pool.Join();

        ofs.close();
    }

    //ThreadPoolの処理は順不同であることの確認
    {
        std::string filepath = "test4.png";

        adapt::ThreadPool pool(6);

        std::ofstream ofs;
        ofs.exceptions(std::ios::failbit | std::ios::badbit);
        ofs.open(filepath, std::ios::binary);

        for (int i = 0; i < vmat.size(); i++) {

            std::future f = pool.AddTask([](const cv::Mat& mat, std::ofstream& ofs) {
                std::vector<uchar> buf;
                cv::imencode(".png", mat, buf);
                ofs.write((char*)buf.data(), buf.size());  //順番を守りたい
                },
                std::reference_wrapper(vmat[i]), std::reference_wrapper(ofs));
        }
        pool.Join();

        ofs.close();
    }

    EXPECT_EQ(phst::base::checksum("test1.png"), phst::base::checksum("test2.png"));
    EXPECT_EQ(phst::base::checksum("test1.png"), phst::base::checksum("test3.png"));
    EXPECT_NE(phst::base::checksum("test1.png"), phst::base::checksum("test4.png"));

}

スレッドプール

なぜパスワードで暗号化したファイルをメールで送ってはいけないのか

セキュリティの基本は「人は愚かである」という前提に立つことである。しかし、我が国のセキュリティ意識は多くは「頑張ればなんとかなる」精神に立っており、しばしば誤った頑張り方が重大なインシデントを招く。その一つが、

個人情報や機密情報を送ろうとするときに、PDFファイル、Excelファイル、Wordファイル等にパスワードをかけて暗号化して送り、別のメールでパスワードを送る という日本人固有のセキュリティ仕草

である。パスワードをかけて送ることの動機は単純で、普段と違うものを送信するのだから、普段とは違う行為をしなければならないと考えているのだ。(なんとか認証を受けるためとも言われてる。)この普段とは違うとは、セキュリティを本当に高める行為ではなく、自分が知っていて、相手も知っていて、なんとなく頑張った気になれる方法という意味である。

なぜ暗号化は無意味か?

そもそも、我々は、個人情報や機密情報を誰(何)から守らなければならないのだろうか。通常、不正アクセスしようとするときは、次のような手段が考えられる。

  1. メールを送った人のパソコンに物理的に不正アクセス
  2. メールを送った人のパソコンやメールボックスに遠隔で不正アクセス
  3. 送信の途中経路で情報を搾取
  4. メールを受信した人のパソコンやメールボックスに遠隔で不正アクセス
  5. メールを受信した人のパソコンに物理的に不正アクセス

ファイルをパスワードで暗号化し、パスワードを別送すると、どのケースで情報を守ることができるのだろうか?

答えは、3以外のケースで守れないである。

1.と 5.の物理的にアクセスできるなら、パスワードを書いた別メールも閲覧できるため無意味である。2. と 4. も同じで、パソコンやメールボックスにアクセスできるなら、別送のメールを即座に削除しているのでなければ無意味である。3. については、送信サーバーと受信サーバーの経路間が暗号化されていれば、パスワードをかけていなくても盗聴されないし、経路間の暗号化に対応してない場合は、パスワードをかけていても全てのメールを盗聴し放題なので無意味である。

すなわち、守れる限定的なケースは、送信者・受信者が誤ってそのファイルを無関係な人に渡したり送信したりしてしまうケースのみである。誤送信してしまう可能性まで考慮するなら、暗号化前のファイルを送信してしまう可能性もあるし、根本的な解決策にはなってない。しかも、暗号化に脆弱性があったり、パスワードが短かったりすると、意味をなさない。

なぜ暗号化は有害か?

パスワードをかけた暗号化したファイルの送受信は、別の観点からも危険な行為である。

通常、添付ファイルがウイルスに感染しているかどうかは、メールボックス(Gmailなど)と、パソコンのセキュリティソフト(Windowsセキュリティなど)の2つ*1の関門で検査する。パスワードで暗号化されたファイルは、中身がウイルスに感染していてもメールボックス(Gmailなど)では検知できず、最初の関門、そしてGmailであれば最強の関門をスルーしてしまうのだ。

普段からパスワード付きメールを送る傾向にある組織は、セキュリティ意識が低い場合が多く、メールボックスでの添付ファイルの検査が不十分だったり、パソコンのセキュリティソフトやOfficeソフトウェアがアップデートされていなかったりと、ウイルス感染のリスクが高い。普段から暗号化されたファイルの送受信をしていれば、そういうなりすましメールを受信した時に、疑いなくウイルスをパソコンにダウンロードし、開いてしまいやすい。ゆえに、そういう組織のパソコンを一つでも乗っ取ってしまえば、そこから関係者に感染を広げることが容易であると言える。

ではどうすればよいのか

まず、添付ファイルを使わないことが第一である。暗号化有り無しに関わらず、添付ファイルで個人情報や機密情報を送らない方が良い。

同一組織内であれば、各個人が保有している認証情報を使ってアクセスできる場所にファイルを置き、閲覧者を限定し、そのURLをメールで送るのが良い。認証情報はその個人しか保有していないと仮定してよいし、そういうサービスはほぼ確実に暗号化されているので盗聴の心配も少ないだろう。仮にURLが漏れても、他の人はアクセスできない。他にも、SlackやTeamsなどのようなチャットツール経由で送るのも有効である。閲覧者が限定できかつ暗号化されている。

異なる組織間であれば、個人情報や機密情報を扱うなと言いたい。個人情報や機密情報は異なる組織間で勝手に共有していいものではない。どうしても必要ならば、仮の組織を作って、上記のように各個人が保有している認証情報を使ってアクセスできるようにするか、できなければメールアドレスを保有していることを要件としてアクセスを可能にできるクラウドサービスOneDriveやGoogleドライブを使うべきである。URLが漏れてもメールアドレスの所有者でない限りアクセスできないケースが多い。

また、同一組織内・異なる組織間のいずれのケースでも、閲覧者にファイルをダウンロード・印刷させず、ウェブブラウザでの閲覧に制限できるならなお良い。さらに、一定期間が過ぎるとURLが無効になる設定や、ダウンロード回数に制限を設ける設定も有効だろう。

まとめ

人は愚かである。セキュリティは破られるという立場に立つと、やるべきは破られた時に発覚しやすく、他者への影響が最小限であることが求められる。パスワードで暗号化したメールは、どのケースでも個人情報や機密情報が守れないばかりでなく、セキュリティインシデントの発覚を遅らせ、他者への悪影響がより大きい行為であり、セキュリティ規則で禁止してしまったほうがいい行為であるといえる。

この件に限らず、セキュリティの常識は年々変化する。例えば、コンピュータ(特にGPU)の発展から、パスワード長に対する要件が年々厳しくなっている(ファイルへの8桁パスワードなら数時間で破られる)。 よって、定期的に専門家の監査を受けるとか、普段からアンテナを高くして最新のセキュリティ事情を知るとか、そういう姿勢が、これから社会人になる皆さんには求められているだろう。

*1:OfficeやAdobeなどのソフトウェアに脆弱性がないことも重要だが、ウイルスを検知できるわけではないので、ここでは2つとする

OpenCVのPNG形式でエンコードする時の各パラメータの圧縮率と圧縮時間について

OpenCVで画像をPNGファイル、すなわち可逆な形式で圧縮し、ファイルで出力することなくデータを取得したいとき、 cv::imencode というエンコード関数を使う。原理的には以下のように記述する。(そのままでは動かないよ)

vector<int> params = vector<int>(2);
params[0] = CV_IMWRITE_PNG_COMPRESSION;
params[1] = 3; //level
std::vector<uchar> buf;
cv::imencode(".png", img, buf, params);

ここで、paramsの中身について色々と調べてみた。一種の使い方でもある。以下、CV_IMWRITE_PNG_ は略す。

params[0] params[1] 説明
COMPRESSION=16 0-9 圧縮する。レベルは0-9まで可変
STRATEGY=17 STRATEGY_DEFAULT=0 ノーマルデータ
STRATEGY_FILTERED=1 フィルターされた?
STRATEGY_HUFFMAN_ONLY=2 ハフマン符号化
STRATEGY_RLE=3 一致距離を1つに制限
STRATEGY_FIXED=4 単純なデコーダーが可能になる?
BILEVEL=18 0 2値化=Bilevelなし
0以外 2値化=Bilevelあり

COMPRESSION では、ストラテジーSTRATEGY_RLE が使われ、圧縮は必ず行われる。

STRATEGYBILEVEL を使うと、圧縮レベルは内部的に-1アサインされ、圧縮されない。

paramsを与えない場合、STRATEGYSTRATEGY_RLEと同じになる。

画像とビデオの読み込みと書き込み — opencv v2.1 documentation

利用可能な上記のテーブルのパラメータについて、圧縮後のデータサイズと処理時間を測定してみた。圧縮レベルは0-3のみ試した。 条件は、ピクセルサイズは2048×1088で、Sizeは出力ファイルサイズ[byte]、Timeは320回の平均の処理時間[msec]である。

2値画像

輝度値、すなわち要素の値が0または1の二値画像(元は原子核乾板の顕微鏡画像)に対して行った。

params[0] params[1] Size Time
16 0 118,938 26.3
16 1 49,204 24.5
16 2 49,204 23.8
16 3 49,204 23.6
17 0 95,730 8.35
17 1 95,730 8.77
17 2 290,162 18.2
17 3 61,402 7.43
17 4 115,524 8.29
18 0 61,402 7.72
18 1 64,694 6.44
bmp bmp 2,229,302 1.17

Timeが3倍変わるのはおいしくないので、 CV_IMWRITE_PNG_STRATEGYCV_IMWRITE_PNG_STRATEGY_RLE あたりがバランスが良いだろうか。

通常のモノクロ画像

輝度値、すなわち要素の値が0から255までの256階調のモノクロ画像(原子核乾板の顕微鏡画像)に対して行った。

params[0] params[1] Size Time
16 0 2,233,319 48.8
16 1 1,141,548 53.0
16 2 1,141,548 52.4
16 3 1,141,548 53.4
17 0 1,371,852 47.2
17 1 1,371,852 47.5
17 2 1,212,549 25.1
17 3 1,212,854 27.8
17 4 1,925,222 49.6
18 0 1,212,854 27.2
18 1 エラー エラー
bmp bmp 2,229,302 1.03

CV_IMWRITE_PNG_STRATEGYCV_IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY あたりがバランスが良いだろうか。

カラー画像は用途外なので試していない。

乱数で生成したモノクロ画像

ピクセルサイズ2048×1088の画像に、全てのピクセルに乱数で適当な輝度値を与えた。

params[0] params[1] Size Time
16 0 2,233,324 49.1
16 1 2,233,324 52.3
16 2 2,233,324 52.6
16 3 2,233,324 53.5
17 0 2,233,319 70.6
17 1 2,233,319 67.8
17 2 2,233,324 24.3
17 3 2,233,324 27.6
17 4 2,233,319 76.1
18 0 2,233,324 26.1
18 1 エラー エラー
bmp bmp 2,229,302 1.17

ランダムな輝度値の画像は、想像通りほぼ圧縮されていない。こういうケースでは、bmpをそのまま出力するべきだろう。

パラメータ名の変更

OpenCV3系列から、 CV_IMWRITE_PNG_STRATEGY_DEFAULT 等のグローバル変数は、名前空間 cv内の変数に変わっています。なので、以下のパラメータ名を使ってください。

 cv::IMWRITE_PNG_COMPRESSION
 cv::IMWRITE_PNG_STRATEGY
 cv::IMWRITE_PNG_BILEVEL

 cv::IMWRITE_PNG_STRATEGY_DEFAULT
 cv::IMWRITE_PNG_STRATEGY_FILTERED
 cv::IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY
 cv::IMWRITE_PNG_STRATEGY_RLE
 cv::IMWRITE_PNG_STRATEGY_FIXED