c++

rust vs. python

前回の最後に眼をつけておいた事例をやってみた。と、言っても例によってrustの部だけだけどね。これを走らせるには、コンパイラーのrustと、そのパッケージングシステムのcargoが必要。

初回起動時にはネットから178個ものパッケージを自動でDLしてコンパイルするんで、待ちくたびれるはめになる。どうも、次回の記事向けに余分なパッケージもDLしてる節がある。だって、今回の記事には不必要なhttp関連もやつもDLしてるからね。

sakae@deb:/tmp/web_engineer_in_rust/chapter2/rust$ cat .rust-version
rustc 1.56.1 (59eed8a2a 2021-11-01)

記事では、ここが1.56.0になってたんで、自システムに合わせておいた。元のままだと、微妙にバージョンが違うrustcが用意されるのだろうか? こういう暗部には関わりたくない。

DLされて事前にコンパイルされた奴はtarget内に格納される。

sakae@deb:/tmp/web_engineer_in_rust/chapter2/rust$ du -sh target/
461M    target/

ちっともエコ・システムになっていないのが気に食わん。まあ、バージョンが固定され、事前コンパイルで、省時間を達成するなら、しょうがないんかな。

sakae@deb:/tmp/web_engineer_in_rust/chapter2/rust$ cargo run --release --bin run_calc_stream_channel
    Finished release [optimized] target(s) in 0.19s
     Running `target/release/run_calc_stream_channel`
移動平均の長さ:7
移動平均の最後の要素:1428573.5714285714
計算にかかった時間:75.004044646秒
Vecの使用メモリ量(参考):79.999952MB
プロセスの使用メモリ量(参考):82.993152MB
移動平均の長さ:5000
移動平均の最後の要素:1428216.9284
計算にかかった時間:74.76793098秒
Vecの使用メモリ量(参考):79.960008MB
プロセスの使用メモリ量(参考):163.606528MB

記事では4種類の環境を試せるようになっているけど、上記はそのうちの一つの実行例。事前にコンパイルをしてあるので、実行に取り掛かるまでの時間は、気にならない。言わば、Makefileが内蔵されてて、makeが走っているんだな。

それにしても、どんな機能が有るんか分からない、tokioなんて名前のパッケージがDLされるのは、少々気味が悪いと思うのは、オイラーだけかな? そんな事を言ってたら、世の中にある言語システムなんて、何も使えなくなるぞ!!

とか言いながら、くだんのやつを使ってるのを探してみた。

use tokio_stream::StreamExt as _;

fn get_csv_path(relative_path: &str) -> std::path::PathBuf {
    let project_path = env!("CARGO_MANIFEST_DIR");
    std::path::Path::new(project_path)
        .parent()
        .unwrap()
        .join(relative_path)
}

Crate tokiostream 有名なやつなのか。知らないともぐりなのか。 こういうURLから連鎖で、Rust by Example こういう所に飛べるって、勢いが有るな。

Pythonプログラミング入門 (by 東京大学) なんてのもある。ブランドで対抗しますって訳だな。

c++

C++ Examples

C++ Standard Library

C++ reference

江添亮のC++入門

江添亮の詳説C++17

著者が無料で公開して下さっているので、手元に置いておきたい。

sakae@pen:/tmp$ w3m -dump_head https://ezoeryou.github.io/cpp17book/
HTTP/1.1 200 OK
Connection: close
Content-Length: 106433
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
 :

URLのヘッドを確認すると、gzipで圧縮されている。ならば下記のようにして、お取り寄せと開梱を行う。

sakae@pen:/tmp$ w3m -dump_source https://ezoeryou.github.io/cpp17book/ >z
sakae@pen:/tmp$ file z
z: gzip compressed data, from Unix, original size modulo 2^32 639660
sakae@pen:/tmp$ gzip -dc z >c17.html
sakae@pen:/tmp$ w3m c17.html

これを家宝にしておこう。c++11以降はモダンC++って扱いらしいからね。

devel with c++

入門本に掲載されてたMakefileをBSDMakeでも動くように、ちと改変。

gcc_options = -std=c++17 -Wall --pedantic-errors -g

run : program
        ./a.out

program : main.cpp all.h all.h.gch
        c++ $(gcc_options) -include all.h main.cpp -o a.out

all.h.gch : all.h
        c++ $(gcc_options) -x c++-header -o all.h.gch all.h

clean :
        rm -f ./a.out
        rm -f ./all.h.gch

.PHONY : run clean

こうしておけば、emacsでソースを編集して、直にコンパイルと実行が出来る。変な何とか-modeってやつを入れなくても、動く。もう、rubyとかを編集して実行って感覚で使える。有り難い事です。

read book

少し入門辺を読んでみた。アルゴリズムってのが面白そう。

int main()
{
    std::vector<int> v = {1,2,3,4,5} ;

    std::for_each( std::begin(v), std::end(v),
        []( auto value ) { std::cout << value ; } ) ;
}

ヴェクターの内容を出力する奴。begin,endは、ヴェクターの範囲を示す。次の行は、無名関数を定義してる。[]がLAMBDAに相当するのかな。次の括弧は、仮引数、最後のブラケット内は、関数本体。

なんだ、昔のschemeなら、とっくに実現してたのが、やっとこさ実現したって事か。でも、最初と最後を指定しないといけないなんて、不恰好だ。c++20 の時代になって、やっとこの不恰好さが取れて、この場合なら、v だけを指定すればよくなるらしい。

(for-each display '("a" "b" "c"))
(for-each cons '(1 2 3) '(4 5 6))

引数の順番がCフラフラとは逆になってるけど、やらせる事は一緒。二番目の例なんて、Cフラフラ語では、スイスイと書けるのか? きっとフラフラするだろうね。 何十年も遅れて、やって来た言語、だな。

例は例な奴 (csv read by c++)

新しい言語を習得する時、オイラーはやる事が決っている。そう、CSVで保存してる血圧・脈拍のデータをハンドリングする事だ。今迄、これをお題にpython,haskell,scheme,golang等でやってみたんだ。

んで、難関はCSVを読み込む所。そんなのCSVのモジュールになってるでしょ。探してみても容易に見付からなかった。でも、こんな例が有ったので、肴にしてみる。Cフラフラには、splitも無いんかい。遅れているな。で、その代わりにgetlineなんて言う思いもしない奴を使うようだ。 所変れば、作法も違うのね。splitぐらいは、自作するのが流儀とな。

#include <fstream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

vector<string> split(string& input, char delimiter) {
    istringstream stream(input);
    string field;
    vector<string> result;
    while (getline(stream, field, delimiter)) {
        result.push_back(field);
    }
    return result;
}

int main() {
    ifstream ifs("current.csv");
    string line;
    while (getline(ifs, line)) {
        vector<string> strvec = split(line, ',');
        for (int i=0; i<strvec.size();i++){
            printf("%5d\n", stoi(strvec.at(i)));
        }
    }
}

早速、上で出て来たmakeのお世話になってみる。

sakae@pen:/tmp/cpp$ make
g++ -std=c++17 -Wall --pedantic-errors -g -x c++-header -o all.h.gch all.h
g++ -std=c++17 -Wall --pedantic-errors -g -include all.h main.cpp -o a.out
main.cpp: In function ‘int main()’:
main.cpp:23:24: warning: comparison of integer expressions of different signedne
ss: ‘int’ and ‘std::vector<std::__cxx11::basic_string<char> >::size_type’ {aka ‘
long unsigned int’} [-Wsign-compare]
   23 |         for (int i=0; i<strvec.size();i++){
      |                       ~^~~~~~~~~~~~~~
./a.out
21120505
  142
   76
   47
21120521
  127
   :

警告を出しつつ、取り敢えず動いてくれた。でも、気持悪いな。こいつの修整の仕方は、知ってるぞ(勉強したぞ)。

for (size_t i=0; i < strvec.size(); i++){

インデックス番号に負数は有り得ません。何処かのrubyみたいに、負数は配列の尻尾からの番号になりますって言う、気の効いた機能なんて無いんです。

rubyのそれは、配列はリング状になってる。だから、先頭を表すインデックス番号0の前は、ー1。それは、配列の尻尾って考えに及ばないのさ。なんたって、Cフラフラ語は、国際機関が制定するISO規格の言語だから、柔軟性が欠如してるのさ。

intは負数も許す型。それは不味いって事で。正数のみを許す整数にしとけ。そう、その為の型が、 size_t なのさ。

getline

上の肴を良くみると、getlineが2箇所に出て来る。一つはsplitの中の、getline(stream, field, delimiter)。もう一つは、mainの中のgetline(ifs, line)。違いは何処ですか? って、幼稚園児向けの問題だな。引数が2つの場合と3つの場合が有ります。そういうあんたは、幼稚園児並み。侮ってはいけないんだろうね。

どう解剖する? そりゃ、中を追跡するに限る。となると、debuggerの出番だな。

(gdb) b getline
Breakpoint 1 at 0x7ffff7c5ddf0: getline. (7 locations)
(gdb) r
 :
   51
[Inferior 1 (process 2592) exited normally]

ちゃんとBPがセットされたはずなんだけど、全くヒットしない。一応どんな所にセットされたか、確認しとく。

(gdb) info break
 in __getline at getline.c:28
 <std::basic_istream<wchar_t, std::char_traits<wchar_t> >::getline(wchar_t*, long, wchar_t)@plt>
 <std::istream::getline(char*, long, char)@plt>
 <std::istream::getline(char*, long, char)>
 <std::basic_istream<wchar_t, std::char_traits<wchar_t> >::getline(wchar_t*, long, wchar_t)>
 <std::istream::getline(char*, long)>
 <std::basic_istream<wchar_t, std::char_traits<wchar_t> >::getline(wchar_t*, long)>

非常に長い行のデータが出て来るんで、emacsの矩形選択削除を使った。始点を C-x spaceで選択。終点に移動して、C-x r d する。こうして、前半を削除。後半のWhat部分だけを残した。

debianのgdbって、鉄板な奴だと思っていたけど、どうも阿呆っぽい所があるな。そんじゃ、今迄嫌われていたOpenBSDのlldbで試してみる。

at lldb

64Bitな版なんで、アドレス表示が長いのは勘弁。

ob$ lldb a.out
(lldb) target create "a.out"
Current executable set to '/tmp/cpp/a.out' (x86_64).
(lldb) b getline
Breakpoint 1: 2 locations.
(lldb) break list
Current breakpoints:
1: name = 'getline', locations = 2
  1.1: where = a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >
& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char>
 >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic
_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 29 at
 istream:1564:20, address = a.out[0x0000000000007a8d], unresolved, hit count = 0

  1.2: where = a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >
& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char>
 >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic
_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, char) +
 32 at istream:1510:23, address = a.out[0x0000000000007530], unresolved, hit cou
nt = 0

ごちゃごちゃしてるけど、よく見ると、被試験プログラムのgetlineを対象に選んでくれている。走らせてみる。

(lldb) r
Process 13691 launched: '/tmp/cpp/a.out' (x86_64)
4 locations added to breakpoint 1
1 location added to breakpoint 1
Process 13691 stopped
 * thread #1, stop reason = breakpoint 1.1
    frame #0: 0x00000e85631f5a8d a.out`std::__1::basic_istream<char, std::__1::c
har_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__
1::allocator<char> >(__is=0x00007f7ffffc43e0, __str="") at istream:1564:20
   1561 getline(basic_istream<_CharT, _Traits>& __is,
   1562         basic_string<_CharT, _Traits, _Allocator>& __str)
   1563 {
-> 1564     return getline(__is, __str, __is.widen('\n'));
                           ^
   1565 }
   1566
   1567 #ifndef _LIBCPP_CXX03_LANG
(lldb) bt
 * thread #1, stop reason = breakpoint 1.1
  * frame #0: 0x00000e85631f5a8d a.out`std::__1::basic_istream<char, std::__1::c
har_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__
1::allocator<char> >(__is=0x00007f7ffffc43e0, __str="") at istream:1564:20
    frame #1: 0x00000e85631f5186 a.out`main at main.cpp:21:12
    frame #2: 0x00000e85631f4d98 a.out`__start + 312
(lldb) up
frame #1: 0x00000e85631f5186 a.out`main at main.cpp:21:12
   18   int main() {
   19       ifstream ifs("current.csv");
   20       string line;
-> 21       while (getline(ifs, line)) {
                   ^
   22           vector<string> strvec = split(line, ',');
   23           for (int i=0; i<strvec.size();i++){
   24               printf("%5d\n", stoi(strvec.at(i)));

mainの中でヒットしたな。getlineのソースは、istreamの中にある。これって、/usr/include/c++/xx/istreamだな。

(lldb) c
Process 13691 resuming
Process 13691 stopped
 * thread #1, stop reason = breakpoint 1.2
    frame #0: 0x00000e85631f5530 a.out`std::__1::basic_istream<char, std::__1::c
har_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__
1::allocator<char> >(__is=0x00007f7ffffc4230, __str="", __dlm=',') at istream:15
10:23
   1507 getline(basic_istream<_CharT, _Traits>& __is,
   1508         basic_string<_CharT, _Traits, _Allocator>& __str, _CharT __dlm)
   1509 {
-> 1510     ios_base::iostate __state = ios_base::goodbit;
                              ^
   1511     typename basic_istream<_CharT, _Traits>::sentry __sen(__is, true);
   1512     if (__sen)
   1513     {
(lldb) up
frame #1: 0x00000e85631f5025 a.out`split(input="21120505,142,76,47", delimiter='
,') at main.cpp:12:12
   9        istringstream stream(input);
   10       string field;
   11       vector<string> result;
-> 12       while (getline(stream, field, delimiter)) {
                   ^
   13           result.push_back(field);
   14       }
   15       return result;

何度か継続すると、splitの中のそれが現れた。

後は丁寧に/usr/include/c++ の中にあるヘッダーファイルを見ていけばいいんだな。直接のコードがヘッダーファイル内に記述されてるってのは、Cフラフラの流儀なのかな。そもそもフラフラにフラフラと手を出したのは、wyhashが原因だったけど、あやつもヘッダー内にコードが書かれていたな。

一応32Bit環境でも、確認しておく。FreeBSDな。

[sakae@fb /tmp/cpp]$ lldb a.out
(lldb) target create "a.out"
Current executable set to '/tmp/cpp/a.out' (i386).
(lldb) b getline
Breakpoint 1: 2 locations.
(lldb) break list
Current breakpoints:
1: name = 'getline', locations = 2
  1.1: where = a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 15 at istream:1564:20, address = a.out[0x00406e9f], unresolved, hit count = 0
  1.2: where = a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, char) + 17 at istream:1510:23, address = a.out[0x004069f1], unresolved, hit count = 0

相変わらず、目がクラクラ、体がフラフラするのが、この言語の特徴です。メニエール症とかが本物だっtかな。

(lldb) r
Process 7065 launching
Process 7065 launched: '/tmp/cpp/a.out' (i386)
4 locations added to breakpoint 1
1 location added to breakpoint 1
Process 7065 stopped
 * thread #1, name = 'a.out', stop reason = breakpoint 1.1
    frame #0: 0x00406e9f a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(__is=0xffbfea6c, __str="") at istream:1564:20
   1561 getline(basic_istream<_CharT, _Traits>& __is,
   1562         basic_string<_CharT, _Traits, _Allocator>& __str)
   1563 {
-> 1564     return getline(__is, __str, __is.widen('\n'));
   1565 }
   1566
   1567 #ifndef _LIBCPP_CXX03_LANG
(lldb) c
Process 7065 resuming
Process 7065 stopped
 * thread #1, name = 'a.out', stop reason = breakpoint 1.2
    frame #0: 0x004069f1 a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(__is=0xffbfea6c, __str="", __dlm='\n') at istream:1510:23
   1507 getline(basic_istream<_CharT, _Traits>& __is,
   1508         basic_string<_CharT, _Traits, _Allocator>& __str, _CharT __dlm)
   1509 {
-> 1510     ios_base::iostate __state = ios_base::goodbit;
   1511     typename basic_istream<_CharT, _Traits>::sentry __sen(__is, true);
   1512     if (__sen)
   1513     {
(lldb) bt
 * thread #1, name = 'a.out', stop reason = breakpoint 1.2
  * frame #0: 0x004069f1 a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(__is=0xffbfea6c, __str="", __dlm='\n') at istream:1510:23
    frame #1: 0x00406ee4 a.out`std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(__is=0xffbfea6c, __str="") at istream:1564:12
    frame #2: 0x004066d6 a.out`main at main.cpp:24:12
    frame #3: 0x00406326 a.out`_start1(cleanup=0x2041eb80, argc=1, argv=0xffbfec68) at crt1_c.c:72:7
    frame #4: 0x00406480 a.out`_start at crt1_s.S:46

mainが終了したら直にexitするように、仕込まれているのね。

(lldb) up
frame #3: 0x00406326 a.out`_start1(cleanup=0x2041eb80, argc=1, argv=0xffbfec68) at crt1_c.c:72:7
   69   #endif
   70
   71           handle_static_init(argc, argv, env);
-> 72           exit(main(argc, argv, env));
   73   }
   74
   75   __asm(".hidden  _start1");

This year's Index

Home