Jupyter Notebook をコマンドラインで作成して Jupyter kernel をテストする方法

Jupyter Notebook のカーネルを自動でテストする方法はいくつかあるようだけど、ユーザが使うことを想定しているような実装には見えない。今回は、nbformat を使って Python プログラムから notebook(.ipynb ファイル)を作って実行し結果を確認する方法について記録しておく。

Notebook の作成

import nbformat

nb = nbformat.v4.new_notebook()
title = "Python3 Test Notebook"
code = """
import sys
print(sys.version)
"""
nb["cells"] = [
    nbformat.v4.new_markdown_cell(title),
    nbformat.v4.new_code_cell(code)
]

with open("/notebook/Python3Test.ipynb", "w") as f:
    nbformat.write(nb, f)

こんな感じにすると、1つ目のセルに Python のバージョンをチェックするコードが入った notebook が Python3Test.ipynb の名前で保存される。

Notebook の実行

$ jupyter nbconvert --ExecutePreprocessor.kernel_name=<kernel> --to notebook --execute --inplace /notebook/Python3Test.ipynb

このようにすると、notebook を実行して結果を同ファイルに上書きしてくれる。

Notebook の中身の確認

import sys
import nbformat

with open(sys.argv[1]) as f:
    nb = nbformat.read(f, as_version=4)

for cell in nb['cells']:
    if 'outputs' in cell:
        for output in cell['outputs']:
            print(output['text'])

スクリプト実行するときに第一引数に notebook のファイル名を指定する。notebook 内の結果部分だけがすべて標準出力に出る。

Dockerfile の中で conda activate できないときの対処法

Dockerfile でビルド中に conda activate しようとすると、conda init しろみたいなメッセージが出るけど、それを書いたところで解決しない。ちょっと調べてもなかなか良い workaround が見つからなかったのでメモしておく。

これを回避するためには大きく2つの方法があり、一つは conda run を使う方法で、もう一つは RUN の中身を実行する SHELL をインタラクティブにする方法。

conda run を使う方法

RUN <<EOF
set -xe
conda run -n <env> <command>
EOF

みたいにすると、conda 環境の中でコマンドを実行してくれる。ただ、これだけだとコマンドによってはインタラクティブなシェルで実行するときと挙動が変わるものがあって、思った結果にならないことがある。

SHELL をインタラクティブにする方法

これは、SHELL を明示的に指定する方法。

SHELL ["/bin/bash", "-i", "-c"]

RUN <<EOF
set -xe
conda activate <env>
command
coda deactivate

こうすると、-i によりインタラクティブなシェルで実行される。この場合、conda activate できるので普段の使い方と同様のスクリプトを書くことができる。

HedgeDoc を Docker compose で動かす(SSL + リバースプロキシ)

概要

HedgeDoc というマークダウンでメモを書けるサービスが便利そうだったので自分用に立ち上げてみた。
サクッと閲覧のみで公開できるのはすごく便利そうに思える。

  • フロントエンドの nginx をリバースプロキシとして使っているので、そこからプロキシされるように設定。
  • サブディレクトリ /hedgedoc/ で運用。
  • 外部とは SSL でやり取りさせる。

HedgeDoc 提供コンテナ群

HedgeDoc を提供するためのコンテナ群はこのようにした。
shared はフロントエンドの nginx から hedgedoc:3000 でアクセスできるようにするために接続している。

version: '3.1'

services:

  database:
    image: postgres:13.4-alpine
    environment:
      - POSTGRES_USER=hedgedoc
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=hedgedoc
    volumes:
      - ./database:/var/lib/postgresql/data
    restart: always

  app:
    container_name: hedgedoc
    image: quay.io/hedgedoc/hedgedoc:latest
    environment:
      - CMD_DB_URL=postgres://hedgedoc:password@database:5432/hedgedoc
      - CMD_DOMAIN=www8281uo.sakura.ne.jp
      - CMD_HOST=hedgedoc
      - CMD_PORT=3000
      - CMD_URL_PATH=hedgedoc
      - CMD_URL_ADDPORT=false
      - CMD_ALLOW_ANONYMOUS=false
      - CMD_ALLOW_EMAIL_REGISTER=false
      - CMD_PROTOCOL_USESSL=true
    volumes:
      - ./uploads:/hedgedoc/public/uploads
    restart: always
    depends_on:
      - database

networks:
  default:
    external:
      name: shared

リバースプロキシの設定

リバースプロキシにしている nginx の設定はこんな感じ。
socket.io の項目をどうしたらいいか少し難しかったけど以下の内容でうまくいくようだ。

location /hedgedoc/ {
    proxy_set_header Host \$host;
    proxy_set_header X-Real-IP \$remote_addr;
    #proxy_set_header X-Forwarded-Host \$host;
    #proxy_set_header X-Forwarded-Server \$host;
    proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto \$scheme;
    proxy_pass http://hedgedoc:3000/;
}
location /hedgedoc/socket.io/ {
    proxy_set_header Host \$host;
    proxy_set_header X-Real-IP \$remote_addr;
    #proxy_set_header X-Forwarded-Host \$host;
    #proxy_set_header X-Forwarded-Server \$host;
    proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto \$scheme;
    proxy_set_header Upgrade \$http_upgrade;
    proxy_set_header Connection \$connection_upgrade;
    proxy_pass http://hedgedoc:3000/socket.io/;
}

参考サイト

VPS を再構築した話

VPS の OS が古くなってしまったので作り直した。

作りこんでいたサービスのほとんどはもはや使っていなかったので、とりあえずこの wordpress だけを持ってきた。
次回以降のことを考えて、docker で構築。

はじめに

外部 ↔ フロントエンドコンテナ(nginx) ↔ WordPress コンテナ群(nginx, php-fpm, mysql)という構成。
これだと、サーバ内にリバースプロキシが二つもあって無駄な処理をすることになるが、管理しやすさを考えてこうしてみた。問題があれば変更しよう。

コンテナ間通信ネットワークの作成

まずはコンテナ間通信のためのネットワークを作成し、

$ sudo docker network create --driver bridge shared

WordPress 用コンテナ群の作成

WordPress を提供するためのコンテナ群を作成。
これは nginx + php-fpm で動くようにした。
WordPress をサブディレクトリで動かすために working_dir: を設定するのだが、これに気が付くのに時間を費やした。

version: '3.1'

services:

  rproxy:
    container_name: wp-rproxy
    build:
      context: ./mynginx
      dockerfile: Dockerfile
    restart: always
    volumes:
      - ./wordpress:/var/www/html/blog
      - ./nginx-log:/var/log/nginx
    depends_on:
      - wordpress

  wordpress:
    image: wordpress:6.4.2-php8.3-fpm-alpine
    restart: always
    working_dir: /var/www/html/blog
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: mywordpress
      WORDPRESS_DB_NAME: wp
    volumes:
      - ./wordpress:/var/www/html/blog
      - ./wordpress-log:/var/log
    depends_on:
      - db

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_DATABASE: wp
      MYSQL_USER: wp
      MYSQL_PASSWORD: ***
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - ./db:/var/lib/mysql
      - ./db-log:/var/log

この mynginx は以下の Dockerfile で作成。

FROM nginx:alpine


COPY <<EOF /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name localhost;

    root /var/www/html;
    index index.php;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /blog/ {
        try_files \$uri \$uri/ /index.php?\$args;
    }

    location ~ \.php\$ {
        include fastcgi_params;
        fastcgi_pass wordpress:9000;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_param PATH_INFO \$fastcgi_path_info;
    }
}
EOF

フロントエンドコンテナの作成

今後別のサービスを動かしたくなるかもしれないので、wordpress を提供しているコンテナ群とは独立に一つ nginx を立ててフロントエンドにする。
これは基本的には / にあるファイルの提供と、サブディレクトリに配置したサービスへのリバースプロキシにする。
なので、サービスを追加した際にはこのコンテナ(の default.conf)を適宜更新する。

version: '3.1'

services:

  rproxy:
    container_name: rfront
    build:
      context: ./mynginx
      dockerfile: Dockerfile
    ports:
      - 80:80
    restart: always
    volumes:
      - ./html:/var/www/html
      - ./nginx-log:/var/log/nginx

networks:
  default:
    external:
      name: shared

この mynginx は以下の Dockerfile で作ります。

FROM nginx:alpine

COPY <<EOF /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '\$remote_addr - \$remote_user [\$time_local] "\$request" '
                      '\$status \$body_bytes_sent "\$http_referer" '
                      '"\$http_user_agent" "\$http_x_forwarded_for"';

    log_format upstreamlog '[\$time_local] \$remote_addr \$host \$upstream_addr '
                           '\$upstream_cache_status \$upstream_status '
                           '\$upstream_http_location \$request';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
EOF

COPY <<EOF /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name www8281uo.sakura.ne.jp;

    root /var/www/html;
    index index.html index.htm;

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/upstream.log upstreamlog;
    error_log /var/log/nginx/error.log;

    location /blog/ {
        index index.php;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-Host \$host;
        proxy_set_header X-Forwarded-Server \$host;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_pass http://wp-rproxy:80/blog/;
        proxy_redirect default;
    }
}
EOF

おわりに

WordPress くらい有名なサービスだとたくさん情報があるので調べればすぐにいろいろ出てくる。
Docker を使うことで、php-fpm を使った高速なサイト構築が簡単に出来るというのはすごいことだ。

C 言語プログラムから MIPS アセンブリを生成する方法とコンパイラによる最適化について

コンピュータ・アーキテクチャの講義で MIPS 命令セットを使って説明をしているが、コンパイラが生成するアセンブリが気になる人もいると思うのでこの記事で扱っておく。

最近の環境だと clang というコンパイラを使うのが良い。Windows では、msys2 を使うと clang を簡単にインストールして使うことができる。

環境づくり:clang のインストール

msys2 をインストール後、以下のコマンドを入力すると clang をインストールできる。

$ pacman -S msys/clang

現在(2022/7/4)のバージョンは以下。

$ clang --version
clang version 11.0.0 (https://github.com/msys2/MSYS2-packages ca391a3660d17cdee1e94d5afd2e72a4f750cddb)
Target: x86_64-pc-windows-msys
Thread model: posix
InstalledDir: /bin

mips ターゲットのアセンブリ生成

コンパイル対象のプログラム(fib.c)

フィボナッチ数列を再帰的に計算するためのプログラムを例にする。

int fib(int n)
{
  if (n == 0)
    return 0;
  else if (n == 1)
    return 1;
  else
    return fib(n-1) + fib(n-2);
}

アセンブリ生成

以下のコマンドでアセンブリ言語を出力できる(出力は fib.s という名前のファイル)。最適化をしない(=極力ソースコードの表現のままのアセンブリを出力)場合は -O0 をつける。

$ clang -O0 -S -target mips -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -mcpu=mips32 fib.c

生成結果

	.text
	.abicalls
	.option	pic0
	.section	.mdebug.abi32,"",@progbits
	.nan	legacy
	.text
	.file	"fib.c"
	.globl	fib                             # -- Begin function fib
	.p2align	2
	.type	fib,@function
	.set	nomicromips
	.set	nomips16
	.ent	fib
fib:                                    # @fib
	.frame	$fp,40,$ra
	.mask 	0xc0000000,-4
	.fmask	0x00000000,0
	.set	noreorder
	.set	nomacro
	.set	noat
# %bb.0:
	addiu	$sp, $sp, -40
	sw	$ra, 36($sp)                    # 4-byte Folded Spill
	sw	$fp, 32($sp)                    # 4-byte Folded Spill
	move	$fp, $sp
	sw	$4, 24($fp)
	lw	$1, 24($fp)
	bnez	$1, $BB0_3
	nop
# %bb.1:
	j	$BB0_2
	nop
$BB0_2:
	sw	$zero, 28($fp)
	j	$BB0_7
	nop
$BB0_3:
	lw	$1, 24($fp)
	addiu	$2, $zero, 1
	bne	$1, $2, $BB0_6
	nop
# %bb.4:
	j	$BB0_5
	nop
$BB0_5:
	addiu	$1, $zero, 1
	sw	$1, 28($fp)
	j	$BB0_7
	nop
$BB0_6:
	lw	$1, 24($fp)
	addiu	$4, $1, -1
	jal	fib
	nop
	lw	$1, 24($fp)
	addiu	$4, $1, -2
	sw	$2, 20($fp)                     # 4-byte Folded Spill
	jal	fib
	nop
	lw	$1, 20($fp)                     # 4-byte Folded Reload
	addu	$2, $1, $2
	sw	$2, 28($fp)
	j	$BB0_7
	nop
$BB0_7:
	lw	$2, 28($fp)
	move	$sp, $fp
	lw	$fp, 32($sp)                    # 4-byte Folded Reload
	lw	$ra, 36($sp)                    # 4-byte Folded Reload
	addiu	$sp, $sp, 40
	jr	$ra
	nop
	.set	at
	.set	macro
	.set	reorder
	.end	fib
$func_end0:
	.size	fib, ($func_end0)-fib
                                        # -- End function
	.ident	"clang version 11.0.0 (https://github.com/msys2/MSYS2-packages ca391a3660d17cdee1e94d5afd2e72a4f750cddb)"
	.section	".note.GNU-stack","",@progbits
	.addrsig
	.addrsig_sym fib
	.text

結果の見方

  • BB は Basic Block(基本ブロック)を意味している。
    • 一つの入り口(すなわち、内部のコードが他のコードの分岐先になっていない)と一つの出口を持ち、内部に分岐を含まないコードを指す。(Wikipedia
  • スタック領域に 40 バイト確保する(実際に使っているのは 20 バイト)
  • move 命令などはアセンブリで許されている汎用表現で、この後機械語にアセンブルするときには実在する命令に置き換えられる。
  • MIPS には push, pop などスタックを操作する命令はないので、スタック領域に対して load word (lw), store word (sw) している。
  • $fp の値は $sp と同じになっている。
  • MIPS は遅延スロットを1つもつので、分岐命令の次の命令まで分岐前に実行される。上のアセンブリでは nop 命令が置かれている。
  • $BB0_6jal fib が2回呼ばれていて、これらは fib(n-1)fib(n-2) にそれぞれ対応している。

関数の呼び出し回数とスタック

この時、fib() 関数が呼び出された回数を調べてみよう。デバッガ(gdb)を使うと調べることができる。本記事では fib(10) を計算したときの様子を示す。

$ gdb a.exe
GNU gdb (GDB) 11.1
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-msys".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Traceback (most recent call last):
  File "<string>", line 3, in <module>
ModuleNotFoundError: No module named 'libstdcxx'
/etc/gdbinit:6: Error in sourced command file:
Error while executing Python code.
Reading symbols from a.exe...
(gdb) b fib
Breakpoint 1 at 0x10040108a
(gdb) ignore 1 100000
Will ignore next 100000 crossings of breakpoint 1.
(gdb) r
Starting program: /path/to/a.exe
[New Thread 6748.0x4874]
[New Thread 6748.0x3e68]
[New Thread 6748.0x2534]
c: 55
[Thread 6748.0x5c20 exited with code 0]
[Thread 6748.0x2534 exited with code 0]
[Thread 6748.0x4874 exited with code 0]
[Inferior 1 (process 6748) exited normally]
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000010040108a <fib+10>
        breakpoint already hit 177 times
        ignore next 99823 hits
(gdb) q

上記の結果から、fib(10) の計算に177回 fib() を呼び出していることがわかる。

プログラムそれ自体から、 fig(k) の呼び出し回数は fib(k+1)fib(k+2) の呼び出し回数の和である。fib(k) の呼び出し回数を g(k) と書くことにすれば、g(k) = g(k+1) + g(k+2) であり、これはフィボナッチ数列と類似の構造を持っている。fib() 呼び出し回数の総数は k について和をとればよい。fib(1)fib(0) を呼ばないことに注意すれば、 fib(n) を得るためには fib(k)k=0,...,n まで足してさらに fib(n-1) を加えた回数と一致することがわかるはずだ。

また、関数呼び出しの深さは fib(n) を考えるときに fib(n-1) を計算するパスが最も深くなる。そして、これは fib(1) にたどり着くまでネストするので最大で n-1 の深さとなる。上のコードでは1回の呼び出しに40バイトのスタックが必要なので、40 × (n-1) のスタック領域が少なくとも必要で、大きな n について計算しようとするとスタック領域を使いつくしてプログラムがエラーとなる(スタックオーバーフローという。実際にやってみよう)。

上記をちゃんと理解すれば、とても非効率なことをしていることがわかると思う。(整数の足し算しかしないのに、答えの数値よりも多い回数の関数呼び出しが必要。)

知っておくとよいかもしれないこと

この記事で紹介する MIPS アセンブリ生成方法はクロスコンパイルというもので、標準ライブラリに含まれる関数などを呼ぶとエラーになる場合がある。実際に実行したいわけでなければ、そのような関数は適当なプロトタイプ宣言だけしておいてコンパイルすればよい。例えば、printf のプロトタイプは以下のように書いておけばよい。

int printf(const char *, ...);

最適化 (-O2) するとどうなるか?

コンパイラはプロセッサで実行したときに適した(例えば、高速になるように、など)プログラムを出力するための最適化機能が実装されていて、それを使うと簡単に10倍くらいプログラムの性能が変わったりする。

標準的な最適化である -O2 をつけた時に出力結果がどう変わるか見てみよう。

$ clang -O2 -S -target mips -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -mcpu=mips32 fib.c

この時生成結果は以下のようになる。

        .text
        .abicalls
        .option pic0
        .section        .mdebug.abi32,"",@progbits
        .nan    legacy
        .text
        .file   "fib.c"
        .globl  fib                             # -- Begin function fib
        .p2align        2
        .type   fib,@function
        .set    nomicromips
        .set    nomips16
        .ent    fib
fib:                                    # @fib
        .frame  $fp,32,$ra
        .mask   0xc0030000,-4
        .fmask  0x00000000,0
        .set    noreorder
        .set    nomacro
        .set    noat
# %bb.0:
        addiu   $sp, $sp, -32
        sw      $ra, 28($sp)                    # 4-byte Folded Spill
        sw      $fp, 24($sp)                    # 4-byte Folded Spill
        sw      $17, 20($sp)                    # 4-byte Folded Spill
        sw      $16, 16($sp)                    # 4-byte Folded Spill
        move    $fp, $sp
        move    $16, $4
        sltiu   $1, $16, 2
        bnez    $1, $BB0_2
        addiu   $17, $zero, 0
$BB0_1:                                 # =>This Inner Loop Header: Depth=1
        jal     fib
        addiu   $4, $16, -1
        addiu   $16, $16, -2
        sltiu   $1, $16, 2
        beqz    $1, $BB0_1
        addu    $17, $2, $17
$BB0_2:
        addu    $2, $16, $17
        move    $sp, $fp
        lw      $16, 16($sp)                    # 4-byte Folded Reload
        lw      $17, 20($sp)                    # 4-byte Folded Reload
        lw      $fp, 24($sp)                    # 4-byte Folded Reload
        lw      $ra, 28($sp)                    # 4-byte Folded Reload
        jr      $ra
        addiu   $sp, $sp, 32
        .set    at
        .set    macro
        .set    reorder
        .end    fib
$func_end0:
        .size   fib, ($func_end0)-fib
                                        # -- End function
        .ident  "clang version 11.0.0 (https://github.com/msys2/MSYS2-packages ca391a3660d17cdee1e94d5afd2e72a4f750cddb)"
        .section        ".note.GNU-stack","",@progbits
        .addrsig
        .text
  • $BB0_1 をみると、jal fib は1行しかでておらず、 beqz $1, $BB0_1 により $BB0_1 が繰り返し実行されることがわかる。
    • この再帰呼び出しに着目すると、1回目は fib(n-1) を呼び出すが、2回目は fib(n-3) を呼んでおり、元の実装と異なることがわかる。
    • これは、fib(n) = fib(n-1) + fib(n-2) と計算するか、fib(n) = fib(n-1) + fib(n-3) + ... と計算するかの違い。つまり、関数内部の構造を解釈して等価な結果を出力する関数に書き換えていることがわかる。
    • さらに、fib(0) を再帰的に呼び出す処理が排除されている。fib(0) = 0 なのでこれを呼び出す必要はない。

関数呼び出し回数

この最適化により呼び出し回数がどうなるかを考えてみよう。fib(n-2) の呼び出しがなくなっているため、呼び出し回数は fib(k)k=0,...,n-1 までの和 + 1 に抑えられる。(fib(0) の再帰呼び出しがなくなった効果は fib(2) の呼び出し回数と一致する。)

$ gdb a.exe
GNU gdb (GDB) 11.1
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-msys".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Traceback (most recent call last):
  File "<string>", line 3, in <module>
ModuleNotFoundError: No module named 'libstdcxx'
/etc/gdbinit:6: Error in sourced command file:
Error while executing Python code.
Reading symbols from a.exe...
(gdb) b fib
Breakpoint 1 at 0x10040108c
(gdb) ignore 1 100000
Will ignore next 100000 crossings of breakpoint 1.
(gdb) r
Starting program: /path/to/a.exe
[New Thread 16864.0x2e10]
[New Thread 16864.0x62f8]
[New Thread 16864.0x49b8]
c: 55
[Thread 16864.0x5bf4 exited with code 0]
[Thread 16864.0x49b8 exited with code 0]
[Thread 16864.0x62f8 exited with code 0]
[Inferior 1 (process 16864) exited normally]
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000010040108c <fib+12>
        breakpoint already hit 89 times
        ignore next 99911 hits
(gdb) q

元の約半分の89回の fib() 呼び出しで済んでいることがわかる。

別のコンパイラ(GCC)だとどうなるか?

GCC の場合は MIPS のクロスコンパイル環境を用意するのがやや面倒なので割愛するが、なんと fib(10) を計算するための fib() の呼び出し回数は2回になる。これは tail call optimization というコンパイラ技術を使って、関数呼び出しを関数内の反復処理に置き換える最適化が行われるからである。

$ gcc -O2 fib.c main.c
$ gdb a.exe
GNU gdb (GDB) 11.1
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-msys".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Traceback (most recent call last):
  File "<string>", line 3, in <module>
ModuleNotFoundError: No module named 'libstdcxx'
/etc/gdbinit:6: Error in sourced command file:
Error while executing Python code.
Reading symbols from a.exe...
(gdb) b fib
Breakpoint 1 at 0x100401090
(gdb) ignore 1 100000
Will ignore next 100000 crossings of breakpoint 1.
(gdb) r
Starting program: /path/to/a.exe
[New Thread 15940.0x7004]
[New Thread 15940.0x74f8]
[New Thread 15940.0x6bdc]
c: 55
[Thread 15940.0x6510 exited with code 0]
[Thread 15940.0x6bdc exited with code 0]
[Thread 15940.0x74f8 exited with code 0]
[Inferior 1 (process 15940) exited normally]
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000100401090 <fib+16>
        breakpoint already hit 2 times
        ignore next 99998 hits
(gdb) q

このように、同じプログラムでも最適化の有無、コンパイラの違いなどによって生成されるプログラムが変わり、使用するコンピュータのリソース(スタック領域など)が大きく変わる。

普段意識せず使っているコンピュータの発展はこのような技術の積み重ねによって支えられていることがわかると思う。興味を持った人はぜひコンピュータ・アーキテクチャ、コンパイラなどに親しんでほしい。

MacOS + Vim + im_control.vim + im-select の設定方法

MacVim で <C-^> による IME 制御が効いていないみたいなので、im-select を使って解決したよ、という話。

使うもの

  • MacVim (Homebrew cask の “macvim”)
  • im_control.vim
    https://github.com/fuenor/im_control.vim
  • im-select
    https://github.com/daipeihust/im-select

手順

  1. vim + im_control.vim をインストール
  2. im-select のインストール (/usr/local/bin/ に配置)
  3. .vimrc に以下のように書く
if (g:os == "Darwin")
  let IM_CtrlMode = 1
  inoremap <silent> <C-j> <C-r>=IMState('FixMode')<CR>
  function! IMCtrl(cmd)
    let cmd = a:cmd
    if cmd == 'On'
      let res = system('im-select com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese > /dev/null 2>&1')
    elseif cmd == 'Off'
      let res = system('im-select com.apple.keylayout.ABC > /dev/null 2>&1')
    elseif cmd == 'Toggle' " dummy
      let res = system('im-select > /dev/null 2>&1')
    endif
    return ''
  endfunction
endif

IM_CtrlMode を 1 にしているので、IMCtrl()は ‘On’ か ‘Off’ を引数に呼ばれるので ‘Toggle’ は真面目に実装する必要はない。

Docker コンテナでホストと同じユーザ・グループを利用する方法

/etc/passwd, /etc/group. /etc/shadow でユーザとグループを管理している場合の話。
忘れそうだったのでメモ。

今回、JupyterHub の構築をしていて、docker イメージ使うと簡単そうだったので利用したのだけど、ホストのユーザが JupyterHub を使えるようにしたかったので、いろいろ試行錯誤した。

結論としては以下のようにするのがよさそう。

  • ユーザ管理(追加、削除など)はホスト側で行う
  • Docker コンテナはホストの /etc/{passwd,group,shadow} を Read-Only で参照
    docker run -v src:dst:ro を使う
  • ユーザ管理コマンドで /etc/{passwd,group,shadow} の i-node が変わっても良いようにする
    → ディレクトリ単位でバインドする、ただし、Docker コンテナの /etc をホストの /etc で上書きしてしまうのは困るので、どちらの内容が見えるかはファイルごとに制御したい

そのために、

  • /import/etc/ を作成する
  • イメージにもともとある /etc/{passwd,group,shadow}/import/etc/ に移動する
  • cd /etc; ln -s /import/etc/{passwd,group,shadow} してシンボリックリンクする
  • docker run -v /etc:/import/etc:ro -v /home:/home としてディレクトリごとマウントする

多分、これでOK。

プレゼン動画作りのメモ

Windows 10 の場合

  • PDF またはパワポをフルスクリーン表示して、Xbox Game Gear のキャプチャ機能で録画
    • 途中で噛んだりしても後で編集すればいいので続けて取った方が結果早い
  • Windows フォトや Adobe Premiere Pro などでトリミング&編集
  • 書き出した後、HandBrake などで再エンコードして容量削減
    • CPU でエンコードしたほうがファイルサイズは小さくなる

Adobe Premiere Pro の使い方

  • アセンブリ > メディアブラウザー から素材となるメディア(動画)を 右クリック > 読み込み
  • プロジェクト:○○ からメディアを タイムライン にドラッグして配置
  • エフェクト > オーディオエフェクト > ノイズリダクション / レストレーション から クロマノイズ除去リバーブを除去 を各メディアに適用
  • トリミングするときは、レーザーツール を使ってメディアを分割し、リップル削除 で削除と同時にタイムラインを左詰め
  • 動画の書き出しは、アセンブリ で編集済みの項目を選択した状態で ファイル > 書き出し > メディア
    • 設定は現状よくわかっていないので、H.264 でハードウェアエンコードできる設定を使用。

便利なショートカット

  • 再生、順方向加減速:L
  • 停止:K
  • 逆再生、逆方向加減速:J
  • カーソル位置でレーザーツール(編集点の追加):Ctrl + K
  • クリップ選択、移動:Ctrl + ↑ or Ctrl + ↓
  • リップル削除:Shift + Delete

参考資料

Petalinux v2018.3 をインストールするときのメモ

  • Ubuntu の場合は 16.04
  • sudo apt-get install -y gcc git make net-tools libncurses5-dev tftpd zlib1g-dev libssl-dev flex bison libselinux1 gnupg wget diffstat chrpath socat xterm autoconf libtool tar unzip texinfo zlib1g-dev gcc-multilib build-essential libsdl1.2-dev libglib2.0-dev zlib1g:i386 screen pax gzip
  • 上記だけではダメで、sudo apt-get install gawk が必要

食器洗い乾燥機を自力で設置した記録

目次

  • 必要なものの調査
  • 棚の設置
  • 分岐水栓の取り付け
  • 食器洗い乾燥機の設置

必要なものの調査

まず、購入する食器洗い乾燥機はパナソニックの NP-TA2 にすることにした。約4万5千円。

これを設置する場所をキッチンの中で探したが、そのままでは置くことができない。そこで台をキッチンに設置し、その上に乗せることにした。必要な面積はおおよそ 55 cm × 35 cm なので、アイリスオーヤマのメタルラックを利用するのが良さそう、という結論に。ちょうどよいサイズの棚板を扱っている。これに 15 cm のポールを2本取り付け、残りの2本分はキッチンの台に乗せることにした。このポールは高さを微調節することができ、水平をとるのも容易だった。平らな方が食洗機を載せやすいので硬質クリアシートを合わせて購入。棚板1枚で使うので若干強度面の不安を感じたのでサイドバーを買っておいたがこれは不要だったかもしれない。約3千円。

続いて、食洗器を水道につなぐための部品として分岐水栓が必要。我が家では TKGG31E という TOTO の蛇口が取り付けられていたので、これに合う部品をパナソニックのサイトから検索。すると CB-SSH8 という部品が適合することが分かったので、これを購入。約1万円。これを取り付けるには 2.5 mm の六角レンチとサイズの違う2つのスパナが必要。

棚の設置

棚は事前にキッチン周りの寸法を良く図っておいたおかげでかなり狙い通りになった。電源ケーブルと給水ホースを通す隙間も取れてよかったと思う。

分岐水栓の取り付け

中に説明書が入っているので、その通りにすればよい。栓を出す向きは360度自由に決めることができるようになっている(実はこれがかなり不安だった)。

水の栓を止めてから作業しないといけないが、今回は量水器ボックスの栓を締めて行った。

普通に作業するだけなら10分くらいでできると思うが、作業中にレンチが足りないことが分かって買いに走ったので少し時間がかかった。

食器洗い乾燥機の設置

重いという点を除けば特に苦労することはなかった。

給水ホース、排水ホースをそれぞれ接続し、電源を取ってアースをとるだけ。

アースは残念ながら冷蔵庫と食器棚で埋もれてしまっていたので、ドキドキしながらもシンクに接続しておいた。何かあった時は自己責任・・・。ただ、食洗器自体を台の上に載せているのと、台所マットを利用している点、一応シンクにアースを接続していることを考えると、感電リスクはそれほど高くはないと判断している。

設置を依頼したらかなり費用が掛かっていたと思うので、自力でできたのは良かったと思っている。 なにより妻が喜んでいるので良かった。 賃貸なので、退去するときに水栓を戻さないといけないのが億劫だけど。