Jekyll2020-02-22T13:55:03+09:00https://ifritjp.github.io/blog/site/feed.xmlhoge blogQiita に投稿する程でないネタを上げるネタサイト。 ポエムや、メモ、自分用の備忘録、Web から集めただけの情報などなど。raspberry pi の USB MASS STORAGE 自動マウントを無効化する2020-02-21T00:00:00+09:002020-02-21T00:00:00+09:00https://ifritjp.github.io/blog/site/2020/02/21/raspberrypi-mount<p>raspberry pi に SSD を接続して簡易 NAS にしている。
この簡易 NAS では、
SSD を取り外ししやすいように autofs によるマウントを設定した。
しかし、SSD を接続すると PCManFM の自動マウントが動いて
autofs が正常にマウントできない現象が発生した。</p>
<p>そこで PCManFM の自動マウントを無効化した。</p>
<h1>PCManFM の自動マウントを無効化</h1>
<p>~/.config/pcmanfm/LXDE-pi/pcmanfm.conf の以下の設定を変更する。</p>
<pre class="example">
mount_on_startup=0
mount_removable=0
</pre>
<p>これで、PCManFM の自動マウントを無効化できる。</p>ifritJPemacs lisp の quote2020-02-18T00:00:00+09:002020-02-18T00:00:00+09:00https://ifritjp.github.io/blog/site/2020/02/18/emacs-quoted-list<p>emacs lisp の quote でハマったのでネタに書いておく。</p>
<pre class="src" lang="lisp">
(defvar hoge-val nil)
(defun hoge-init ()
(setq hoge-val '(:val nil))
)
(defun hoge-set ()
(plist-put hoge-val :val "1"))
</pre>
<p>上記のように変数 hoge-val に対して plist-put で処理する関数を定義して、
次のようにコールすると。</p>
<pre class="src" lang="lisp">
(let (val1 val2 val3)
(hoge-init)
(setq val1 (plist-get hoge-val :val))
(hoge-set)
(setq val2 (plist-get hoge-val :val))
(hoge-init)
(setq val3 (plist-get hoge-val :val))
(message (format "%s %s %s" val1 val2 val3)))
</pre>
<p>最後の (message (format “%s %s %s” val1 val2 val3)) で “nil 1 1” が出力される。</p>
<p>てっきり、 “nil 1 nil” が出力されるものだと思っていた。
なぜなら、val3 をセットする直前に hoge-init を実行しており、
この hoge-init は hoge-val を ‘(:val nil) で初期化する関数なので、
(plist-get hoge-val :val) は nil を返すと考えたからだ。</p>
<p>しかし実際には、最後の (plist-get hoge-val :val) は “1” になる。</p>
<p>なぜこのような結果になるかと言うと、
‘() は定数として扱い、
関数 hoge-init を実行する際には新しくリストを生成せず、
defun を評価した時の値そのものが使い続けられる。</p>
<p>そして (plist-put) でリストの中身を操作した場合、その定数自体が書き変わり、
hoge-init 関数は変数に書き変わった定数を代入しているため初期化できない。</p>
<p>一方で、 hoge-init の処理に list 関数を使うと、”nil 1 nil” となる。</p>
<pre class="src" lang="lisp">
(defun hoge-init ()
(setq hoge-val (list :val nil))
)
</pre>
<p>(list) は評価されるたび新規にリストを生成しているため、変数を初期化出来る。</p>
<p>よく考えてみると納得できるけど、
実際の動きと見た目のギャップにどうにもこうにも意味不明だった。</p>
<p>これまで一度も意識せずにきたのが不思議なくらい、かなり基本的な内容だと思う。</p>
<p>quote した値の変更は、要注意ってことで。</p>ifritJPorg-mode 9.3.5 で babel(dot/plantuml) が動かなかった2020-02-13T00:00:00+09:002020-02-13T00:00:00+09:00https://ifritjp.github.io/blog/site/2020/02/13/emacs-org-9.3.5<p>emacs の org-mode では、
.org ファイル内に C や python 等ソースコードを書いて、
export 時にそのソースコードを色付けした状態で載せることができる。</p>
<p>この機能を babel と言う。</p>
<p>babel では、ソースコードの色付けだけでなく、
dot や plantuml 等のグラフ生成言語を利用することで、
.org ファイル内に書いたグラフ生成言語からグラフを生成して、
所定位置にグラフを挿入することもできる。</p>
<p>今回、 org-mode 9.3.5 の babel を使って dot の画像を出力しようとしたところ、
エラーしたので原因を追ってみた。</p>
<h1>エラー箇所</h1>
<p>エラーは次の関数で発生していた。</p>
<pre class="src" lang="lisp">
(defun org-babel-chomp (string &optional regexp)
"Strip a trailing space or carriage return from STRING.
The default regexp used is \"[ \\f\\t\\n\\r\\v]\" but another one
can be specified as the REGEXP argument."
(let ((regexp (or regexp "[ \f\t\n\r\v]")))
(while (and (> (length string) 0)
(string-match regexp (substring string -1)))
(setq string (substring string 0 -1)))
string))
</pre>
<p>エラーの内容は次のものだった。</p>
<pre class="src" lang="txt">
Debugger entered--Lisp error: (wrong-type-argument stringp nil)
string-match(nil "c")
(and (> (length string) 0) (string-match regexp (substring string -1)))
(while (and (> (length string) 0) (string-match regexp (substring string -1))) (setq string (substring string 0 -1)))
(let ((regexp (or regexp "[ \f\011\n\015\013]"))) (while (and (> (length string) 0) (string-match regexp (substring string -1))) (setq string (substring string 0 -1))) string)
</pre>
<p>このエラーは、
上記の org-babel-chomp 関数の regexp 引数が nil だった場合に発生する。</p>
<h1>エラーの修正</h1>
<p>このエラーに対し、
次のように let で宣言する変数を別名(regexp-work)で定義することで回避した。</p>
<pre class="src" lang="lisp">
(defun org-babel-chomp (string &optional regexp)
"Strip a trailing space or carriage return from STRING.
The default regexp used is \"[ \\f\\t\\n\\r\\v]\" but another one
can be specified as the REGEXP argument."
(let ((regexp-work (or regexp "[ \f\t\n\r\v]")))
(while (and (> (length string) 0)
(string-match regexp-work (substring string -1)))
(setq string (substring string 0 -1)))
string))
</pre>
<h1>エラーの原因</h1>
<p>エラーの原因を確認するため、
エラーを再現する処理を抜き出して書き換えると次になる。</p>
<pre class="src" lang="lisp">
;;; -*- lexical-binding: t; -*-
(defun hoge (regexp)
(let ((regexp (or regexp "a")))
(string-match regexp "b")))
</pre>
<p>上記の hoge 関数の引数 regexp に nil をセットしてコールすると同じエラーになる。
なお、この現象は lexical-binding を有効にしている時だけ発生する。</p>
<p>上記関数の処理を説明すると次のようになる。</p>
<ul>
<li>let で新しく変数 regexp を宣言する
<ul>
<li>このとき、引数 regexp が nil 以外なら、引数 regexp の値を変数 regexp にセットする</li>
<li>引数 regexp が nil なら、 “a” を変数 regexp にセットする。</li>
</ul>
</li>
</ul>
<p>つまり、let で宣言している変数 regexp には必ず nil 以外がセットされるはずである。</p>
<p>しかし、実際には string-match に渡される regexp には nil がセットされている。</p>
<p>何故このような結果になるか原因を想像すると、</p>
<p>「string-match でアクセスするシンボル regexp は、
let で宣言している regexp ではなく、関数の引数 regexp が参照されるため」</p>
<p>と考えるのが妥当だろう。</p>
<p>string-match は let のスコープなので、
普通に考えれば string-match の regexp は let で宣言している変数 regexp であるはず。
しかし、実際には何故か関数の引数 regexp になっている。</p>
<p>これが emacs lisp の仕様なのか、はたまた仕様外の動作なのかは良く分からない。</p>
<p>ちなみに、これが発生している環境は emacs 26.2 だが、
他の環境で発生するかどうかは確認していない。</p>
<p>org-mode の履歴を追ってみたが、
この関数の処理は lexical-binding を使うようになる前から変っていないので、
lexical-binding にした事による影響だろう。</p>
<p>以上。</p>ifritJPemacs 用 reviewboard モードの宣伝2020-02-03T00:00:00+09:002020-02-03T00:00:00+09:00https://ifritjp.github.io/blog/site/2020/02/03/emacs-reviewboard<p>この記事は、emacs 用 reviewboard モードの宣伝である。</p>
<p><a href="https://github.com/ifritJP/emacs-reviewboard-front">https://github.com/ifritJP/emacs-reviewboard-front</a></p>
<p>reviewboard は、ソースコードレビューを Web 上で行ない記録するためのツール。</p>
<p>今は github の Pull-Request に代表されるように
Web 上のソースレビューが普及しているが、
reviewboard の初版が 2007 年であることを考えると、
当時は先進的なツールだったと思う。</p>
<p>そんな reviewboard を emacs で操作するモードを今になって作ったので、
どれ程の人が使うかは不明だが、折角なので宣伝しておく。</p>
<h1>機能</h1>
<p>このモードでは、次の機能を提供する。</p>
<ul>
<li>修正ファイル一覧から必要なファイルを選択して review request (以降 rrq と記す)を登録
<ul>
<li>rrq の summary/description/testing_done を編集</li>
<li>修正ファイルの追加、削除可能</li>
</ul>
</li>
<li>レビューを受けて更新したファイル郡を、一発でアップロード</li>
<li>レビューコメントのリプライ登録</li>
<li>rrq の publish/close/discard</li>
<li>rrq に登録したファイルをコミット</li>
</ul>
<h2>設定</h2>
<h3>環境</h3>
<ul>
<li>curl, rbt, svn を事前にインストールしておく
<ul>
<li>rbt は、 diff の登録に利用する。</li>
<li>curl は、 reviewboard の WebAPI へのアクセスに利用する。</li>
<li>環境によっては、 proxy 等の環境変数設定が必要な場合がある。</li>
</ul>
</li>
<li>上記 github から emacs-reviewboard-front を取得し適当な場所に展開する</li>
</ul>
<h3>emacs-lisp</h3>
<ul>
<li>emacs-reviewboard-front のパスに load-path に追加する。</li>
<li>次の設定を行なう。</li>
</ul>
<pre class="src" lang="el">
(require 'rbfront-mode)
(setq rb/front-rb-api-token "TOKEN")
(setq rb/front-rb-url "http://reviewboard.host/path")
(setq rb/front-rbt "rbt")
(setq rb/front-proxy "http://proxy.host:8080/")
(setq rb/front-rb-repository "RESPOSITORY_NAME")
</pre>
<ul>
<li>rb/front-rb-api-token は、
reviewboard のアカウント管理ページで生成した API Tokens を指定する。</li>
<li>rb/front-rb-url は、
reviewboard のサーバの URL を指定する。</li>
<li>rb/front-proxy は、
reviewboard のサーバにアクセスする際に使用する proxy を指定する。</li>
<li>front-rb-repository は、
reviewboard に diff を登録する際の repository 名を指定する。</li>
</ul>
<h2>新規登録</h2>
<p>emacs-reviewboard-front では、現状 svnp.el を使用することを前提としている。</p>
<p>ここでは、svnp.el の細かい使用方法については説明しない。
rrq の新規登録に必要な最低限の操作について説明する。</p>
<ul>
<li>M-x svn-status で修正ファイル一覧を表示し、
commit する要領でファイルを選択する。</li>
<li>j キー押下で、rrq 編集バッファが表示される。</li>
</ul>
<h2>編集バッファ</h2>
<p><img src="/blog/site/assets/rb-new.png" alt="/blog/site/assets/rb-new.png" /></p>
<ul>
<li>title と description、 test を編集する。</li>
<li>編集後、 C-c C-c 押下により submit 処理で reviewboard に登録する。
<ul>
<li>新規登録の場合、 mini-buffer で reviewer を選択する。
<ul>
<li>この mini-buffer では TAB キーによる補完が可能。</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3>修正ファイルの追加・削除</h3>
<ul>
<li>rrq に登録する修正ファイルを追加したい場合、 C-c C-a を押下する。
<ul>
<li>mini-buffer で、ファイルが存在するディレクトリを指定し、
その後表示されるファイル一覧から上記のようにファイルを選択する。</li>
<li>選択後、 j キー押下で、ファイルが追加される。</li>
</ul>
</li>
<li>rrq に登録する修正ファイルを除外する場合、
除外するファイルにカーソルを移動して C-c C-SPC を押下する。
<ul>
<li>除外を reviewboard に反映するには、 C-c C-u を押下する。</li>
</ul>
</li>
</ul>
<h3>review コメント</h3>
<ul>
<li>review コメントの表示はサーバアクセスが多くなるため、
デフォルトでは非表示にしている。</li>
<li>表示する場合、 C-c C-d する。</li>
<li>デフォルトで表示にする場合、 rb/front-display-comment-p に nil 以外を設定する。</li>
<li>review コメントに対するリプライを登録する場合、
コメントにカーソルを合わせて C-c C-r。</li>
</ul>
<h3>submit モード</h3>
<p>submit 時の動作を、次のどちらかに変更できる。</p>
<ul>
<li>submit と同時に publish する</li>
<li>submit だけする</li>
</ul>
<p>C-c C-t でモードを切り替える。</p>
<p>デフォルトは publish する。</p>
<p>デフォルトを submit だけに切り替える場合、
rb/front-submit-and-publish-p に nil を設定する。</p>
<h2>rrq リスト表示</h2>
<p>M-x rb/front-list で、
自分が登録した rrq 一覧を表示する。</p>
<p><img src="/blog/site/assets/rb-list.png" alt="/blog/site/assets/rb-list.png" /></p>
<h3>リスト操作</h3>
<dl>
<dt>(g)</dt><dd>リストを更新する</dd>
<dt>(RET)</dt><dd>カーソル位置の rrq を編集する</dd>
<dt>(u)</dt><dd>カーソル位置の rrq の diff を、再アップロードする</dd>
<dt>(p)</dt><dd>カーソル位置の rrq を publish する。</dd>
<dt>(c)</dt><dd>カーソル位置の rrq を close する。</dd>
<dt>(d)</dt><dd>カーソル位置の rrq を discard する。</dd>
<dt>(C)</dt><dd>カーソル位置の rrq に登録したファイルを commit する。</dd>
</dl>
<h3>diff の再アップロード</h3>
<p>再アップロードを行なうため、ローカルの work ディレクトリを指定する必要がある。
work ディレクトリの指定は mini-buffer で行なう。</p>
<h2>注意</h2>
<ul>
<li>rrq 編集バッファで C-c C-c を実行すると、
バッファ内容がサーバに登録され、即時 publish する。</li>
<li>rrq 編集バッファの C-c C-a による修正ファイル追加は、
新規 rrq の場合を除き即時 publish する。
新規 rrq の場合、submit 時に rrq 情報と一緒に更新ファイル情報が登録される。</li>
</ul>ifritJPC 言語のラッパー関数オーバーヘッド2019-10-15T00:00:00+09:002019-10-15T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/10/15/wrapper-overhead<p>プログラムを組む際、ラッパー関数を作ることは良くある。</p>
<p>このラッパー関数のオーバーヘッドが気になったので簡単に調べてみた。</p>
<p>計測用サンプルは次の通り。</p>
<pre class="src" lang="c">
#include<stdio.h>
typedef void (func_t)( int val1, int val2 );
void func( int val1, int val2 )
{
printf( "%d %d", val1, val2 );
}
void wrapper0( int val1, int val2 )
{
func( val1, val2 );
}
void wrapper1( func_t * pFunc, int val1, int val2 )
{
pFunc( val1, val2 );
}
void wrapper2( int val1, int val2, func_t * pFunc )
{
pFunc( val1, val2 );
}
main() {
wrapper0( 0, 1 );
wrapper1( func, 0, 1 );
wrapper2( 0, 1, func );
}
</pre>
<p>関数 func() をコールする 3 種類のラッパー関数 wrapper0, wrapper1, wrapper2 を用意した。</p>
<p>それぞれのラッパー関数は次の形になっている。</p>
<table>
<tr><th>ラッパー</th><th>引数</th></tr>
<tr><td>wrapper0</td><td>呼び出し先と同じ引数</td></tr>
<tr><td>wrapper1</td><td>ラッパー独自引数の後に呼び出し先と同じ引数</td></tr>
<tr><td>wrapper2</td><td>呼び出し先と同じ引数の後にラッパー独自引数</td></tr>
</table>
<p>これを gcc の x64 で -O の最適化した結果が次になる。
(func の処理は省略)</p>
<pre class="src" lang="asm">
0000000000000021 <wrapper0>:
21: 48 83 ec 08 sub $0x8,%rsp
25: e8 00 00 00 00 callq 2a <wrapper0+0x9>
2a: 48 83 c4 08 add $0x8,%rsp
2e: c3 retq
000000000000002f <wrapper1>:
2f: 48 83 ec 08 sub $0x8,%rsp
33: 48 89 f8 mov %rdi,%rax
36: 89 f7 mov %esi,%edi
38: 89 d6 mov %edx,%esi
3a: ff d0 callq *%rax
3c: 48 83 c4 08 add $0x8,%rsp
40: c3 retq
0000000000000041 <wrapper2>:
41: 48 83 ec 08 sub $0x8,%rsp
45: ff d2 callq *%rdx
47: 48 83 c4 08 add $0x8,%rsp
4b: c3 retq
000000000000004c <main>:
4c: 48 83 ec 08 sub $0x8,%rsp
50: be 01 00 00 00 mov $0x1,%esi
55: bf 00 00 00 00 mov $0x0,%edi
5a: e8 00 00 00 00 callq 5f <main+0x13>
5f: ba 01 00 00 00 mov $0x1,%edx
64: be 00 00 00 00 mov $0x0,%esi
69: bf 00 00 00 00 mov $0x0,%edi
6e: e8 00 00 00 00 callq 73 <main+0x27>
73: ba 00 00 00 00 mov $0x0,%edx
78: be 01 00 00 00 mov $0x1,%esi
7d: bf 00 00 00 00 mov $0x0,%edi
82: e8 00 00 00 00 callq 87 <main+0x3b>
87: b8 00 00 00 00 mov $0x0,%eax
8c: 48 83 c4 08 add $0x8,%rsp
90: c3 retq
</pre>
<p>上記通り wrapper0 と wrapper2 は、ほぼ同じコードになっており、
wrapper1 は引数をずらす処理が余分に入っている。</p>
<p>想像通りの結果といえば想像通りだが、
ちゃんと最適化された処理になっている。</p>
<p>以上のことから言えることは、
ラッパー関数独自の引数は、先頭ではなく末尾にもっていった方が良いということだ。</p>
<p>ただし、ここまで最適化が効くケースは、
ラッパー関数内での目的の関数コールが先頭にある場合に限られるので、
目的の関数コールを先頭に持ってこれない場合は、気にしないで良いだろう。</p>
<p>なお、 -O2 で最適化をかけると wrapper1, wrapper2 は次の処理に最適化された。</p>
<pre class="src" lang="asm">
0000000000000030 <wrapper1>:
30: 48 89 f8 mov %rdi,%rax
33: 89 f7 mov %esi,%edi
35: 89 d6 mov %edx,%esi
37: ff e0 jmpq *%rax
39: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000000040 <wrapper2>:
40: ff e2 jmpq *%rdx
</pre>
<p>個人的には、こっちの方が納得がいく。</p>
<p>また、次のようにラッパー関数に static 宣言を付加して、
外部からコールされないことを明示すると、</p>
<pre class="src" lang="c">
#include<stdio.h>
typedef void (func_t)( int val1, int val2 );
void func( int val1, int val2 )
{
printf( "%d %d", val1, val2 );
}
static void wrapper0( int val1, int val2 )
{
func( val1, val2 );
}
static void wrapper1( func_t * pFunc, int val1, int val2 )
{
pFunc( val1, val2 );
}
static void wrapper2( int val1, int val2, func_t * pFunc )
{
pFunc( val1, val2 );
}
main() {
wrapper0( 0, 1 );
wrapper1( func, 0, 1 );
wrapper2( 0, 1, func );
}
</pre>
<p>出力結果は次のように、 ラッパーがインライン展開され、
ラッパーの引数の違いによる差分は無くなった。</p>
<pre class="src" lang="asm">
0000000000000021 <main>:
21: 48 83 ec 08 sub $0x8,%rsp
25: be 01 00 00 00 mov $0x1,%esi
2a: bf 00 00 00 00 mov $0x0,%edi
2f: e8 00 00 00 00 callq 34 <main+0x13>
34: be 01 00 00 00 mov $0x1,%esi
39: bf 00 00 00 00 mov $0x0,%edi
3e: e8 00 00 00 00 callq 43 <main+0x22>
43: be 01 00 00 00 mov $0x1,%esi
48: bf 00 00 00 00 mov $0x0,%edi
4d: e8 00 00 00 00 callq 52 <main+0x31>
52: b8 00 00 00 00 mov $0x0,%eax
57: 48 83 c4 08 add $0x8,%rsp
5b: c3 retq
</pre>
<p>基本的に、ソースコードはメンテナンス性や可読性を優先すべきだが、
ソースコードを自動生成するような場合は、
このような細かいことも意識しておいた方が良いだろう。</p>
<p>以上。</p>ifritJPC 言語の可変長引数 (va_list) 処理のオーバーヘッド2019-08-06T00:00:00+09:002019-08-06T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/08/06/va-performance<p>以前 C 言語の関数ポインタによる関数コールのオーバーヘッドがどの程度なのか調べたが、
今回は可変長引数(va_list)処理のオーバーヘッドについて調べてみた。</p>
<h1>結果</h1>
<p>初めに結果から書くと、</p>
<pre class="example">
可変長引数(va_list)処理のオーバーヘッドは、めちゃめちゃ掛る。
また、引数の数に応じて時間が増加する。
</pre>
<h1>所感</h1>
<p>今回の実験によって、 va_list 処理には当初の想定を遥かに越えたオーバーヘッドが
かかることが分った。</p>
<p>個人的には、コンパイラがもっと賢くやってくれているものだと思っていたが、
実際には全く賢くなかった。</p>
<p>C 言語で可変長引数を積極的に使用することはあまりないとは思うが、
可変長引数の使用はオーバーヘッドを十分考慮に入れて慎重に検討するべきだということが判った。</p>
<p>この可変長引数のオーバーヘッドを調べたのは、
LuneScript のメソッド呼び出し処理を C 言語にトランスコンパイルした際に
可変長引数を利用しようと思ったからなのだが、
この結果から可変長引数は使えないことが分った。</p>
<p>対応する前に結果が分って良かったが、
可変長引数が使えなくなったのは当初の目論見が崩れてしまった。</p>
<h1>実験詳細</h1>
<p>ここでは、今回の実験方法について説明する。</p>
<h2>コード</h2>
<p>実験用に次の C 言語コードを作成した。</p>
<pre class="src" lang="c">
int func( int val1, int val2 ) {
return val1 + val2;
}
int sub( int dummy, int val1, int val2 ) {
return func( val1, val2 );
}
int funcv2( va_list ap ) {
int val1 = va_arg( ap, int );
int val2 = va_arg( ap, int );
return val1 + val2;
}
int subv2( int dummy, ... ) {
int val;
va_list ap;
va_start( ap, dummy );
val = funcv2( ap );
va_end( ap );
return val;
}
</pre>
<p>func, sub は、可変長引数を使用しないパターン。
funcv2, subv2 は、可変長引数を使用しするパターン。</p>
<p>ちなみにコードの全体は次の通りである。</p>
<pre class="src" lang="c">
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
int func( int val1, int val2 ) {
return val1 + val2;
}
int sub( int dummy, int val1, int val2 ) {
return func( val1, val2 );
}
int funcv2( va_list ap ) {
int val1 = va_arg( ap, int );
int val2 = va_arg( ap, int );
return val1 + val2;
}
int subv2( int dummy, ... ) {
int val;
va_list ap;
va_start( ap, dummy );
val = funcv2( ap );
va_end( ap );
return val;
}
int funcv3( va_list ap ) {
int val1 = va_arg( ap, int );
int val2 = va_arg( ap, int );
int val3 = va_arg( ap, int );
return val1 + val2 + val3;
}
int subv3( int dummy, ... ) {
int val;
va_list ap;
va_start( ap, dummy );
val = funcv3( ap );
va_end( ap );
return val;
}
double getTime( void ) {
struct timeval tm;
gettimeofday( &tm, NULL );
return tm.tv_sec + tm.tv_usec / 1000000.0;
}
main( int argc, const char * argv[] ) {
long long loop = strtoll( argv[ 1 ], NULL, 10 ) * 1000ll;
long long count = 0;
int sum = 0;
double prev = getTime();
if ( strcmp( argv[ 2 ], "1" ) == 0 ) {
for ( count = 0; count < loop; count++ ) {
sum += sub( 0, 1, 2 );
}
}
else if ( strcmp( argv[ 2 ], "2" ) == 0 ) {
for ( count = 0; count < loop; count++ ) {
sum += subv2( 0, 1, 2 );
}
}
else {
for ( count = 0; count < loop; count++ ) {
sum += subv3( 0, 1, 2, 3 );
}
}
printf( "%s: %lld time = %g, %d\n", argv[ 2 ], loop, getTime() - prev, sum );
}
</pre>
<p>このプログラムは、コマンドラインの引数によって
sub, subv2, subv3 を指定の回数分実行し、実行時間を表示する。</p>
<h2>計測結果</h2>
<table>
<tr><th></th><th>時間(秒)</th></tr>
<tr><td>固定長引数(sub: 2 引数)</td><td>0.62</td></tr>
<tr><td>可変長引数(subv2: 2 引数)</td><td>11.95</td></tr>
<tr><td>可変長引数(subv3: 3 引数)</td><td>16.16</td></tr>
</table>
<p>上記結果を見ると分かる通り、可変長引数は処理時間の桁が違う。</p>
<p>また、引数の数に応じて時間が増加する。</p>
<p>以上</p>ifritJP如何なる開発手法、プログラム言語を用いても、日本の全てのソフトウェアプロジェクトは必ず技術的負債になる2019-08-02T00:00:00+09:002019-08-02T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/08/02/engineering<p>「日本の全てのソフトウェアプロジェクトは必ず技術的負債になる」というタイトルですが、
次の条件を満す場合に限ります。</p>
<p><b>「プロジェクトに関わるソフトウェアエンジニアの大半が技術に無関心」</b></p>
<h1>動機</h1>
<p>このネタは、次の記事を読んで個人的に思うことがあったのをきっかけに
書いています。</p>
<ul>
<li>オブジェクト指向プログラミング – 1兆ドル規模の大失敗
<ul>
<li><a href="https://okuranagaimo.blogspot.com/2019/07/1.html">https://okuranagaimo.blogspot.com/2019/07/1.html</a></li>
</ul>
</li>
<li>大企業の技術系インターンシップに参加した
<ul>
<li><a href="https://blog.browniealice.net/post/internship2019winter/">https://blog.browniealice.net/post/internship2019winter/</a></li>
</ul>
</li>
<li>ソフト開発で世界と闘った及川卓也氏が見た、日本の弱点と可能性
<ul>
<li><a href="https://headlines.yahoo.co.jp/article?a=20190801-00010000-chuokou-bus_all">https://headlines.yahoo.co.jp/article?a=20190801-00010000-chuokou-bus_all</a></li>
</ul>
</li>
</ul>
<p>上記の記事は各自に読んでもらうとして、
それぞれの記事の内容をものすごく大雑把にまとめると</p>
<ul>
<li>「OOP はダメだから、関数型プログラミングを使え」</li>
<li>「日本を代表する大企業に実情に失望した」</li>
<li>「日本の企業はソフトウェア開発を理解していない」</li>
</ul>
<p>になると思います。</p>
<h1>プログラミング言語は道具にすぎない</h1>
<p>上記ブログで「OOP はダメだから、関数型プログラミングを使え」と書かれています。
私は、OOP が万能だなんて思ってませんし、
上記ブログで指摘されている側面があることも理解しています。</p>
<p>ですが、オブジェクト指向プログラミングにしろ関数型プログラミングにしろ、
万能ではないという意味ではどちらも同じです。</p>
<p>プログラム言語は道具です。
いかなる道具であっても、
その道具を安全に運用できるかどうかは、最終的には使う人に依存することになります。</p>
<p>例えば、古典的なプログラミング言語の代表格に C 言語がありますが、
ご存知の通り C 言語には GC もないですし、 NULL 安全でもありません。
そのような高度な「安全」機能を持たない C 言語は、Linux Kernel の開発言語です。
C 言語によって Linux Kernel を開発しているという事実は、
高度な「安全」機能が搭載されていないプログラミング言語であっても、
使用する人次第で大規模プロジェクトでも問題なく運用できるという一つの実証と言えます。</p>
<p>逆に、C 言語よりも高度な「安全」機能を搭載しているプログラミング言語を使用した
プロジェクトが技術的負債の塊になり運用困難になった、
という例はいくらでも身近にあると思います。
もし身近に無いとしても、ネットで検索すれば多数ヒットします。</p>
<p>だからと言って、 C 言語の様に使用する人への依存が高過ぎる言語と、
Rust のように先進的な安全機能搭載言語のどちらを使っても大差はない、
というつもりはありません。
私が言いたいのは、「より安全」と言われる技術を使っても、
それを使用する人への依存性が無くなることはない、ということです。</p>
<p>自動運転に例えると、
プログラム言語自体が提供する「安全」は高々レベル 3 のサポートにすぎません。</p>
<p>レベル 3 の自動運転には、ドライバーの運転技術が必須であるように、
現存するどのようなプログラム言語であっても、
ソフトウェアエンジニアの能力が欠かせません。</p>
<h1>「ソフトウェアエンジニアの大半が技術に無関心」であることの問題</h1>
<p>なぜ「プロジェクトに関わるソフトウェアエンジニアの大半が技術に無関心」だとダメなのか?</p>
<p>これは単純に、そのようなプロジェクトではどのように「安全」な環境であっても、
その「安全」がレベル 5 の自動運転のように「完全」でない限り、
「不具合をエンジニア自ら作り込んでしまう」からです。</p>
<p>前述している通り、プログラミング言語はあくまでも道具であって、
その道具を安全に運用できるかどうかは、最終的には使う人に依存することになります。
そしてプログラミング言語を使う人はソフトウェアエンジニアであり、
ソフトウェアエンジニアの能力は、多くの場合、技術への関心度に比例します。</p>
<p>特に統計を取った訳ではなく、裏付け資料がある訳でもないですが、
個人的な経験上、技術への関心度が高いソフトウェアエンジニアほど能力が高く、
技術への関心度が低いソフトウェアエンジニアほど能力が低い傾向にあります。</p>
<p>つまり、
「プロジェクトに関わるソフトウェアエンジニアの大半が技術に無関心」であるということは、
ソフトウェアエンジニアの大半の能力が低いということと、ほぼ同義になります。</p>
<p>「技術への関心度が低いソフトウェアエンジニアほど能力が低い傾向にある」という持論の
根拠となるエピソードを一つ挙げておきます。</p>
<p>あるソフトウェアエンジニアAがモジュールの設計をしていました。</p>
<p>そのモジュールは、他モジュールとの依存が高いことが問題になっていたので、
「DI(Dependency injection)の手法を取り入れたら
もっとスッキリした設計になる可能性があるので検討してみてはどうですか?」
と、そのソフトウェアエンジニアAに話をすると、
「そういう難しいことは逆に不具合につながるのでやりたくない」と
言われて一蹴されました。
DI を検討した結果、従来通りの方法を採用する方が良いという結論になったのであれば
納得できますが、なんとなく難しそうというイメージだけで拒否していました。
そして、そのモジュールは依存が高いまま実装されました。</p>
<p>DI のことを理解していれば、それが難しいと考える人はほとんどいないでしょうし、
テストがしやすいことから、むしろ不具合も低減できる可能性があり、
DI を取り入れることで不具合に繋がることを心配する人はいないでしょう。</p>
<p>このように、技術への関心度が低いと、
自分が知らない技術を積極的に取り入れるようなことをせず、
自分が使える技術だけで解決しようとします。
これによって、よりスマートに実現できる方法が他にあるにもかかわらず、
潜在的な問題を含む古い方法によってモジュールが作られていき、
それが積み重なってプロジェクト全体の品質が下っていきます。
そしてそれは時間が経過するほど、手をつけられない技術的負債になります。</p>
<p>一言で表現すれば、技術への関心度が低いエンジニアは「技術的負債製造機」です。</p>
<p>例え TEST FIRST の開発プロセスであっても、それは防げないでしょう。
ならぜなら、
テストというのは作成した成果物が仕様通りに出来ていることを確認するものであって、
仕様そのものに不具合があった場合は、その不具合を検知することは出来ないからです。
仕様を作るのはソフトウェアエンジニアです。
能力の低いソフトウェアエンジニアほど、穴の多い仕様を作る傾向にあります。</p>
<p>能力の低いソフトウェアエンジニアには仕様を作らせず、
能力の高いソフトウェアエンジニアだけで仕様を作れば良い、という考えもあると思います。</p>
<p>確かに、能力の高い人の比率が高い場合はそういう運用が可能かもしれません。
しかし、ここでは大半が能力が低いことを前提にしているので、
そのような運用は難しいです。</p>
<p>また、例え仕様に問題がなくても、
実際にコード化した時に不具合が埋め込まれることは良くあります。
そして、テストで検出されることもなくリリースされ、市場で時限爆弾のように爆発する、
お決まりのパターンです。もはや伝統芸能の域です。</p>
<h1>なぜ日本で問題なのか?</h1>
<p>ここまでの話を納得していただけたとして、次の疑問が浮ぶかもしれません。</p>
<p>「プロジェクトに関わるソフトウェアエンジニアの大半が技術に無関心」が
技術的負債を生み出す原因ならば、日本でなくても同じことが言えるのではないか?</p>
<p>それは確かにそうです。
しかし、日本の場合、終身雇用 & 転職しずらい社会環境によって、
一度雇ったソフトウェアエンジニアが技術に無関心だったとしても、
そのソフトウェアエンジニアを他の優秀なソフトウェアエンジニアに入れ替える、
ということが非常に困難なため、このような状況になり易いです。</p>
<p>さらに、日本ではソフトウェア開発をゼネコン方式で開発するという文化があり、
一つのプロジェクトを社内の優秀なソフトウェアエンジニアだけで開発する、
というのは非常に稀なケースであり、
一部(あるいは全部)のモジュールをアウトソーシングするケースが多くあります。</p>
<p>これによって、プロジェクトの品質コントロールをより困難にしています。</p>
<p>また、日本では全ての社員の待遇に差を付けず、
等しくすることを善しとする文化があるようで、
ソフトウェアエンジニアの能力に応じた待遇にする、というようなことを滅多にしません。
一方で、マネジメント能力に関しては、
能力に応じた待遇にするキャリアパスが古くから存在するため、
自分ではコードを一切書かないで一日中パワーポイントやエクセルの資料をせっせと作成している
ソフトウェアエンジニア(?)が多く存在します。
そして、マネジメント能力以外のソフトウェアエンジニアの能力が評価対象ではないため、
自然と「プロジェクトに関わるソフトウェアエンジニアの大半が技術に無関心」と
いう状況になる傾向にあります。
いわゆる Japanese Traditional Big Company では、
特にこの傾向が顕著なのではないでしょうか?</p>
<p>最初に紹介したブログの著者が「日本を代表する大企業に実情に失望した」原因は、
このような背景があるためだと思います。</p>
<p>また、このような背景を作り出しているのは、
Yahoo の記事にある「日本の企業はソフトウェア開発を理解していない」ためだと思います。</p>
<p>以上のように、日本のソフトウェア開発プロジェクトには
技術的負債を生み出す環境が整っているため、
いかなる開発手法、プログラム言語を用いても技術的負債化を防ぐことは出来ません。</p>
<p>それなのに、この状況を改善する為と称して、新しいプロジェクト進捗管理手法を導入する、
という斜め上な施策が実施されることがあります。</p>
<p>どういう論理で考えると、「新しいプロジェクト進捗管理手法を導入すること」と、
「プロジェクトの技術的負債化を防ぐこと」が繋がるのでしょうかね?</p>
<h1>最後に</h1>
<p>私は LuneScript という言語を開発しています。
「プログラム言語は単なる道具でしかない」というのは、
ある意味自己否定しているようにも思われるかもしれません。</p>
<p>ですが、プログラム言語自体で提供できる安全機能は
まだまだ残っていると思っているので、
ソフトウェアエンジニアの助けになるような安全機能を提供できるように
今後も開発を続けていきたいと考えています。</p>
<p>以上。</p>ifritJPemacs26.2 で矢印(→)等の一部のフォントが半角表示されるようになった2019-07-19T00:00:00+09:002019-07-19T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/07/19/font<p>emacs のバージョンを 26.2 に変えたことで、
色々と細かいところの使い勝手が変っている。</p>
<p>その中で、 <code>→</code> 等の一部のフォントが半角表示されるようになったのが
微妙にストレスだったのでちょっと追ってみた。</p>
<h1>原因</h1>
<p>原因、と言うよりは起因と言った方が良いかもしれないが、
<code>→</code> 等の一部のフォントが半角表示されるようになったのは、
フォントに “DejaVu Sans Mono” を使用していることに起因していた。</p>
<p>これを “Bitstream Vera Sans Mono” に変更することで、現象が治った。</p>
<p>全く同じ環境で、 emacs 26.2 ではなく、以前使用していたバージョンの emacs だと
現象は発生しなかった。</p>
<p>emacs の処理が変ったことが原因であるのはほぼ間違い無いが、
emacs の何がどう変ってこの現象が発生し、
どう設定(使用するフォントを変える以外で)すれば、
現象を修正できたのかは残念ながら分からないまま。</p>
<p><b>と、思ったが、次のブログに答えがあった。</b></p>
<p><a href="http://misohena.jp/blog/2017-09-26-symbol-font-settings-for-emacs25.html">http://misohena.jp/blog/2017-09-26-symbol-font-settings-for-emacs25.html</a></p>
<p>詳しくは、上記ブログを確認してもらうとして、
要点だけ説明すると use-default-font-for-symbols に nil 以外が設定されていると、
シンボル等の文字のフォントが default フォントを使用するようになるらしい。
このデフォルト値が t であるため、矢印等の一部のフォントが半角になっていた。</p>
<p>ということで、
以下を設定してやれば、使用するフォントを変えなくても全角で表示されるようになる。</p>
<pre class="example">
(setq use-default-font-for-symbols nil)
</pre>
<p>じゃぁ、どうして “Bitstream Vera Sans Mono” に変えると
全角で表示されたのか?が気になったんで調べてみたが、
どうやら “Bitstream Vera Sans Mono” には矢印などのフォントが
含まれていなことが原因のようだ。</p>
<p>fontforge でフォントの中身を見ると、
“Bitstream Vera Sans Mono” には矢印のフォントがなく、
“DejaVu Sans Mono” には矢印のフォントがあることが判った。</p>
<p>つまり、”DejaVu Sans Mono” には矢印のフォントがあるので、それが表示され、
“Bitstream Vera Sans Mono” には矢印のフォントがないので、
別で設定していた全角のフォントが表示された、ということだろう。</p>
<p>あぁ、これでストレスが一つ減った。</p>ifritJPstream は rewind/seek できる?2019-07-10T00:00:00+09:002019-07-10T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/07/10/stream<p><b>これは seekable な stream と none_seekable な stream の使い分けに関する記事です。</b></p>
<p><b>使い分けが十分出来ている人は読まなくても大丈夫です。</b></p>
<p>皆さんは bitstream という単語をご存知でしょうか?</p>
<p>AV (Audio&Visual) が好きな人や、
それらの業界に関係のある人ならそこそこ聞く単語だと思いますが、
一般的にはあまり馴染の無い単語でしょうか。</p>
<p>馴染の無い人の為に身近な HDD レコーダを例に挙げて説明すると、
HDD レコーダはデジタル放送の電波に乗っているデータをそのまま記録していますが、
このデータが bitstream です。
HDD レコーダは、デジタル放送の bitstream を HDD に記録し、
記録した bitstream を再生する装置と言えます。
もちろん、実際にはそんな単純ではないですが、概ね間違ったことは言ってません。</p>
<h1>stream</h1>
<p>プログラムでデータを扱う時、stream という概念を使って制御します。</p>
<table>
<tr><th>言語</th><th>stream (入力)</th></tr>
<tr><td>Java</td><td>InputStream</td></tr>
<tr><td>Swift</td><td>InputStream</td></tr>
<tr><td>Go</td><td>io.Reader</td></tr>
</table>
<p>上記は言語毎の入力系 stream の例です。</p>
<p>ちなみに入力系の stream とは何かというと、
流れてくるデータを読み出すためのものです。</p>
<p>例えば、先ほどの HDD レコーダの例で説明すると、</p>
<ul>
<li>デジタル放送の電波に乗っている bitstream を読み取る部分</li>
<li>HDD に記録されている bitstream を読み込む部分</li>
</ul>
<p>が入力系の stream です。</p>
<p>また、上記言語の stream (InputStream,io.Reader)には共通することがあります。</p>
<p>それは、データの流れが一方通行で遡ることが出来ない、ということです。</p>
<p>プログラム的に言うと、上記の stream は seek や rewind をサポートしていません。</p>
<p>これを、先ほどの HDD レコーダの例で説明すると、
「過去に放送された番組の録画はできない」ということです。</p>
<p>24 時間全ての番組を常に録画し続けて、
「1週間前に放送された任意の番組を再生する」機能を持つ HDD レコーダはありますが、
それはあくまで録画してあるものを再生しているのであって、
過去に放送された番組を録画することは出来ません。
もしそれが出来るなら、
本当の意味でのタイムマシーンを作ることが出来ることと同義になります。</p>
<p>なお、「過去に放送された番組の録画はできない」ですが、
「録画した番組」の逆再生などは出来ます。</p>
<p>先ほど説明した通り、次のどちらもの入力 stream です。</p>
<ul>
<li>デジタル放送の電波に乗っている bitstream を読み取る部分
<ul>
<li>過去に放送された番組の録画はできない</li>
</ul>
</li>
<li>HDD に記録されている bitstream を読み込む部分
<ul>
<li>録画した番組は逆再生など出来る</li>
</ul>
</li>
</ul>
<p>これはつまり、 stream には次の 2 つのタイプが存在することを意味します。</p>
<ul>
<li>流れが一方通行で遡ることが出来ない stream</li>
<li>流れを遡ることが出来る stream</li>
</ul>
<p>これ以降、上記をそれぞれ none_seekable と seekable とします。</p>
<h1>none_seekable と seekable の使い分け</h1>
<p>上記の通り、stream には none_seekable と seekable の 2 つのタイプが存在します。</p>
<p>では、実際のプログラムでは stream はどう使い分けるべきか? と考えた場合、
seekable である必要がない場合は極力 none_seekable を使うべきです。</p>
<p>なぜならば、
seekable は none_seekable を包括する概念であり、
seekable な stream は none_seekable として使用することが出来ますが、
none_seekable な stream は seekable として使用することが出来ないからです。</p>
<p>次に、疑似言語を使って説明します。</p>
<pre class="src" lang="txt">
fn funcA( data: seekable ) {
sub( data );
}
fn funcB( data: none_seekable ) {
sub( data );
}
</pre>
<p>上記は、 seekable な引数を持つ関数 funcA と、
none_seekable な引数を持つ関数 funcB を定義する疑似言語コードです。
また sub() は、 none_seekable な引数を持つ関数とします。</p>
<p>ここで、この関数 funcA は seekable な stream でしか使用できないのに対し、
この関数 funcB は seekable, none_seekable どちらでも使用できることになり、
funcB は funcA よりも汎用性が高いと言えます。</p>
<p>関数の汎用性が高いことが良いプログラムである、とは一概には言えませんが、
ミドルウェアなどのライブラリでは、汎用性が高い方が良いとされます。</p>
<p>つまり、 stream を入力に持つ関数の処理においては、
seek や rewind の使用は極力避け、
none_seekable の stream で処理可能にすべきである、と言えます。</p>
<p>ただし例外として、 seek や rewind を使用しないと目標のパフォーマンスが出ないとか、
必要なワークメモリが規定を越えてしまう、等の問題がある場合は、
無理に none_seekable で処理する必要はありません。</p>
<p>とはいえ、あくまでも原則は、
seekable ではなく none_seekable で処理できるかどうかを検討するべきです。</p>
<p>言語の組込みの型として seekable と none_seekable が分かれていない言語は、
結構あると思います。</p>
<p>そのような言語でも、
seekable と none_seekable の考え方自体は有効なので実践してください。</p>
<h1>none_seekable で処理することのメリット</h1>
<p>seekable ではなく none_seekable で処理することのメリットとして、
Web ブラウザでの処理を例に挙げて説明します。</p>
<p>もしもブラウザの処理が全て seekable であった場合、
ブラウジングスピードが遅くなることが予想されます。</p>
<p>なぜなら、Web ブラウザは、サーバから HTML をダウンロードし、
HTML 内のリンクを抽出し、そのリンクをさらにダウンロードします。
そしてリンクが画像の場合、画像をデコードして表示します。</p>
<p>画像のデコード処理が none_seekable であるならば、
画像データのダウンロード開始と同時にデコード処理が開始でき、
画像データのダウンロード終了とほぼ同時にデコード処理を完了できます。</p>
<p>一方でもしも画像のデコード処理が seekable だった場合、
画像データをダウンロード終了してからデコード処理を行なわなければならず、
その分タイムロスになります。
さらに欠点はタイムロスだけでなく、
画像データの全てをダウンロードして一旦 RAM やストレージに格納しておく必要があり、
その分のリソースを消費することになります。</p>
<p>画像データのサイズなんてイマドキのハードウェアスペックなら無視できる、
という意見もあるかもしれませんが、例えば 8K の低圧縮画像などは軽く数 10MB を越えます。
こういった画像のデータを全てダウンロードしてからデコードするなんてしてたら、
無駄にリソースを消費することが分かると思います。</p>
<p>また、最近はほとんど使われていませんが、
progressive JPEG なんて画像フォーマットが使われていた時期がありましたが、
これは none_seekable で処理して始めて意味のあるものです。</p>
<p>progressive JPEG を簡単に説明すると、
画像データの一部をダウンロードするだけで、低解像度の画像をデコードできる技術で、
ダウンロードが進むごとにデコード結果の解像度が上がるというものです。</p>
<p>これは、ネットワークの通信速度が低速なころに使用されていた画像フォーマットで、
いまではほとんど使われなくなったものですが、
none_seekable で処理しなければ全く意味のないものです。</p>
<p>他にも none_seekable で処理することのメリットとして、
動画配信に代表されるストリーミングサービスがあります。</p>
<p>あれも、 none_seekable が前提にあるからこそ可能なサービスです。</p>
<p>「ストリーミングサービスが none_seekable だ」と書くと
「Youtube はシークできるぞ」とかツッコミがあると思うので一応補足しておきます。</p>
<p>たしかに Youtube などの動画配信サービスはシークできるのが当たり前です。
しかし、通常再生時は none_seekable で処理していて、
シークなどの操作が入った時だけ、
サーバからデータをダウンロードしなおして処理しています。
つまり、基本は none_seekable です。</p>
<p>もしも動画データが seekable 前提だった場合、
動画データを全てダウンロードしてからでないと再生できないか、
seek 処理が大量に発生してサーバ間の通信負荷が非常に高くなることが予想されます。</p>
<p>また、seekable(randam access) は none_seekable(sequential) と比べて
非常にパフォーマンスが悪くなるのが一般的です。</p>
<p>例えば HDD の randam access は sequential と比べて 2 桁以上のパフォーマンス劣化、
SSD でも 1 桁以上劣化します。
RAM であっても、randam access することでキャッシュミスが発生しやすくなり、
パフォーマンス劣化からは逃れられません。
現代ではほとんど使われませんが、
テープデバイスなんて使った日には、どれほどかかるか想像すら出来ません。</p>
<h1>データフォーマット</h1>
<p>stream を処理する際に、
それを none_seekable として扱うには、
stream に流れるデータのフォーマットが none_seekable として
扱い易い構造になっている必要があります。</p>
<p>データフォーマットが none_seekable として扱い難い構造の場合、
上記のように「目標のパフォーマンスが出ない」、「必要なワークメモリが規定を越えてしまう」
という問題が発生する可能性があります。</p>
<p>ある程度の大きさになるデータフォーマットを定義する時は、
必ず none_seekable で処理することを考えて定義しましょう。</p>
<p>なお、 stream で処理することが多い画像や音声などのデータフォーマットは、
基本的には none_seekable で処理できるように定義されています。</p>
<p>もしもそうでなければ、放送や動画配信でデジタルデータを扱うことは出来ません。</p>
<p>ちなみに、データの encode と decode の none_seekable での扱い易さは、
相反することがあります。</p>
<p>その場合、どちらかを優先するか、折衷案の検討が必要です。
一つ言えることは、作業バッファを 0 にすることはまず不可能なので、
どの程度の作業バッファサイズなら妥当かを判断することが重要です。</p>
<h1>例外</h1>
<p>none_seekable で処理することで、
ダウンロードとデコードを同時に処理できるため高速に処理できる、と説明しましたが、
一部例外があります。</p>
<p>それは、専用ハードウェアを使用してデコードする場合です。</p>
<p>HDD レコーダなどの家電製品では、
動画や音声を処理する専用ハードウェアを搭載しています。
それら専用ハードウェアは、データを渡すと高速に処理して結果を返してくれますが、
処理するデータは全て揃えてから渡さなければならない、
という制約があることがほとんどです。</p>
<p>その場合は、none_seekable でダウンロードとデコードを同時に処理するよりも、
専用ハードウェアを使用して処理する方が高速に処理できます。</p>
<p>ただし、当然専用ハードウェアであるため、処理できるデータは限られていますし、
そのような専用ハードウェアが利用できる環境は限られています。</p>
<h1>まとめ</h1>
<p>stream を扱う際は、次を注意する必要があります。</p>
<ul>
<li>極力 none_seekable で扱う</li>
<li>データフォーマットを決める時点で、 none_seekable で扱えることを考慮する</li>
</ul>
<h1>最後に</h1>
<p>なんでこんなことを書いたかというと、
最近とある画像コーデックのライブラリを扱うことがあったんですが、
そのライブラリへの入力が seekable であることを前提としていてムカついた、
という経験をしたためです。</p>
<p>データ streaming 処理を行なう場合の基本的な考えなので、
必ずこれらを考慮に入れて設計するようにお願いします。</p>
<p>以上。</p>ifritJPpython のクラスを JSON 化2019-07-04T00:00:00+09:002019-07-04T00:00:00+09:00https://ifritjp.github.io/blog/site/2019/07/04/python-json<p><a href="https://github.com/ifritJP/game-message-tts">コレ</a> を作るにあたって、データの serialize/deserialize の方法を調べた結果、
<a href="https://pypi.org/project/marshmallow-dataclass/">marshmallow_dataclass</a> に落ち着きました。</p>
<p>いくつか調べた中で、パッと見、直感的に出来そうだった、というだけの理由ですが。。</p>
<p>実際、面倒な処理はほとんど無く、 serialize/deserialize が可能になりました。</p>
<h1>使い型</h1>
<p><a href="https://pypi.org/project/marshmallow-dataclass/">marshmallow_dataclass</a> は、
クラスを宣言する際に <code>@dataclass</code> デコレータを付けて宣言し、
メンバの型を宣言するのが基本です。</p>
<p>こんな感じ。</p>
<pre class="src" lang="py">
@dataclass
class LogItem:
# ゲームタイトル
title:str
# 日付
date:int
# テキスト
text:str
# テキスト長
len:int
</pre>
<p>メンバの宣言が python っぽくないと思う方もいるかもしれませんが、
静的型付け言語になれていると、こっちの方が馴染み易い気がします。</p>
<p>JSON 化する場合は、
次のようにクラスメソッドに JSON 化するクラスのインスタンスを渡すだけです。</p>
<pre class="example">
item = LogItem( "title", time.time(), "text", len( "text" ) )
print( marshmallow_dataclass.class_schema( LogItem )().dumps( item ) )
</pre>
<p>逆に JSON からクラスインスタンスを生成するには、
次のようにクラスメソッドに渡すだけです。</p>
<pre class="example">
marshmallow_dataclass.class_schema( LogItem )().loads( text )
</pre>
<p>とても簡単です。</p>
<p>ただ、躓いた点があったので、気をつけるべき点として書いておきます。</p>
<ul>
<li>python3.7 以降を使用する</li>
<li><code>@dataclass</code> デコレータを付けたクラスに次を宣言してはならない
<ul>
<li>コンストラクタ __init__</li>
<li>@staticmethod load()</li>
</ul>
</li>
</ul>ifritJP