2025年を振り返る

今年もあっという間に大晦日。一年が本当に早い。
ということで例年の通り振り返り。

・仕事は暇だった。
 12月から職場が変わって心機一転!
 久しぶりに技術欲が出てきて色々勉強中。
 とりあえずしばらくはAWSとAIに中心に勉強!(まずは資格取得かな)

・投資は順調。
 仮想通貨も始めた。長期投資が目的なので、5〜10年後に爆上げしてくれればOK。
 それ以外はオルカンとNASDAQ100。
 資産は順調に増えて行くが、何かレバレッジを掛けないともう一段階上には行けない。
 仕事なのか投資なのか、来年は何か策を考えたいと思う。

・旅行は久しぶりの海外!
 台湾・中国に行けた!来年も2〜3カ国くらいは行きたいな〜
 英語も身に着けたいけど、しばらくは技術中心かな。
 来年は北海道も久しぶりに行きたい。

・遊びはまあまあ。程よく遊んだ。
 交友関係が広がって、飲み会が増えたかな。
 ありがたいので交友関係は大事にしていきたい。

・ライブも充実。
 toconoma、I am robot and proudに行けた!
 来年はtoconomaやkraftwerkmonobrightの予定も!
 音楽は浴びていきたい。


振り返ると特徴的な事は無かったかな。
ダラっと仕事をしてしまったので、時間が勿体無かったかな。
年齢的にも時間にシフトしていきたいな。
そして時間を有意義に使って、もう一段階上に行きたい。
12月から職場が変わって、モチベーションと意識が上がっているので、
キープして仕事で成果を上げていきたい。


今年もお世話になりました!来年もよろしくお願いします!

2024年を振り返る

今年もお疲れ様でした。
いつか振り返るように、今年のまとめ。

・ギター(アジカンばかりだけど)
・株(ずっとチャート見てた)
・旅行(新潟、天草、五島列島稚内
・遊び
・20代が終わった

今年は仕事も落ち着いており、よく遊んだな!という感じ。
来年も良く遊びつつ、少し努力していきたい。

お世話になった方々ありがとう!来年もよろしくお願いします!

2023年を振り返る

毎年の恒例行事化していますが、とりあえず書いておきます。


今年はざっくり以下。
・飛行機めちゃ乗りました
・データ構造とアルゴリズムを少し勉強しました
・投資の勉強をしました(継続中)
アイヌ語アイヌ文化を少し勉強しました
・プールに少し通いました
・引っ越ししました


来年は、もう少し投資の勉強をする予定。
あと音楽もやっていきたいなぁ。
何か新しいことを始めたい気分。
どうなったかはまた来年。


2022年を振り返る - cyblue9's blog

P.S.
気づけば毎年大晦日、何かしらのコード書いてるな。来年も書いているかな。

はい、書いています。
先週から開発が始まってしまい、今年も書いてます。


良いお年を!

2022年を振り返る

今年は仕事忙しかったこともあって一瞬でした。
仕事は充実度が高かったのでとても良かったです。

他には以下のことが良かったです。
・100kmウォーキングを完歩できたこと
・アプリを2つ開発できたこと(1つはHACK DAYで受賞できた!)
・たくさん旅をできたこと(富山、山口、下関、小倉、函館、上高地、横須賀、富士宮、山陰、大阪、滋賀、紀伊半島
・ライブに行けたこと(toconomaフィッシュマンズ、loop pool)
・旧友とも遊べたこと

来年は充実させつつ、季節感を味わいながらゆっくり過ごしたいです。(仕事が落ち着けば)
少し新たなチャレンジも検討しているので、できたらいいな。
それでは今年もありがとうございました。
来年もよろしくお願いいたします。
良いお年をお迎え下さい。

P.S.
気づけば毎年大晦日、何かしらのコード書いてるな。来年も書いているかな。

2021年を振り返る

今年も終わるので、例年の恒例行事の振り返り。

とはいえ、これと言って書くこともない。。。

毎年着実に人生の幅が広がっていると思います。
意図しているのでいい感じです。
今年はアウトプットは少なかったから、増やしていきたいな。
もうちょい脳みそに汗かいた方がいいかな?

何もしないと活動時間が減っていきそうなので、来年は意図的に活動していこうと思います!
それでは良いお年を!

プログラミングの次のステップを目指す君たちへ

「プログラミングを学んでみたけど、何を作ったら良いか分からない。」 そんな質問を最近よく受ける。そのたびに僕は「何かアプリを作る。とか、競技プログラミングをする。とか・・・」という曖昧な答えをする。なぜ、曖昧な答えをするのか。それには一言で答えられない理由があるからだ。それを、ここに記したい。

はじめに

僕はここ数年、次に何を作ったらいいか。と迷うことはない。日々、アイデア・仮説が浮かんでくるからだ。しかしそんな僕も昔は、悩む彼らと同じ、もしくはそれ以上だった(3年くらい同じ悩みと向き合ってきた)。そのとき僕も同じように有識者に相談した。しかし、彼らの答えといえば「作りたいものを作ればいいじゃん」だった。「いや、その作りたいものがないんだよ!」とは言えず、路頭に迷っていた。

正直今も自分の人生を掛けるほど何か作りたいものがあるわけではない。しかし、小さなことだけれども、あれを試してみたいな。とか、プロトタイプを作ってみたいな。と日々思う。そう成れたのは主に2つの理由があると思う。

  • 技術の理解
  • 生き方の理解

それぞれについて少し考えてみたい。

技術の理解

今振り返ると、作りたいものがなかった頃は、技術の理解が足りていなかったと思う。その頃は、せいぜい様々なプログラミング言語の文法を理解しているレベルだった。しかし今は、コンピュータ技術の根本や背景を理解できている。また、コンピュータがやるべき仕事 も理解できている。特に大切なのは後者で、プログラミングができる。とか、プロトコルを知っている。ではなくて、コンピュータがやるべき仕事は何か、これからコンピュータをどのように使っていけば良いか。を理解することが大切であると思う。そのためには、表面的な知識・理解ではなく、技術の歴史・背景・考え方を学ぶ必要があるし、それらを理解するためには(堂々巡りのようだが)、表面的な知識の圧倒的理解が必要である。

例えば、僕はここ2年くらいはlive coding(メディア・アート)やそのツール開発をしている。その理由は主に以下のようである。

  1. プログラミングがシステム開発の道具だけになりつつある現代へのアンチテーゼ
  2. プログラミングを理解している人が、理解していない人へ自然言語で説明することが求められている現代へのアンチテーゼ
  3. コンピュータとアートがこれから結びついていく将来への展望

このように、細かな技術どうこうよりも、これからのコンピュータ技術のあり方、コンピュータってこんなことできるんだと!ということを考え・発信していきたいと思い活動していると、その情熱が枯渇しない限りはアイデアが止まることはない。

作りたいものがない。という人達に足りないのは、ビジョンだと思う。そしてビジョンを描けるようになるためには、それ相応の知識が必要である。アウトプットができないのは、インプットが足りないからである。だから焦らず、学び続けることを大切にして欲しい。

生き方への理解

悩んでいた当時の僕にもアイデアも少しはあったと思う。しかし、それを形にできなかったのは、時間をかけて上手くいかなったらどうしよう。とか、もう誰かがやっているよ。というもう一人の自分の声が聞こえてきたからだと思う。そんな当時の僕は、ポール・グレアム氏のエッセイが指南書だった。

今、何を、どうやってすればいいかって? まず興味の持てるプロジェクトを選ぶことだ。ある分量の資料を研究するとか、 何かを作ってみるとか、何かの問題の答えを見つけてみるとか。 ひと月以内で終わらせられるようなプロジェクトがいい。 そして、ちゃんと終わらせられる手段があるようなものにする。 少しは頑張らなくちゃならないようなものがいいけれど、ほんとうに少しだけでいい。 特に最初はね。もし二つのプロジェクトのどっちを選ぶか迷ったら、 面白そうな方を選ぼう。失敗したら、もう一方を始めればいいんだ。 これを繰り返す。そうすると次第に、ちょうど内燃機関みたいに、 このプロセスが自分で走り出すようになる。一つのプロジェクトが次の プロジェクトを生み出すようになるんだ。(そうなるまでには何年もかかるけれどね。)プロジェクトが君の将来目指すものにあまり関係なさそうだったとしても、 心配することはない。目指すものに到達する道っていうのは、君が思うより ずっと大きく曲がりくねるものなんだ。プロジェクトをやることで、道は伸びてゆくんだ。 一番大事なのは、わくわくして取り組むことだ。そうすれば経験から学ぶことができるからだ。

当時は理解できなったが、今では本当にその通りだと思う。現在は歳を取って、一ヶ月くらいあっという間に過ぎる。だから、やってもやらなくてもあっという間に過ぎるなら、やってみるかとなる。

また、アイデアの循環ができるようになってきた。何か作り始めたら、途中でアイデアが生まれ、次に作りたいものができるし、作り上げたものから話が広がり、別の仕事を受けることもある。

何かを作ったら終わりではなく、次に繋がるようになる。それは少し経験がないと理解できないと思うが、やり始めなければ永遠に理解できない。上手くいかなったらな。とか、時間を無駄にしてしまってはな。とか嫌なことが頭を過ることは分かる。しかし、実際失敗することはないし、今見えていない次のアイデアは絶対にある。だから、とりあえず手を動かしてみたい。これを人生の早い段階で気付けると後々楽になる。

まとめ

何を作ったらいいか分からない。は、技術者的理由と人間的理由があると思う。というのが僕の意見だ。

本当に今は楽しい。次々アイデアが浮かんでくる(そのためにそれ相応の努力をしているが、その話は別途としよう)。自分が昔憧れていた側に回れているいることは、とても不思議だし、とても自信になっている。今悩んでいる子たちも、自分でブレークスルーを作り、こちら側に来てくれることを願う。

【Python】ListとDequeのパフォーマンスの違い〜CPyhtonの実装を読んで〜

先日以下のブログを読みました。

dev.classmethod.jp

PythonのListとDequeのパフォーマンスを比較しています。 しかし、「なぜパフォーマンスに違いが生まれるのか。」記されていないため、この記事ではCPythonの実装を読みながら、違いが生まれる理由を探ってみたいと思います。
※本記事で扱うCPythonのコードは最新版ではありません。 ListはCommit ID3c87a667bb367ace1de6bd1577fdb4f66947da52、DequeはCommit IDd4edfc9abffca965e76ebc5957a92031a4d6c4d4です。

 目次

  1. List Objectについて
  2. Deque Objectについて
  3. List vs Deque(先頭要素)
    3.1 List
    3.2 Deque
    3.3 まとめ(先頭要素)
  4. List vs Deque(中間要素)
    4.1 List
    4.2 Deque
    4.3 まとめ(中間要素)
  5. List vs Deque(末端要素)
    5.1 List
    5.2 Deque
    5.3 まとめ(末端要素)
  6. おわりに
  7. 参考文献

1. List Objectについて

初めに、簡単にデータ構造を整理しておきます。
Listは以下のようになっております。

typedef struct {
    PyObject_VAR_HEAD

    PyObject **ob_item;

    Py_ssize_t allocated;
} PyListObject;

#ifndef PyList_MAXFREELIST
#define PyList_MAXFREELIST 80
#endif
static PyListObject *free_list[PyList_MAXFREELIST];
static int numfree = 0;

ob_itemがvectorへのポインタを指すことで、可変長データ構造を実現しています。 (Listをnew()する場合、free_listからPyListObjectを一つ取り出し、 メモリ確保しています。

op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));

)

https://github.com/python/cpython/blob/3c87a667bb367ace1de6bd1577fdb4f66947da52/Include/listobject.h https://github.com/python/cpython/blob/3c87a667bb367ace1de6bd1577fdb4f66947da52/Objects/listobject.c

2. Deque Objectについて

Dequeは、双方向リンクリストと配列のハイブリッドで実現されています。

#define BLOCKLEN 64
#define CENTER ((BLOCKLEN - 1) / 2)

typedef struct BLOCK {
    struct BLOCK *leftlink;
    PyObject *data[BLOCKLEN];
    struct BLOCK *rightlink;
} block;

typedef struct {
    PyObject_VAR_HEAD
    block *leftblock;
    block *rightblock;
    Py_ssize_t leftindex;       /* 0 <= leftindex < BLOCKLEN */
    Py_ssize_t rightindex;      /* 0 <= rightindex < BLOCKLEN */
    size_t state;               /* incremented whenever the indices move */
    Py_ssize_t maxlen;          /* maxlen is -1 for unbounded deques */
    PyObject *weakreflist;
} dequeobject;

static PyTypeObject deque_type;

#define MAXFREEBLOCKS 16
static Py_ssize_t numfreeblocks = 0;
static block *freeblocks[MAXFREEBLOCKS];

図で整理すると以下の通りです。(一部簡略) f:id:blue_9:20200105195853p:plain

https://github.com/python/cpython/blob/d4edfc9abffca965e76ebc5957a92031a4d6c4d4/Modules/_collectionsmodule.h

https://github.com/python/cpython/blob/d4edfc9abffca965e76ebc5957a92031a4d6c4d4/Modules/_collectionsmodule.c

3. List vs Deque(先頭要素)

参考ブログでは以下のコードで、処理時間を比較しています。

List

list_array = list(range(100000))
for i in range(1000):
    list_array.pop(0)

処理時間は以下の通りです。
1回目 elapsed_time:0.05488991737365723[sec]
2回目 elapsed_time:0.04288434982299805[sec]
3回目 elapsed_time:0.054895877838134766[sec]

Deque

deque_array = deque(range(100000))
for i in range(1000):
    deque_array.popleft()

処理時間は以下の通りです。
1回目 elapsed_time:0.008365631103515625[sec]
2回目 elapsed_time:0.007332801818847656[sec]
3回目 elapsed_time:0.008947134017944336[sec]

3.1. List

List.pop()の実装部はここです。
エラーチェック後、実質的にpop処理をしているのは以下の部分です。

status = list_ass_slice(self, index, index+1, (PyObject *)NULL);

pop(0)の場合、以下のようになります。

ex: a = [1,2,3,4,5,6,7,8,9,10]

list_ass_slice(self, 0, 1, (PyObject *)NULL);

#define b ((PyListObject *)v)
    if (v == NULL)
        n = 0;

    norig = ihigh - ilow; // 1
    d = n - norig;        // -1
    item = a->ob_item;
    s = norig * sizeof(PyObject *); // sizeof(PyObject *)

    if (s) {
        memcpy(recycle, &item[ilow], s);
    }

    if (d < 0) { /* Delete -d items */
        Py_ssize_t tail;
        tail = (Py_SIZE(a) - ihigh) * sizeof(PyObject *); // 9 * sizeof(PyObject *)
        memmove(&item[ihigh+d], &item[ihigh], tail); // a[1:10] を a[0:9]に移動
        if (list_resize(a, Py_SIZE(a) + d) < 0) { // Py_SIZE(a)の変更
        }
        item = a->ob_item;
    }
    for (k = norig - 1; k >= 0; --k)
        Py_XDECREF(recycle[k]); // ref--
    result = 0;

決めては以下の処理。

memmove(&item[ihigh+d], &item[ihigh], tail); // a[1:10] を a[0:9]に移動

計算量がO(n)であることが分かります。

3.2. Deque

deque.leftpop()の実装部はここです。 サイズチェック後、実質的にpop処理をしているのは以下の部分です。

item = deque->leftblock->data[deque->leftindex];
deque->leftindex++;
Py_SIZE(deque)--;

計算量がO(1)であることが分かります。

3.3. まとめ(先頭要素)

先頭要素への処理の計算量は以下の通りです。

処理 計算量
List.pop(0) O(1)
Deque.leftpop() O(n)

4. List vs deque(中間の要素)

参考ブログでは以下のコードで、処理時間を比較しています。

List

list_array = list(range(10000))
for i in range(1000000):
    list_array.remove(20)
    list_array.insert(20, 20)

処理時間は以下の通りです。
1回目 elapsed_time:25.979241132736206[sec]
2回目 elapsed_time:26.479660749435425[sec]
3回目 elapsed_time:26.32246232032776[sec]

Deque

deque_array = deque(range(10000))
for i in range(1000000):
    deque_array.remove(20)
    deque_array.insert(20, 20)

処理時間は以下の通りです。
1回目 elapsed_time:21.0693142414093[sec]
2回目 elapsed_time:21.471088647842407[sec]
3回目 elapsed_time:21.121334075927734[sec]

4.1. List

Listのremoveの実装はここです。
中身の肝は以下。

list_ass_slice(self, i, i+1, PyObject *)NULL)

popと同様に計算量がO(n)であることが分かります。

Listのinsertの実装はここです。
中身の肝は以下。

ins1(self, index, object)

insert(20,20)の場合、以下のようになります。

ex: a = [0,...,19,21...,100000]

ins1(self, 20, 20)

Py_ssize_t i, n = Py_SIZE(self); // 99,999
if (list_resize(self, n+1) < 0) // Py_SIZE(self)++

items = self->ob_item;
for (i = n; --i >= where; )
     items[i+1] = items[i];
Py_INCREF(v);
items[where] = v;

計算量がO(n)であることが分かります。

4.2. Deque

dequeのremoveの実装はここです。
中身の肝は以下。

for (i=0 ; i<n ; i++) {
        PyObject *item = deque->leftblock->data[deque->leftindex];
        int cmp = PyObject_RichCompareBool(item, value, Py_EQ);

        if (cmp > 0) {
            PyObject *tgt = deque_popleft(deque, NULL);
            assert (tgt != NULL);
            if (_deque_rotate(deque, i))
                return NULL;
            Py_DECREF(tgt);
            Py_RETURN_NONE;
        }
        _deque_rotate(deque, -1);
}

要素判定は常に左端要素を使用します。要素が見つかるまでrotate(-1)を行います。要素が見つかり次第、popleft()し、rotate(要素index)します。
計算量がO(n)であることが分かります。

この_deque_rotate()の中身はここです。
中身の肝は以下。

Py_ssize_t m = n;

if (m > rightindex + 1)
    m = rightindex + 1;
if (m > leftindex)
    m = leftindex;
    assert (m > 0 && m <= len);
    rightindex -= m;
    leftindex -= m;
    src = &rightblock->data[rightindex + 1];
    dest = &leftblock->data[leftindex];
    n -= m;
    do {
        *(dest++) = *(src++);
    } while (--m);

    deque->leftblock = leftblock;
    deque->rightblock = rightblock;
    deque->leftindex = leftindex;
    deque->rightindex = rightindex;

計算量がO(n)であることが分かります。

次にinsertの実装はここです。
中身の肝は以下。

    if (_deque_rotate(deque, -index))
        return NULL;
    if (index < 0)
        rv = deque_append(deque, value);
    else
        rv = deque_appendleft(deque, value);
    if (_deque_rotate(deque, index))
        return NULL;

rotate()して左端か右端にappend()しています。
rotate()の計算量は上記の通り、O(n)であることが分かります。

4.3. まとめ(中間要素)

中間要素への処理の計算量は以下の通りです。

処理 計算量
List.remove() O(n)
List.insert() O(n)
Deque.remove() O(n)
Deque.insert() O(n)

参照ブログでは、Dequeの方が高速に処理できました。 その理由は、以下の通りです。

List: 削除要素以外の全要素でメモリ移動が伴う
Deuqe: 先頭要素から削除要素までのみメモリ移動が伴う

処理時間測定コードでは、先頭から20番目の要素をromove()、insert()しています。よってDequeの方が、メモリ移動の対象になる要素が少なく、処理時間が高速だったと考えられます。

5. List vs deque(末端の要素)

参考ブログでは以下のコードで、処理時間を比較しています。

List

list_array = list(range(1000))
for i in range(100000):
    list_array.append(0)

処理時間は以下の通りです。
1回目 elapsed_time:0.017949581146240234[sec]
2回目 elapsed_time:0.019548416137695312[sec]
3回目 elapsed_time:0.019548416137695312[sec]

Deque

deque_array = deque(range(1000))
for i in range(100000):
    deque_array.append(0)

処理時間は以下の通りです。
1回目 elapsed_time:0.023968935012817383[sec]
2回目 elapsed_time:0.023933887481689453[sec]
3回目 elapsed_time:0.01991128921508789[sec]

5.1. List

Listのappendの実装はここです。 中身の肝は以下。

app1(self, object)

append(0)の場合、以下のようになります。

Py_ssize_t n = PyList_GET_SIZE(self);
if (list_resize(self, n+1) < 0) // Py_SIZE(self)++

Py_INCREF(v);
PyList_SET_ITEM(self, n, v); // (((PyListObject *)(op))->ob_item[i] = (v))

計算量がO(1)であることが分かります。

5.2. Deque

dequeのappendの実装はここです。 中身の肝は以下。

deque_append_internal(deque, item, deque->maxlen)

deque_append_internal()の実装はここです。 中身の肝は以下。

    deque->rightindex++;
    deque->rightblock->data[deque->rightindex] = item;

計算量がO(1)であることが分かります。

5.3. まとめ(末端要素)

末尾要素への処理の計算量は以下の通りです。

処理 計算量
List.append() O(1)
Deque.append() O(1)

6. おわりに

本記事では、PythonのListとDequeの処理の違いをCythonのコードから探ってみました。
ListとDequeによって「なぜパフォーマンスに違いが生まれるのか。」理解につながれば幸いです。

7. 参考文献

https://postd.cc/python-internals-pyobject/ https://note.nkmk.me/python-collections-deque/ https://docs.python.org/ja/3/c-api/sequence.html https://docs.python.org/ja/3/c-api/intro.html https://docs.python.org/3/c-api/typeobj.html