c/c++におけるerrno値の定義

CtelloのGitHubのソースをビルドして実行すると以下のようなエラーが出ました。

最初は何気なくTello sdk 2.0用だからと思ってましたが、考えてみれば引数のエラーだから2.0は無関係。

cello.cppで、

#include <errno.h>

されているので、errno.hの中身を追跡するとエラー番号一覧がわかるはず。

コードの中では、

strerror(errno)

を使ってデコードしてますが、

[11/09/22 12:28:51] [ctello] [info] Finding Tello ...
[11/09/22 12:28:51] [ctello] [error] sendto: 22 (Invalid argument)
[11/09/22 12:28:52] [ctello] [error] sendto: 22 (Invalid argument)
[11/09/22 12:28:53] [ctello] [error] sendto: 22 (Invalid argument)

 

基本のエラー番号をまとめてるサイトがあって、

https://qiita.com/h2suzuki/items/0cc924cdd9d5c6d47448

こちらは有用だと思います。

肝心の引数エラーの原因調査はこれから。

 

admin

c++でtello制御(コマンドファイル実行とビデオストリーム収録を並行実行)

Telloからビデオストリーム受信して、デコードして表示とローカルファイルに格納、同時にコマンドファイルからコマンド読み出して並行に実行させるバージョンです。

Telloにコマンド送って時間待ち5秒は冗長だろうしスマートでもないから改善余地ありです。

コードはこちら、

https://github.com/chateight/c_plus_video

ctello_stream.cppが今回のソースです。

 

admin

 

Spdlog(c++ログツール)

Ctelloでspdlogが必須だったので、触ってみました。

spdlogはc++用のログツール、c++に標準で存在しないのは不思議な気もしますが。

本家

https://github.com/gabime/spdlog

使用例も豊富です。

 

<インストール>

① 以下のヘッダーファイルをコピー

https://github.com/gabime/spdlog/tree/v1.x/include/spdlog

② おすすめは、

“Compiled version (recommended – much faster compile times)”

と言ってますが、macだとbrewが一番簡単でしょう。

% brew install spdlog

% brew list --version|grep spdlog

spdlog 1.10.0_1

 

<サンプルコード>

#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"

int main()
{
    spdlog::info("infromaion!");
    spdlog::warn("warning!");
    spdlog::error("error!");

    std::string test_message = "OK";
    spdlog::info("Test result is {}", test_message);

    int number_int = 10;
    float number_float = 12.345;
    spdlog::info("number_int is {:03d}", number_int);

    // set log level which also affects to the file log
    spdlog::set_level(spdlog::level::warn);
    spdlog::info("number_float is {:03.2f}", number_float);

    // logging to the log file
    auto log_file = spdlog::basic_logger_mt("logger", "log/basic.txt");
    log_file -> info("hello!");
    spdlog::set_default_logger(log_file);
    spdlog::warn("warning! write to the log file");
}

 

<使い方(cmakeファイル)>

https://github.com/gabime/spdlog/blob/v1.x/example/CMakeLists.txt

から以下のmakefileで、

cmake_minimum_required(VERSION 3.14)
project(SpdProject)

set(CMAKE_CXX_STANDARD 14)

if(NOT TARGET spdlog)
    # Stand-alone build
    find_package(spdlog REQUIRED)
endif()

# ---------------------------------------------------------------------------------------
# Example of using pre-compiled library
# ---------------------------------------------------------------------------------------

add_executable(spdlogTest src/spdlog_t.cpp)
target_link_libraries(spdlogTest PRIVATE spdlog::spdlog)

 

<実行結果>

・コンソール

% ./build/spdlogTest 
[2022-11-06 18:49:01.191] [info] infromaion!
[2022-11-06 18:49:01.192] [warning] warning!
[2022-11-06 18:49:01.192] [error] error!
[2022-11-06 18:49:01.192] [info] Test result is OK
[2022-11-06 18:49:01.192] [info] number_int is 010

・ログファイル(basic.txt)

実行の都度追記されます。

[2022-11-06 18:34:16.437] [logger] [warning] warning! write to the log file
[2022-11-06 18:49:01.192] [logger] [warning] warning! write to the log file

 

<ソース他>

ソースコード他は、以下に置いてあります。

https://github.com/chateight/spdlog_ex

 

admin

c++でtelloを制御する

Pythonからc++のライブラリ呼ぶのは、ライブラリのビルド条件が複雑なので、いっそ全てをc++で作りあげるのが良さそうです。opencvもc++用が存在してるわけだから。

SDK 1.3に記述ある通り、UDPの送信とステータスの受信ポートが違うことは要注意です。

https://dl-cdn.ryzerobotics.com/downloads/tello/20180910/Tello%20SDK%20Documentation%20EN_1.3.pdf

やっていることはPython版と同じく、外部のテキストファイルに記述されたコマンドを順次telloに送ってステータスを受信しているだけです。

実は、コマンドの実行結果を受け取る前に次々にコマンド送ってるから、スプールされてしまっているようだから、作りが変だとは思う。

コードはこちら、

https://github.com/chateight/c_plus_video

ソケット通信の基本の解説は、

https://qiita.com/Michinosuke/items/0778a5344bdf81488114

通信サンプルは、

https://qiita.com/srs/items/c9286b5cff99e741b993

 

c++での制御は、すでに先駆者がいるから、この先はこれを使った方がいいでしょう。

https://github.com/carlospzlz/ctello

P.S. 2022/11/5

これはTello EDU用でした、違いを修正してもいいかもしれないけど。

 

動作は他の手段で動かすのと同じ。

 

admin

h264decoder共有ライブラリはBoost.Python使っている?

以下の記事中で、

https://isehara-3lv.sakura.ne.jp/blog/2022/10/28/telloで画像転送python-h-264共用ライブラリ/

c++のライブラリをPythonから呼び出すためにpybind11のインストールが指定されていますが、c++ライブラリのラッパーファイル(h264decoder_python.cpp)を見てみると、pybind11ではなくてBOOST_PYTHONが使われているからpybind11は不要だよねと思いました。

BOOST_PYTHON_MODULE(libh264decoder)

試しに、仮想環境(v_python)からpybind11をpip uninstall pybind11しても動作はするから、やはり不要らしい。

Boost.Pythonをpybind11と比較するとサイズが巨大らしい。pybind11の方が後発だから、恐らく機能的には優っているだろうしユーザーも多いのではないかと思います。

P.S. 2022/10/31

H.264 decoderのビルドのためのCMakeLists.txtを見てみるとpybind11を探して、もし存在しなければ持ってきてるからビルドには必要とされているようです、何故だろう?

find_package(pybind11)
if(pybind11_FOUND)
  message("Using existing pybind11 v${pybind11_VERSION}")
else()
  message("Fetching pybind11")
  include(FetchContent)
  FetchContent_Declare(
    pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11
    GIT_TAG v2.5.0)
  FetchContent_MakeAvailable(pybind11)
endif()

P.S. 2022/11/7

今更ながらですが、改めて見るとpybind11使ってました。じゃ最初のソースは何見たんだろう?とりあえずすっきりはしましたが、

PYBIND11_MODULE(h264decoder, m)
{
  PyEval_InitThreads(); // need for release of the GIL (http://stackoverflow.com/questions/8009613/boost-python-not-supporting-parallelism)
  py::class_(m, "H264Decoder")
                            .def(py::init<>())
                            .def("decode_frame", &PyH264Decoder::decode_frame)
                            .def("decode", &PyH264Decoder::decode);
  m.def("disable_logging", disable_logging);
}

 

admin

includePathが見つからない(Intel Mac)他

久々にIntelMacとM1MacでVScode環境でc++コンパイルしようとしたらいくつかのエラー発生。IntelMacは問題が二箇所で、M1Macは一箇所だけでしたが

— Intel Mac —

<問題1:includePathが見つからない>

以下を参考にパスを追加

https://jpdebug.com/p/151056

% g++ -v -E -x c++ –

で検索されたパスをc_cpp_properties.jsonに追記してコンパイルはできるようになったけど波線(つまり見つからないエラー)は消えないので、以下を追加

c_cpp_properties.jsonの、

“compilerPath”: “/usr/bin/clang”,

“compilerPath”: “/usr/bin/g++”,

に変えたら解決したようだ、これは即ち

% where g++ 

/usr/bin/g++

で示されるパスですが

<問題2:無限にXcodeコマンドラインツールをインストールしろメッセージ>

Xcodeコマンドラインツールをインストールしろと怒られて、何回インストールしても直らない

https://qiita.com/arks22/items/bb1a70a4803881c4e4e1

が該当していて、エラーメッセージの中に確かに解決案が提示されてます

~~~~~

Please ensure Xcode packages are up-to-date — try running ‘xcodebuild -runFirstLaunch’.

~~~~~

で以下の実行で解決したようです

% xcodebuild -runFirstLaunch

どこかのOSのアップデート、恐らく至近、で問題が出るようになったんでしょう

 

— M1Mac —

MacBook Air(M1)ではg++の版数を再指定(古い版数でコンパイルされたので)しなければいけなかったし、これもアップデート影響なのか

感覚的にはかりそめの解決のような気もするけれども、元々開発環境とはそういうものだろう

 

admin

new int[]とstd::vectorクラスでの実行速度差

<動作環境>

Intel MacBook Pro 16

 

<メモリ管理>

new int[]方式はメモリの領域管理、特にリリースを自分で管理しなければいけないのでバグの入り込み余地が多いのですが、コンテナクラスを使うことでその可能性を少なくしてくれます。しかし、想像するにパーフォーマンスは落ちるでしょう。ということで比べてみました。

#include <iostream>
#include <utility>
#include <vector>

const int a_size = 1000000*100;

class home
{
    int* m_land;
    int* cp_land;

public:
    explicit home(int size)
        : m_land{new int[size] ()}{}
    
    ~home() { delete [] m_land;}

    home(home&& other);

    int* land() const { return m_land; }

    int* land_cp() const { return cp_land; }

    void copy()
    {
        cp_land = new int[a_size];
        for (int i = 0; i <= a_size; i++)
        {
            cp_land[i] = m_land[i];
        }
    }

    void v_copy()
    {
        std::vector v_int(a_size, 0);
        //std::vector vc_int(a_size);
        //std::copy(v_int.begin(), v_int.end(), vc_int.begin());
        std::vector vc_int = v_int;
    }

    void del()
    {
        delete [] cp_land;
    }
};

home::home(home&& other)
    : m_land{other.m_land}
{
    other.m_land = nullptr;
}


int main()
{
    std::chrono::system_clock::time_point  start, end;
    start = std::chrono::system_clock::now();

    home A{a_size};
    std::cout << "A\'s land address : " << A.land() << std::endl;

    A.copy();
    std::cout << "A\'s cp_land address : " << A.land_cp() << std::endl;
    A.del();

    home B{std::move(A)};
    std::cout << "B\'s land address : " << B.land() << std::endl;

    B.v_copy();

    std::cout << std::endl;
    end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast(end-start).count();
    std::cout << "elapsed time : " << elapsed << " ms" << std::endl;
}

追加したのはv_copyメソッドとその中でパターンの変更とcopyメソッド使う使わないはソース(35~38行目、69行目の該当行コメントアウト)で対応。

 

<実行結果>

① std::vectorで領域確保だけで2秒近く要している

② 領域のパディングは自動と明示である程度の差がある、new int[]は0でしかできないけど、vectorは任意の値で初期化できるのはアドバンテージ

③ コピーは単純コピーが相対的std::copyより早いけど、それでもマニュアルのforループコピーよりもおよそ2倍(std::copyはおよそ4倍)の時間が掛かる

④ std::copyは実行時間最遅

領域サイズ:100MB

<初期状態:マニュアル(new int[])で領域確保とコピー>
A's land address : 0x7fad25700000
A's cp_land address : 0x7fad05700000
B's land address : 0x7fad25700000

elapsed time : 497 ms


<領域確保:おそらく0埋めされるはず>
A's land address : 0x7f8996700000
A's cp_land address : 0x7f8976700000
B's land address : 0x7f8996700000

elapsed time : 2292 ms


<領域初期化値を明示的に追加>
A's land address : 0x7f8549700000
A's cp_land address : 0x7f8529700000
B's land address : 0x7f8549700000

elapsed time : 2518 ms


<別のstd::vectorにコピー>
A's land address : 0x7fc174f00000
A's cp_land address : 0x7fc154f00000
B's land address : 0x7fc174f00000

elapsed time : 3510 ms


<コピーにstd::copyを使用>
A's land address : 0x7f8b68700000
A's cp_land address : 0x7f8b48700000
B's land address : 0x7f8b68700000

elapsed time : 4396 ms


便利・セキュアさと処理時間は相反するというのは、まあそんなものでしょうか。

ソースは、

https://isehara-3lv.sakura.ne.jp/blog/2022/07/13/右辺値参照の代表的使い方である移譲処理でのパ/

の記事に追加して以下のurlになります。

https://github.com/chateight/c-plusplus/blob/master/move_perform.cpp

 

P.S. @2022/7/21

M1 MacBook Airで同じ処理実行させると処理速度が全く違う。いずれのケースでも1秒以下(700ms程度)で実行されるから、その差はなんだろう?

そのままのソースではコンパイルできないので、#include <chrono>は必要でしたが、それは本質ではない。

可能性としては、コンパイラーのバージョンぐらい(c++11 vs c++20)なのか?MacBook Pro 16のコンパイルオプションを-std=c++20にしても変わらないからそうじゃないよね。だとすると処理系の差?

 

admin

右辺値参照の代表的使い方である移譲処理でのパーフォーマンス効果

<実行環境>

Intel MacBook pro 16

 

<右辺値参照で移譲処理>

コピーは性能に大きな影響があるのは常ですが、じゃどれぐらい影響あるのか試してみました。

やってること、

① メモリ領域確保(初期化 or 未初期化:13行目の()有無)、この場合には100MB確保と領域の移譲処理

② 領域のコピー(有り or 無し:53/54/55行目のコメントアウト有無)

の4パターンの実行速度比較

#include <iostream>
#include <utility>

const long a_size = 1000000*100;

class home
{
    int* m_land;
    int* cp_land;

public:
    explicit home(long size)
        : m_land{new int[size] ()}{}
    
    ~home() { delete [] m_land;}

    home(home&& other);

    int* land() const { return m_land; }

    int* land_cp() const { return cp_land; }

    void copy()
    {
        cp_land = new int[a_size];
        for (int i = 0; i <= a_size; i++)
        {
            cp_land[i] = m_land[i];
        }
    }

    void del()
    {
        delete [] cp_land;
    }
};

home::home(home&& other)
    : m_land{other.m_land}
{
    other.m_land = nullptr;
}


int main()
{
    std::chrono::system_clock::time_point  start, end;
    start = std::chrono::system_clock::now();

    home A{a_size};
    std::cout << "A\'s land address : " << A.land() << std::endl;

    A.copy();
    std::cout << "A\'s cp_land address : " << A.land_cp() << std::endl;
    A.del();

    home B{std::move(A)};
    std::cout << "B\'s land address : " << B.land() << std::endl;

    std::cout << std::endl;
    end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast(end-start).count();
    std::cout << "elapsed time : " << elapsed << " ms" << std::endl;
}

<結果>

・領域初期化有りモードではおおよそコピー時間差(領域初期化の有無で、領域コピー有りの時間にそれほど差はないのはなぜかと思うけど)、これを見ると領域の初期化も当然重い処理。

・領域初期化無では領域コピー実行の比重がほぼ100%、コピーしなければ1ms以下で処理完了してます。

・いずれにしろ、これらの処理が繰り返し実行される場合には大きなパーフォーマンスの差になるから、移譲は使える時には使ったほうが良い。

領域サイズ:100MB

<領域初期化有り>

・std::move
A's land address : 0x7ff049f00000
B's land address : 0x7ff049f00000

elapsed time : 166 ms

・+ 領域copy
A's land address : 0x7fb615700000
A's cp_land address : 0x7fb5f5700000
B's land address : 0x7fb615700000

elapsed time : 546 ms


<領域初期化無し>

・std::move
A's land address : 0x7fb23ff00000
B's land address : 0x7fb23ff00000

elapsed time : 0 ms

・+ 領域copy
A's land address : 0x7fb03df00000
A's cp_land address : 0x7fb01df00000
B's land address : 0x7fb03df00000

elapsed time : 514 ms

ソースは以下のリンクから、

https://github.com/chateight/c-plusplus/blob/master/move_perform.cpp

メモリ管理をマニュアルでやっていますが、vectorクラスのようなコンテナを使ったときのパーフォーマンスにも興味があります。

 

<疑問一点>

デストラクターでcp_landの解放すると、

*** error for object 0x7ff7b9aae490: pointer being freed was not allocated

のエラーが出ますが、なぜ?取り敢えずはdel()メソッド追加で対応してますが。

 

admin

 

c++ threadインスタンスを無名にしてvectorに格納

マルチスレッドで、スレッドに名称をつけてスレッド数を管理するのは面倒です。

スレッド名を無名にして、vectorに格納しても同じ処理でのスレッド名に意味はないからその方が便利です。

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <cmath>

std::mutex mtx_;                // mutex for exclusive control
std::vector vec{};         // prime numbers array
int cal_int = 1;                // calc target integer
int max_int = 10000000;         // calc target max number
std::vector threads;       // array of threads
int num_thread = 12;                    // number of threads



void add_prime(int i)           // store the prime numberns in the vector
{
    std::lock_guard lock(mtx_);
    vec.push_back(i);
}

int get_int()                   // get the integer to be tested
{
    std::lock_guard lock(mtx_);
    ++cal_int;
    if (cal_int > 2 && cal_int%2 == 0)      // do not return an even number larger than four
    {
        ++cal_int;
    }
    return cal_int;
}

void ThreadA()
{
    while (true)
    {
        bool flag = false;
        int i = get_int();
        if (i > max_int)
        {
            break;
        }
        int sqt = sqrt(i);
        for (int j = 2; j <= sqt; ++j)
        {
            if (i%j == 0)
            {
                flag = true;
                break;
            }
        }
        if (flag != true)
        {
            add_prime(i);
        }
    }
}

int main()
{
    std::chrono::system_clock::time_point  start, end;
    start = std::chrono::system_clock::now();

    std::cout << std::endl;
    std::cout << "Hardware concurrency = " << std::thread::hardware_concurrency() << std::endl;

    for(size_t i=0; i < num_thread; ++i){
        threads.emplace_back(std::thread(ThreadA));
    }

    for(auto& thread : threads){
        thread.join();
    }

    std::cout << std::endl;
    std::cout << "Number of threads = " << num_thread << std::endl;

    std::cout << std::endl;
    end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast(end-start).count();
    std::cout << "elapsed time : " << elapsed << " ms" << std::endl;

    std::cout << std::endl;
    std::cout << "number of prime numbers : " << vec.size() << std::endl;

    std::cout << std::endl;
    std::cout << "last five prime numbers" << std::endl;
    std::sort(vec.begin(), vec.end() );
    
    for (auto itr = (vec.end() - 5); itr != vec.end(); ++itr)
    {
        std::cout << *itr << std::endl;
    }

}

こうすれば、変数を一箇所変えるだけでスレッド数が自由に変更できます。4以上の偶数は素数判定スレッドに渡さないで、処理時間短縮も図っています。

 

<実行結果>

Hardware concurrency = 12

Number of threads = 12

elapsed time : 1358 ms

number of prime numbers : 664579

last five prime numbers
9999937
9999943
9999971
9999973
9999991

https://isehara-3lv.sakura.ne.jp/blog/2022/05/01/c-マルチスレッドの効果/

で、およそ1.8秒でしたが1.3秒程度まで高速化。所詮、多数桁の素数計算には誤差とも言える範囲ですが。

 

admin

 

c++ マルチスレッドの効果

マルチスレッドは多くのアプリケーション、わかりやすいのはブラウザあたり、で普通に使われてますが、多重度を上げても必ずしも性能が向上するわけではありません。それはリソースの排他制御などでオーバーヘッドが発生するから。

で、素数計算プログラムでマルチスレッドの効果を検証してみた。

<ソースコード>

スレッド制御はstd::threadクラスを使います。

https://cpprefjp.github.io/reference/thread/thread.html

スレッドプログラミングで問題になる共通リソース、この場合には計算対象の整数(cal_int)と計算結果の格納(vec{})を排他制御(mutex)で、壊れないように制御します、理屈はセマフォーと同じです。

同じ処理の多重化ならば、特定の関数をスレッド化する訳なので、元のコードは一個で構いません。

スレッド数の可変は対象部分(std::thread th_x(ThreadA); とth_x.join();)をコメントアウトで対応。

算出結果のvectorはスレッドの出力タイミングでソートされた結果にはならないから、処理完了後にソートを実行。

スレッド処理の記述は、

https://qiita.com/nsnonsugar/items/be8a066c6627ab5b052a

を参考。

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <cmath>

std::mutex mtx_;                // mutex for exclusive control
std::vector vec{};         // prime numbers array
int cal_int = 1;                // calc target integer
int max_int = 10000000;          // calc target max number


void add_prime(int i)
{
    std::lock_guard lock(mtx_);
    vec.push_back(i);
}

int get_int()
{
    std::lock_guard lock(mtx_);
    ++cal_int;
    return cal_int;
}

void ThreadA()
{
    while (true)
    {
        bool flag = false;
        int i = get_int();
        if (i > max_int)
        {
            break;
        }
        int sqt = sqrt(i);
        for (int j = 2; j <= sqt; ++j){
            if (i%j == 0){
                flag = true;
                break;
            }
        }
        if (flag != true){
            add_prime(i);
        }
    }
}

int main()
{
    std::chrono::system_clock::time_point  start, end;
    start = std::chrono::system_clock::now();

    std::cout << std::endl;
    std::cout << "Hardware concurrency = " << std::thread::hardware_concurrency() << std::endl;

    std::thread th_a(ThreadA);
    std::thread th_b(ThreadA);
    std::thread th_c(ThreadA);
    std::thread th_d(ThreadA);

    th_a.join();
    th_b.join();
    th_c.join();
    th_d.join();

    std::cout << std::endl;
    end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast(end-start).count();
    std::cout << "elapsed time : " << elapsed << " ms" << std::endl;

    std::sort(vec.begin(), vec.end() );
    for (auto itr = (vec.end() - 5); itr != vec.end(); ++itr){
    std::cout << *itr << std::endl;
    }

    return 0;
}

 

<実行結果>

対象が百万までではスレッド数が2以上による実行時間の差はほとんどない。スレッドの実行時間よりもオーバーヘッドの方が支配的だからだろう。

一方、千万までの計算ではスレッド処理の比重が増加して、スレッド数を増やせば実行時間は速くなるから、スレッド処理が支配的になるせいでしょう。

Hardware concurrency = 12

—100000まで計算—

1 thread

elapsed time : 223 ms
999953
999959
999961
999979
999983


2 threads 

elapsed time : 177 ms
999953
999959
999961
999979
999983


3 threads

elapsed time : 184 ms
999953
999959
999961
999979
999983


4 threads

elapsed time : 194 ms
999953
999959
999961
999979
999983


—10000000まで—

1 thread

elapsed time : 4519 ms
9999937
9999943
9999971
9999973
9999991


2 threads

elapsed time : 2646 ms
9999937
9999943
9999971
9999973
9999991


3 threads

elapsed time : 2103 ms
9999937
9999943
9999971
9999973
9999991


4 threads

elapsed time : 1829 ms
9999937
9999943
9999971
9999973
9999991



 

という結果が言わんとすることは、各スレッドの処理が重ければCPUのマルチコアが有効に働く、しかし処理が軽いとオーバーヘッドによりほとんど実行時間には影響しない。もちろんマルチスレッドの目的は処理の分散・並行処理による高速化以外に各スレッド処理のリアルタイム性の側面もあるのだから、一概には言えないわけだけれども、いずれにしろ適用アプリケーションを事前に考慮した上での実装が必要という当たり前の結論です。

 

admin