ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

Xcode 10 betaにおけるSource ControlとAsset Catalogの新機能のお話

f:id:kamui_project_tony:20180701181028p:plain

はじめに

ども、かむいです
気がついたら2018年も7月に突入してますね(´・ω・`)
投稿がご無沙汰だったので、先月弊社で登壇したイベントがあったので、その話について書きたいと思います

WWDC18報告会 in 弊社

dmmcj.connpass.com

当日はまさかの申込数約200人と大変注目を浴びたイベントとなりましたmm
登壇1番手として変なプレッシャーもあったり(´・ω・`)

そんな中で発表した内容がこちら
https://speakerdeck.com/tony1224/wwdc18-cherry-pick-xcode-10-beta

WWDC18 cherry-pick Xcode 10 beta

というお題目で発表しました
ただXcode絡みのテーマのセッションは複数あったので、その中から

  • Source Control Workflows in Xcode

developer.apple.com

  • Optimizing App Assets

developer.apple.com

の2つについて話しました

Source Control Workflows in Xcode

XcodeにあるSource Control機能がパワーアップしたよ!というお話
具体的には

- Git上の差分をエディタ上に表示
    - 色線で変更行, 競合行が色分けで表示される
- GitLab, Bitbucketもサポート対象に追加
- pullボタンで--rebaseオプションを設定可能に
    - チェックボックスの初期値設定はPreferenceより変更可
- Xcode上でSSHキーの登録, 更新ができるようになった

ていうもの
競合箇所は競合した行のコミッター情報とか、競合した箇所の修正対応(こちらの変更を取り消す, 競合差分を見る)とかもできたりしてより便利に
ちなみにXcodeの3大競合あるあるファイル

- .storyboard
- .xib
- .pbxproj

ではこの機能に反映されては無いようでした
ただここは素直にPull時に指摘される箇所ではあるので、その時点で解消できれば問題ありませんね

ちなみに僕のプロジェクトでは .pbxproj の競合についてはXcodeGenで対処する予定です
まだ実際に使い倒してみての選定ではないため、完全に熟知したタイミングで僕も記事を書こうと思っています

qiita.com

Optimizing For Assets

Asset Catalogの中がパワーアップしたよ!というお話
ただiOS12における新機能はセッションで話された4項目のうちの1項目のみだったので、その項目のみ話させてもらいました
(10分LTだったので意図的に省いてしまったのもありますが)

ちなみにXcodeの話ちゃうやんけってツッコミがあるのですが、Asset Catalogだしまぁいいよねて魂胆です

Session Talk1: Apple Deep Pixcel Imgae Compression

ていうのが今回iOS12から追加されました
Asset Catalogに格納されているリソースをiOS12端末にインストールする場合、これまでと違い最大20%※のサイズダウンを実現させてまっせというもの
※色や画像など、全てをAsset Catalogでまかなっていた場合による差分値なのではという推測です
(セッションでもここについては言及してませんでしたが、自分のアプリで検証済)
そのため画像のみの管理で20%のサイズダウンを発揮できるものでは無さそうです

iOSerの方々はどのあたりまでAsset Catalogを活用されてますでしょうか?

僕は個人アプリの場合

  • 色: 🙅‍♂️ColorUtil.swift とか作って定数で管理。Asset CatalogのColor SetもiOS11からしか使えないというバージョン都合もあったり
  • 動画/音源: 🙅‍♀️ Data Setで入れられるんですよね。まだ対応できてませんでした(´・ω・`)

dev.classmethod.jp

  • 画像: 🙆‍♂️ ただVector使えてなかったり解像度も2xのみだったりしてました

結論: 20%のサイズダウンは出来ずorz

なので諸々のリソースをAsset Catalogに突っ込んでいくと、今後は良いことがあるよという話でした
色はiOS10をカバーするかどうか問題が残るので、チームで相談してみても良いかもしれません

Session Talk3: Cataloging - Namespace

f:id:kamui_project_tony:20180701175919p:plain

最後にこちら発表では話せなかったネタを1つ
Swiftでは相変わらず名前空間が無いよ状態なんですが、Asset Catalog上では名前空間は使えるんですよ奥さんってのを発表されてました
R.swiftも使ってこれを活かせるとかなり便利だったので、僕のプロジェクトでもこれを活かしていく予定です

実装方法もすでにまとめられてる方がいましたので紹介させて頂きます

qiita.com

さいごに

とにもかくにも、発表疲れました(´・ω・`)
当面は勉強会で登壇予定はないものの、8-9月に行われるiOSDC2018にCfP出しました
当選されることがあれば、次回はそのあたりかなと思ってます

ただ申込数半端ないって!多すぎて締め切りのアディッショナルタイムが8時間やもん!そんなん出来ひんやん普通!

なので落ちてもリジェクトコンにすら受かるか怪しい
その場合は技術書典5に申し込んだので、そこで改めて本の形で発表したいッス

え、そこも落ちるの?(´・ω・`)
泣いちゃうよ?(´・ω・`)

Dockerハンズオンをやってみた - Kubernetes輪読会#0

どうも、Nintendo Switch版の斑鳩が配信されるの楽しみな tobi462(過去記事) です。

斑鳩はSTGとしての面白さは当然のこと、硬派なストーリー設定も素晴らしく・・・おっとこれを語りだすと長くなるのでやめておきましょう。

Kubernetes輪読会

さて、ペンギン村のエンジニア数人で Kubernetes の輪読会をすることになりました。

輪読会の対象となる技術書は以下です。

Kubernetes は Google が公開した OSS のコンテナオーケストレーションツールですが、GCP(Google Cloud Platform) に続いて、AWS(Amazon Web Service)も公式にサポートし、実質的にコンテナオーケストレーションツールの標準として君臨したと言えます。

オーケストレーションとは、負荷に応じてコンテナの数を変更して負荷分散を図ったり、アプリケーションの差し替えを用意にしてくれる仕組みのようです。つまり、コンテナ単体では面倒だったことをいろいろサポートしてくれるようです。(このあたりの理解は怪しいです、なにせこれから輪読会を通じて学ぶのですから)

始動 #0

さて、そんなわけで始めることになったのですが、ペンギン村のエンジニアはモバイルに属しているエンジニアが殆どなので、実のところ Kubenetes 以前に Docker についてもあまり馴染みがありません。

そんなわけで初回は #0 ということで、Docker の勉強会的なものをやることになりました。さすがに Kubernetes などのオーケストレーションを理解する前に Docker などのコンテナ技術の基礎は必須だよね、という流れです。

今回は Docker についてある程度知識がある tobi462(過去記事) がハンズオンを開催することで、 Docker やコンテナ技術の基本的なところを理解しあおうという事になりました。

コワーキングスペース

というわけで初回は以下のコワーキングスペースを利用させていただきました。

co-forest.com

コワーキングスペースによっては静かで喋りづらい雰囲気もあったりするのですが、ここは「会話OK」と紹介されていてディスカッションもしやすそうだったので選ばせていただきました。

打ち合わせっぽい会話が聞こえてきたりもしますが、決してガヤガヤしすぎて集中できないというレベルではないのでなかなかバランスの取れた場所なのかなと思います。以前は Swift の TDD を検証するペアプロでも利用させていただいており、こういったディスカッションしつつ何かをやりたいときには絶好の場所だなと感じています。

今回、#0 を開催して結構気に入ったので、今後の輪読会もここで開催していこうかという話になっています 。(他にも良い場所があったら是非教えてください)

Docker ハンズオン

さて、そんなわけで今回のメインコンテンツである Docker ハンズオンです。

外部ディスプレイに映して、それを見ながらやってもらおうと考えていたのですが、ディスプレイケーブルを忘れてしまったので Slack に随時書き込みながら進める形にしました。

実際には口頭で補足説明もしながらだったので、文章にすると多少情報は欠けてしまうのですが、せっかくなので記事に書かせていただこうと思います。

インストール

最初に以下をインストールしてもらいました。

実のところ Docker のハンズオンとしては後半2つは不要なのですが、ハイパーバイザ型とコンテナ型で起動速度の違いを実感してもらおうという意図と、便利だからせっかくだし入れちゃおうぜみたいな感じで一緒にインストールしてもらいました(おぃ

Docker 入門

講師:
 まずは伝統の Hello world からにしましょうか。ターミナルで以下のコマンドを叩いてみてください。

$ docker run hello-world

講師:
 なにやらダウンロードされた後に Hello World 的なものが表示されたと思います。 docker run は指定したイメージをコンテナとして起動するコマンドで、今回は hello-world というイメージ名を指定しています。Docker は指定したイメージがなければ自動的に Dockerレジストリからイメージをダウンロードして実行してくれる仕組みになっています。

 さて、今回の起動したコンテナを見てみましょう。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
1b0a41707472        hello-world         "/hello"            2 minutes ago       Exited (0) 2 minutes ago                       hungry_lovelace

講師:
 -a は終了したコンテナも表示するというオプションで、今回のように Hello world を出力した後に終了するコンテナを表示する場合には指定が必要になります。

 出力されたカラムの意味は以下のとおりです。

カラム 説明
CONTAINER ID コンテナの一意なIDで、自動的に振られる
IMAGE どのイメージをもとにして生成されたコンテナか
COMMAND なんのコマンドが実行されたか
CREATED いつ生成されたか
STATUS どういう状態か。今回は Exited で終了していることがわかる
PORTS ポートの設定が表示されるが、今回は関係ないので表示なし
NAMES コンテナの名前。生成時に指定することも出来るが、そうでなければ自動で振られる。コンテナIDの代わりに使用できる

参加者:
 CONTAINER IDNAMES は起動ごとに変わってるみたいですね。

講師:
 そうですね、同じ Docker Image からいくつでもコンテナを作れるようになっています。これはオブジェクト指向における クラスオブジェクト の関係と考えると分かりやすいかもしれません。

講師:
 せっかくのでイメージの一覧も見てみましょう。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              e38bc07ac18e        6 weeks ago         1.85kB

 それぞれのカラムの意味は以下のとおりです。

カラム 説明
REPOSITORY イメージ名
TAG イメージにつけられるタグで、例えばバージョンを指定できたりします。例えば Swift というイメージがあって、 TAG としては 4.04.1 などに複数あるイメージです。つまりタグを使ってバージョン違いも共存可能ということです。
IMAGE ID イメージの一意なID
CREATED いつ生成されたか
SIZE 最終的なイメージのサイズです。最終的というところがポイントで、ベースのイメージは共有されるようになっています。Gitのコミットグラフにおける親コミットをイメージいただけると分かりやすいかもしれません。

講師:
 さて次にイメージの削除を試してみましょう。

$ docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container a1ad796bab25 is using its referenced image e38bc07ac18e

 エラーになったと思います。これはそのイメージから作られたコンテナが存在する場合には、警告としてエラーになるようになっているためです。ちなみに rmi の最後の iimage の略だと思います、少なくともそう考えておくと分かりやすいです。

講師:
 ではコンテナを先に削除してみましょう。コンテナのIDの調べ方は・・・ docker ps でしたね、自分の環境にあわせて変更してください。

$ docker rm a1ad796bab25

 何回か起動した場合は複数回必要になるので注意してください。

参加者:
 以下のように複数指定もできるのですね。

$ docker rm 881770fa6d68 6ee4de437fa3
881770fa6d68
6ee4de437fa3

講師:
 全部消したら、先程の rmi をもう一度試してみましょう。今度は消せるはずです。

 うまくいきましたね?

参加者:
 はーい!

講師:
 ところで終了したコンテナを一気に消したいと思う機会は多いはずです。その場合は以下のようにすることで一気に削除できます。

$ docker rm $(docker ps -aq)

 どこかのタイミングで試してみると良いでしょう。-q は コンテナID のみを表示するオプションなので、それで列挙して一気に指定するイメージです。

講師:
 ここからVagrantに移りますが、その前に以下のコマンドでUbuntuを落としておきましょう。

$ docker pull ubuntu

 pull はイメージをDockerレジストリからダウンロードするコマンドです。docker images でローカルにダウンロードされた一覧が見れるはずです。

 試しにTagを指定して pull もしてみましょう。

$ docker pull ubuntu:16.04

 : のあとに指定してるのが Tag で、 docker images でも表示されていたものです。Tag を省略した場合は、 latest というタグが使用されるようになっています。

$ docker images | grep ubuntu
ubuntu              16.04                  0b1edfbffd27        4 weeks ago         113MB
ubuntu              latest                 452a96d81c30        4 weeks ago         79.6MB

 最初にタグなしで pull したものは latest というタグのものがDLされているのがわかるかと思います。

参加者:
 確かに。 Using default tag: latest

<ここで軽くコーヒーブレイク>

Vagrant も触ってみる(ハイパーバイザ型の仮想化を試してみる)

講師:
 さて、コーヒーブレイクが済んだところで(?)Vagrant も触っていきましょう。まずはワークディレクトリを作りましょう。(どこでも良いです

$ mkdir -p ~/Vagrant/Ubuntu_1604

 ワークディレクトリに移動後、以下のコマンドを叩いてみましょう。

$ vagrant init bento/ubuntu-16.04

 そういえば Vagrant の説明を何もしてませんでしたね・・・

参加者:
 Vagrant入門:https://thinkit.co.jp/story/2015/03/19/5740

講師:
 Vagrant は雑に言うと、 仮想環境をCLIで操作できるようにするものです。

 例えば Mac上では、VirtualBox というOracleが提供しているハイパーバイザ型仮想化ソフトウェアがありますが、VagrantはそれをCLIで操ることの出来るものです。

 では以下のコマンドを叩いてVMを起動してみましょう。

$ vagrant up

 ちなみに VMVirtual Machine の略で、すなわち「仮想マシン」です。

 説明が遅くなりましたが、 init したタイミングで Vagrantfile というものが作成されています。

$ ls
Vagrantfile

 これは VM を生成するための料理本(クックブック)のようなもので、ここに設定された内容に従ってVMが立ち上がるようになっています。

 さて、ここで VirtualBox を起動してみましょう。

<という算段だっただが、ダウンロードに時間がかかりすぎたので講師の画面を見せることに>

講師:
 さて、Vagrant up は時間がかかるので先にスクリーンショットを張ってしまいます。

f:id:yu_dotnet2004:20180527205041p:plain

 仮想マシンが Runningになっているのが分かると思います。今回は Vagrant から操作して起動しましたが、VirtualBox の画面から同じことをすることも可能です。しかし、CLIで簡単に VM を立ち上げられるようになっていることが分かると思います。

 以下のようにすることで起動したVMに ssh でログインすることが出来るようになっています。

$ vagrant ssh

参加者:
 なるほど、最近の仮想化ってこんなに簡単になってるんですね。

Docker に戻る(ハイパーバイザ型とコンテナ型の違いについて)

講師:
 では、同じ Ubuntu 16.04 をDockerで起動してみましょう。

$ docker run ubuntu:16.04

参加者:
 は、や、い

<ここからハイパーバイザ型とコンテナ仮想型の違いをメタファを使っていろいろ解説した・・・が、何を喋ったか覚えてないので覚えてる範囲で書き起こし>

講師:
 これがコンテナ型仮想化の強い点で、起動が圧倒的に速いのです。ハイパーバイザ型はハードウェアをエミュレーションするので、実行時のオーバーヘッドが大きいのに比べて、コンテナ型はOSの機能を使ってシミュレートしているイメージで実際にはプロセスとして実行されるようになっています。

 Androidの開発を行った経験がある方は、Androidのエミュレータがすごく重いと感じたことがあると思います。これはエミュレーション、すなわちハードウェアレベルで仮想化しているためで、エミュレータ上で動く Android OS は自身が物理ハードウェアで動いているのかエミュレータで動いているのか区別がつかないのです。

 そう、Matrix のように。

参加者:
 ・・・。

講師:
 はい、面白くなかったですねw

参加者:
 いや、普通になるほどって納得してました。

講師:
 iOS のシミュレータは軽いと感じたことはあると思いますが、これはハードウェアエミュレーションしているわけではなく、シミュレーションで近似値を得ようとしているだけなので計算量が少ないためです。ちょっと強引ですが天気予報みたいなものかもしれません、あれもシミュレーションで近似値を予測している点では似ています。

 そんなわけでハイパーバイザ型はAndroidのエミュレータのように実行時のオーバーヘッドが大きいのです。それに比べてコンテナ型はiOSのシミュレータのようにオーバーヘッドが少ないわけですね。

 コンテナ型仮想化はこのオーバーヘッドの少なさ、起動の速さが流行った要因の一つのような気がします。例えば、AWS Lambda に代表される FaaS はトリガーにより発火するタイミングでコンテナを生成し、その中で関数を実行することで完全な独立性を確保しています。Heroku に代表される PaaS でも裏ではコンテナ型仮想化技術を活用しているケースも多いようです。

 ただ、ハイパーバイザ型仮想化にメリットが無いわけではなく、ホストOSとは異なるOSをインストールすることも出来るという利点もあります。

自分で Docker Image を作る

講師:
 さて、今までは既存の Image を使いましたが、そろそろ自分でもイメージを作りたいですよね?

 まずは作業ディレクトリを作りましょう(どこでもOKです

$ mkdir -p ~/Desktop/DockerWork

 Dockerfile という名前で以下のファイルを作ってみましょう。

FROM ubuntu:16.04

CMD [ "date" ]

 ファイルが作り終わったらイメージをビルドしてみましょう。

$ docker build -t ubuntu_date .

参加者:
 なにやらエラーになりました・・・

講師:  あー、最後の . を忘れないようにしてください。build は最後に . が必要な点に注意しましょう。

講師:
 成功すれば自分がビルドしたイメージが images で表示されるはずですっ!

$ docker images
REPOSITORY          TAG                    IMAGE ID            CREATED              SIZE
ubuntu_date         latest                 4f8b6dc9e250        About a minute ago   113MB

 今回は FROM でベースイメージとして ubuntu:16.04 を指定して、その上に変更をしている感じです。ちなみに ubuntu:16.04 は先程ダウンロードしたはずなので、今回はダウンロードが必要なかったはずです。

 以下のコマンドで確認してみると対応関係が分かりやすいかもしれません。

$ docker images | grep ubuntu
ubuntu_date         latest                 4f8b6dc9e250        6 minutes ago       113MB
ubuntu              16.04                  0b1edfbffd27        4 weeks ago         113MB
ubuntu              latest                 452a96d81c30        4 weeks ago         79.6MB

 では、自分で丹精込めてビルドした Image から コンテナ を起動してみましょう!

$ docker run ubuntu_date
Sat May 26 06:28:17 UTC 2018

参加者:
 これはコンテナ上の Ubuntu で date が実行されているということ?

講師:
 はい、そのとおりです!

講師:
 build でイメージを作成して、 run でイメージからコンテナを作成して起動します。

 ちなみに以下のコマンドでイメージの変更履歴が見れます。

$ docker history ubuntu_date
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
4f8b6dc9e250        10 minutes ago      /bin/sh -c #(nop)  CMD ["date"]                 0B
0b1edfbffd27        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           4 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$…   2.76kB
<missing>           4 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:592c2540de1c70763…   113MB

 Dockerfile で自分が指定した CMD ["date"] が最後の履歴として表示されているのがわかるかと思います。

<ここで参加者1名が、そのあとの用事のために離脱したので終了>

スペシャルメニュー

<せっかくなのでちょっとしたおまけメニューを用意>

cargo で Rustプロジェクトを作って、それをDockerコンテナ上でビルド・実行してみよう!

FROM rust:1.26.0

WORKDIR /usr/src/myapp

COPY . .

RUN cargo install

CMD [ "hello" ]

cargo の使い方を忘れちゃったかな? 以下で作れるよ。

$ cargo new --bin hello

参加者の感想

参加者A:
 勉強会のモチベーションが分かった
 コンテナがあればサーバーサイドできる気がしてきた

参加者B:
 ハンズオンありがとうございました!わかりやすかったです!

おわり

まぁ今回はこんな感じで、 Kubernetes 読書会 #0 - Docker勉強会、みたいなものをやりました。

実のところ事前準備するつもりが全くできず、ぶっつけ本番になってしまったのですが、そのわりには楽しんでもらえる内容でできたかな、と個人的にはわりと満足しています。

そして連載ブログにしようぜ、という話があがったのはこれをやった後だったので、情報としては欠けてしまった部分も多いかもしれません・・・が、参考になる人もいると思ったので記事にしてみました。

輪読会は続くよ

さて、次回以降はついに Kubernetes の輪読会に入っていきます。楽しみですね。

というわけで、今回は休日の輪読会というか Docker ハンズオンの内容を記事にしてみました。

ではまた。

Xcode 9 の New Build System を試す

この素晴らしきビルドシステムに祝福を。

どうも tobi462(過去記事)です。

さて、Xcode 9 から新しいビルドシステムが導入されたのは記憶に新しいですが、9.3 の時点でも Preview となっており標準のビルドシステムにはなっていません。

すでに他記事でも紹介や現状のビルドシステムとの比較がされていますが、実際に自分でやってみないと信用しない質のエンジニアなので、今回は自分で試してみた結果です。

追記(2018/05/27):
逆にビルド時間が長くなったり、そもそもエラーでビルドできないケースもあるようです。現時点では Preview 扱いなので、そのうち改善されるかもしれません。

Tl;Dr

  • 以下のパフォーマンス向上結果が得られた
    • Clean Build で 95%
    • 差分ビルドで 90%
  • Swift 4.2 のツールチェーンではビルドエラーになった
  • 自分のPJで試して問題なさそうなら積極的に使っても良さそう?

New Build System ?

What’s New in Xcode を見ると以下のように書かれています。

New in Xcode 9 – Preview of a new build system written in Swift. Currently, This system is optional but it will become the default in a future version of Xcode

  • Added a preview of a new build system written in Swift.
  • Provides higher reliability.
  • Catches many project configuration problems.
  • Improves overall build-system performance.

Note, build system performance does not include the compilers, linkers, and other tools used by the build system.

  • 将来的には標準のビルドシステムになる
  • Swift で書かれた
  • 高い信頼性
  • プロジェクト設定の多くの問題を取得
  • ビルドシステム全体のパフォーマンス向上

Swift で書かれたということは、以前は他の言語だったということでしょうが、Swift にすることでパフォーマンス向上が計れた部分もあるのでしょうか。

ちなみに Notes にかかれていますが、コンパイラやリンカなどの他ツールを利用している形になっているので、それらのパフォーマンス向上はないようです。

つまり、プロジェクト構造からビルドすべきファイルを抽出し、妥当なスケジューリングと並列性でコンパイラやリンカを実行するという部分がビルドシステムであり、そこのパフォーマンスや安定性が向上したという感じでしょうか。(あまりこの分野には詳しくないのですが)

検証環境

今回は、モダンなiOS環境を試し続けている(ことを目標としている)以下の個人リポジトリにてパフォーマンス比較を行ってみることにしました。

github.com

環境は以下のとおりです。

・Xcode 9.3
・MacBook Pro (13-inch, 2017)
・Processor 2.5 GHz Intel Core i7
・Memory 16 GB 2133 MHz LPDDR3
・macOS High Sierra 10.13.4

プロジェクトに適用する

FIle > Workspace Settings… を選び f:id:yu_dotnet2004:20180519200043p:plain

ダイアログの Build SystemNew Build System (Preview) を選択します。 f:id:yu_dotnet2004:20180519200055p:plain

Shared Workspace Settings: だけで十分かなと思ったのですが、念のため Per-User Workspace Settings: の方も変更しました。

ちなみに Shared Workspace Settings: の方を変更すると以下のファイルが作成されるようです。

xxx.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
       <key>BuildSystemType</key>
       <string>Latest</string>
</dict>
</plist>

実行速度を比較する

現状の Xcode では標準でビルド時間が出力されないので、Terminal から以下のコマンドを実行して表示されるようにします。

$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

設定すると以下のような感じで、ビルドにかかった時間が表示されるようになります。 f:id:yu_dotnet2004:20180519200119p:plain

Clean Build

まずはクリーンビルドの比較からです。5回ずつ試してみました。

既存の Standard Build System では、Clean(Cmd + Shift + K)のあとでファイルのIndex化が行われているようだったので、それが完全に終わってからビルドを実行するようにしました。

結果は以下のとおりです。

[Standard Build System]
23.425
22.945
21.240
21.006
21.196

[New Build System]
21.797
20.825
20.602
20.402
20.392

平均値で比較すると 94.7% 程度になり、少しパフォーマンスが上がっているようです。個人的に Clean Build はさほど差がでないと予想していたので、これは少し驚きました。

ちなみに New Build System ではCleanが恐ろしく速かった、というより一瞬になっていました。

俺でなきゃ見逃しちゃうね。

差分ビルド

さて期待している差分ビルドの方です。

こちらは少し雑ですが、1ファイルだけ変更を加えてビルドを再実行する形で計測してみました。

結果は以下のとおりです。

[Standard Build System]
1.492
1.528
1.462
1.472
1.473

[New Build System]
1.329
1.345
1.308
1.312
1.436

これだけ短い時間だと妥当に計測できているかは少し微妙ですが、平均値で比較すると 90.6% となり、まぁ少なくとも遅くはなっていないようです。

Swift 4.2 の Toolchain ではビルドエラー

Xcode 9.3 には Swift 4.1 までしか入ってないですが、今は Swift の 公式ページ から Swift 4.2 の開発版のツールチェーンを入手することが出来ます。(少し前までは自分でビルドするしか無かったのですが;

f:id:yu_dotnet2004:20180519200143p:plain

試しに Swift 4.2 のツールチェーンでも New Build System を試してみたのですが、これはビルドエラーとなりました。

ビルドシステムはコンパイラやリンカと密接に連携するため、このあたりは相性的なものがあるのかもしれません。Swift ツールチェーンを切り替えることが多い方は、こういった問題も起こりうると覚えておくと良いかもしれません。

まとめ

Tl;DR とほぼ同じですが;

  • 以下のパフォーマンス向上結果が得られた
    • Clean Build で 95%
    • 差分ビルドで 90%
  • New Build System では Clean が高速化して一瞬になっていた
  • Swift 4.2 のツールチェーンではビルドエラーになった
  • 自分のPJで試して問題なさそうなら積極的に使っても良さそう?

というわけで検証結果でした。

Swift エンジニアが学ぶ Rust - 0.序章

Rust はとても良い言語です、 TRust me。

ダジャレから始めるのは初めてでしょうか。Rust 勉強中の tobi462 です。

私はプログラミング言語を学ぶのが好きな畑の人間でして、最近では Rust を学んでいるのですが、これがなかなかどうして面白いのです。

Love Swift ?

おそらく私の一番得意な言語は Swift か Java なわけですが、どちらが好きかと言われれば断然 Swift です。

Haskell のような代数データ型としての enum はとても好きですし、Protocol Oriented Programming や extension による既存クラスの拡張、switch によるパターンマッチも好きです。

機能やシンタックスが多すぎて覚えるのが大変な点や、switch によるパターンマッチの読みづらさといった点、バージョンアップごとに破壊的仕様変更が入る(これほど互換性を意識しないプログラミング言語も珍しいのではと思ったりします)などの点を除けば、 概ねよく出来た言語だと感じていました。

Rust ?

そんな中、Rust という一匹のプログラミング言語に出会いました。 www.rust-lang.org

Mozilla が主導になって開発しており、ポスト C++ として期待される言語だとか、高速だとか安全な並列性だとか、なんとかかんとか。

そして他の言語では聞かないような「所有権モデル」なるものを有しているとか。

Rust の第一印象

そういうわけで Rust を軽い気持ちで学び始めたわけです。

そして感じたのは、所有権モデルの難しさです。

いえ、所有権モデルの考え方自体は難しくないのです・・・そう感じるのです。しかし、実際にコードを書いてみるとコンパイルが通せないことこの上なしです。

コンパイラはとても分かりやすいエラーメッセージを出力してくれます。曰く、ライフタイムがどうとか、借用されてるとか、ムーブされてるとか・・・。

なるほど分からん、というわけです。

HTTPサーバを練習がてら書いてみたのですが、 コンパイルエラーが直せなくて泣きたい気持ちになりました。

そんなわけで、ポスト C++ として優れた言語であろうことは感じつつも、学習コストに見合う言語だとは感じませんでした。

2nd Edition 日本語版

それから1年くらいは Rust を触らなかったように記憶しています。

そんな折に、以下のツイートが TL に流れてきました。

[https://twitter.com/yyu/status/985924298282975237:embed]

Rust のコミュニティが分かりやすい学習リソースを目指して 2nd Edition をリリースしたのは聞いていましたが、英語なのでちょっと面倒だと思って読んでいませんでした。

その日本語版が読めるということで、懐かしさもあり読んでみることにしました。

Rust の美しさ

2nd Edition 日本語版を読み進めていくうちに、私は Rust に夢中になっていました。

当初、複雑だと思った Rust の機能は十分によく考えられて用意されており、分かりづらいと思っていた一部のシンタックスも機能を本質的かつ端的に表現しており、それでいてヒューマンリーダブルとコンパイル速度の両立を計っているのだと感じました。(褒めすぎ?)

そして Rust の機能の理解が進むに連れ、Rust のコードの見え方が少しずつ変化していくことにも気づきました。

これは Haskell を学んだ時の感覚に似ている。そう感じました。

Rust の特徴

先ほどから Rust の言語的な紹介をまるでしていませんでしたが、Rust は以下に焦点をあてた言語であるととされています。

  • 速度
  • 安全性
  • 並行性

速度

速度は C++ と同等レベルを目指しており、関数型プログラミングや Trait といったモダンな機能を取り入れつつも、実行速度は C++ と同程度になっているようです。

一般的にプログラミング言語が高機能になる、すなわち抽象化が進むほど実行時のオーバーヘッドは高くつくものですが、Rust コンパイラは「ゼロコスト抽象化」という考え方を元に、実行時のオーバーヘッドが限りなく少なくなるように設計されています。

安全性

メモリ管理について記憶をたどると C++より後の言語はだいたいガベージコレクタ(GC)を採用している印象があります。

GC はメモリ管理を自動化しようという考え方で、実行時のオーバーヘッドと引き換えにプログラマはメモリ管理から開放され、より生産的になれるというものでした。なお、 Swift で採用されている ARC も GC にあたるようです。

C言語の時代ではメモリ管理はプログラマの重要な仕事でしたが、ハードウェアの圧倒的な進化もあり、現代のプログラミング言語ではメモリ使用量やレイアウトを意識する機会も圧倒的に減ったのではないかと思います。

GC は実際のところ 1960 年代の Lisp でも実装されていたものらしいですが、それはさておきプログラマはメモリ管理から開放されたおかげで、メモリリークやダングリングポインタなどのバグを圧倒的に減らせたとされています。

これは Joel on Software | Joel Spolsky, 青木 靖 |本 | 通販 | Amazon などでも語られていました。

GC というメモリ管理手法は、手動管理に比べると圧倒的に安全であり、解放済みのメモリアドレスにアクセス(ダングリングポインタ)して未定義動作を引き起こしたり、あるいはバッファオーバーフロー脆弱性なども防ぐことが出来ました。

とはいえ GC も万能というわけではなく実行時のオーバーヘッドがつきまといます。

Java では フルGC により一時的にプログラムが停止してしまう問題はよく知られています。

さて前置きが長くなりましたが、 Rust の所有権システムでは、GCのようなオーバーヘッドを発生させることなく、かつ手動管理のような非安全性を排除したメモリ管理を採用しています。

これは一言ではとても説明できないので今後に譲りたいと思いますが、端的に言えばコンパイル時にメモリ安全性を確保するアプローチ、といったところでしょうか。

並行性

プログラミングにおける並行性はとても難しいものです。

マルチスレッドのプログラミングは、誇張するわけでもなくシングルスレッドのプログラミングよりはるかに難しく、それを安全に動作させるというのはさらに難しいです。

現代では Actor モデルとして知られる、メッセージ受け渡しモデルの並行プログラミングが主流になりつつあると感じられますが、これは GC と同じくオーバーヘッドがあり、ハードウェアの限界まで搾り取る必要のあるプログラムには向いてないとされます。(とは言え、Go言語の近況をみるとそれも分かりません)

Rust では OS の生スレッドをAPIとしてサポートしつつ、並行プログラミングにおける課題であるデータ競合に対して、コンパイル時に誤りを検出しようというアプローチを取っています。

すなわちオーバーヘッドなしでOSのスレッドを利用しつつも安全な並行性を実現できるということです。

Rust は最強言語の夢を見るか?

多くのプログラミング言語あるいはミドルウェアやFWがそうであるように Rust も適材適所でしょう。Rust が向いている領域もあれば、Rust が向いてない領域もあるでしょう。

スマートフォンがいくら便利だからと言って、それを缶詰の蓋をあけるのに利用する人はいないでしょう。(いないよね?)

なぜ、わざわざこんな話を持ち出したかというと、(Rust を学習中の身ではあり現時点の予想に過ぎないのですが)おそらく Rust が向いている、すなわちトレードオフに見合うソフトウェア開発プロジェクトというのはさほど多くは無いのではないかと想像しているためです。

現代の潤沢なハードウェアにおいて、ハードウェアの限界まで絞りだすようなパフォーマンスが必須の要件となるプロジェクトはさほど多くはないでしょう。

もちろん OS や ドライバ などのソフトウェア開発などには向いているでしょうが。

教養としての Rust

さて、それを考えると Rust を学ぶべきでしょうか?

プログラミング言語の人気度ランキングにしたがって、職につきやすい言語を学ぶべきではないでしょうか。例えば、JavaとかScalaとかRubyとか(実際、高給なのはScalaなんですかね?)。

それでも私は Rust を学ぶ価値があると感じます。

それは古今東西の偉大なプログラマが Lisp を学ぶべきだと言ってきたのと同じ理由であり、すなわちより良いプログラマになるために Rust を学ぶべきなのではないかということです。

Rust の所有権システムはメモリ管理についての考え方をあらためさせてくれますし、シンタックスと調和の取れたモダンな言語仕様からも多くの刺激を得られます。

Swift はかなりモダンな言語だと思っていましたし、実際モダンな言語だと思いますが、それでも Rust を学んでいくと見えてなかったものが見えてくる気がしました。

特に C言語やC++などの手動メモリ管理をする言語を触ったことがなければ、Rust の所有権システムから得られる学びというのは大きいと感じます。(と言いつつ、C言語とかで低レベルなメモリ管理を学ぶほうが良いのかな、という思いもあったりします)

まぁ、つまり教養として Rust を学ぶというのはとても有意義なことで、それはプログラマとしての考え方を広げてくれるのではないか、そんな風に私は感じるわけです。

D.C. - ダ・カーポ -

という訳で、話は最初に戻ります。

すなわち Swift エンジニアの私が Rust を勉強していこう というわけです。

学習するならアウトプットすべきですし、私が学んだ知識を広く共有することで、誰かの役に立てばそれはプログラマ冥利に尽きるというものです。

そんなわけで Rust の学習記録をブログ記事として連載していきたいと思います。

まぁこういうのは最初だけモチベーション高くて、すぐに飽きてしまう(プログラマとしては決して悪くない正確かもしれませんがそれはさておき)かもしれませんが、まぁ続けていけたらなと思います。

最初の記事にこんなにエネルギーを使って果たして良いものかと思いつつ、このあたりで。

Swift 4.2 で追加される removeAll(:where) メモ(SE-0197)

どうも、運動したあとはお酒が飲みたくなってしまう tobi462 です。

ちょっと時間が空きましたが、今回はremoveAll(:where)という新たに追加されたコレクション系のAPIの Proposal を見ていきたいと思います。

今までの Swift 4.2 の記事は以下です。

なんだか増えてきましたね。

Tl;Dr

let isOdd: (Int) -> Bool = { $0 % 2 == 1 }

var xs = [1, 2, 3, 4, 5, 6]
xs.removeAll(where: isOdd) // mutating only
xs // => [2, 4, 6]

removeAll(where:) / SE-0197

RangeReplaceableCollection プロトコルに removeAll(where:) という mutating なメソッドが追加されました。

通常、特定の要素を除外する一般的な方法としては filter が利用できます。

xs.filter { !isOdd($0) }

しかし、これには2つの欠点があると Proposal では述べられています。

  • isOdd で無いもの 、という指定方法であり可読性が悪い(否定の条件は読みづらい)
  • 新しいコレクションを生成するのでメモリ効率が悪い

可読性

可読性については例えば Ruby であれば select(Swiftでいうところの filter )に対して、 reject というメソッドが用意されています。

xs = [1, 2, 3, 4, 5, 6]
xs.reject {|x| x % 2 == 1}

これは除外したいものをクロージャで指定しており分かりやすいと思います。同様の考えで Swift にも removeAll(where:) を追加するのは良い考えであると思います。(ただし、 filter と違って mutating なメソッドしか無いようですが・・・)

メモリ効率

filter の計算量は要素数を n とした場合 O(n) で固定です。

ただし、常に新しいコレクションを返すためメモリ効率という点から見ると優れているわけではありません。また要素が大きい場合はコピーコストがかかるという課題もあります。

今回の removeAll(where:) は(Proposalによると) shuffle-down というアルゴリズムを使用しており、新たなメモリを確保する必要なく要素の削除が行われるようになっています。

実際に merge された実装は以下のようです。
Add remove(where:) to RangeReplaceableCollection by airspeedswift · Pull Request #11576 · apple/swift · GitHub

なにやら込み入っていますが、要素を swap しつつ、最後に一気に除去する作りに見えます。

これにより filter と違って新たなメモリを確保することなく要素の削除が行えるという思想のようです。

速いか?

100万個の Int が格納された配列から奇数のみを抽出した場合、 filterremoveAll(where:) でどちらが速いか検証してみました。

import Foundation

let isOdd: (Int) -> Bool = { $0 % 2 == 1 }

let mesure: (String, () -> Void) -> Void = {
    let start = Date()
    $1()
    let interval = Date().timeIntervalSince(start)
    print("\($0):\n  \(interval)")
}

let xs = Array(1...1_000_000)
var ys = xs

mesure("filter") {
    xs.filter { !isOdd($0) }
}
mesure("removeAll(where:)") {
    ys.removeAll(where: isOdd)
}

-O で実行したところ、手元のMacBookでは以下の結果となりました。

$ ./swift -O removeAll.swift
filter:
  0.018193960189819336
removeAll(where:):
  0.01980602741241455

10回実行した結果の平均みたいな丁寧な計測方法はしていないのですが、そこまで実行速度には変化が無いようです。

removeAll(where:) はメモリ効率に優れてはいるものの、 filter に比べて処理が高速といったことはおそらくなさそうです。

filter バージョンが欲しい?

mutating なAPIではなく、filter のように新しいコレクションを返すAPIが欲しい人もいるかもしれません。

そうした場合は以下のように自前で実装するのが手っ取り早そうです。

extension Sequence {
    func reject(_ isReject: (Element) -> Bool) -> [Element] {
        return filter { !isReject($0) }
    }
}

let xs = [1, 2, 3, 4, 5, 6]
let ys = xs.reject(isOdd) // => [2, 4, 6]

標準APIでないので初見で戸惑う可能性もありますが、filter の条件を否定で書くよりは素直で可読性がよく見えます。まぁ、トレードオフですね。

まとめ

なんかいろいろ書きましたが、指定した条件の要素を削除できるAPIが追加されたよ、って理解で良いような気がします。

莫大な要素数を処理するとか、あるいはメモリがとても制限された環境だとかであれば、このあたりのメモリ効率などを考えることもあるかもしれませんが。

というわけで無駄に長くなってしまいましたが、今回はこれにて。

【Docker】alpine の `apk update` でエラーが発生した時のメモ

どうも、tobi462 です。

今日は docker-compose build での apk update 時に以下のエラーが発生した時の対処メモです。

Step 7/11 : RUN apk update
 ---> Running in 45856361a825
fetch http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://mirrors.aliyun.com/alpine/v3.4/main: temporary error (try again later)
WARNING: Ignoring APKINDEX.47c31cae.tar.gz: No such file or directory

Tl;Dr

Docker のバージョンを最新にしたら解決しました。

$ sudo apt-get install docker-ce
$ docker -v
Docker version 18.03.1-ce, build 9ee9f40

突然の死

Jenkins / Ubuntu 16.04 LTS にシステムを自動デプロイをしていたのですが、ある日からデプロイに成功しなくなりました。

そして Job のログを見ると以下のようなエラーが出ていました。

fetch http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://mirrors.aliyun.com/alpine/v3.4/main: temporary error (try again later)
WARNING: Ignoring APKINDEX.47c31cae.tar.gz: No such file or directory

どうやら docker-compose build 時、何かをDLしようとしたときにエラーになっているようです。

その後、 RUN コマンドを分解して実行したところ、 apk upate で失敗していることがわかりました。

しかし、ローカル(Mac)で docker-compose build しても成功します。

エラーメッセージでぐぐったらいろいろ情報が見つかったので、色々試してみました。

curl で DL できるか試してみる

URLが死んでいるのかと思い、まずは curl でアクセスできるか試してみました。

$ curl -o x.tar.gz http://mirrors.aliyun.com/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  663k  100  663k    0     0  1654k      0 --:--:-- --:--:-- --:--:-- 1654k

どうやらこれは問題ないようです。

ミラーリポジトリを試してみる

ググった中にミラーリポジトリを試してみると良い、という情報があったので試してみました。

どうやら alpine では /etc/apk/repositories にパッケージマネージャ用のリポジトリURLが記載されているようです。

# cat /etc/apk/repositories
http://dl-cdn.alpinelinux.org/alpine/v3.4/main
http://dl-cdn.alpinelinux.org/alpine/v3.4/community

そこで DockerfileRUN コマンドを追加して、ミラーリポジトリを追記してみました。

RUN echo http://mirror.yandex.ru/mirrors/alpine/v3.5/main > /etc/apk/repositories; \
    echo http://mirror.yandex.ru/mirrors/alpine/v3.5/community >> /etc/apk/repositories

残念ながらこの方法では解決しませんでした。

まぁ、 curl で DL は出来ていたのであまり期待はしていませんでしたが。

IP v6 を有効にしてみる?

正直、これはあまり理解していなかったのですが、以下のように /etc/modulesipv6 を足してみたら解決したという記事があったので試してみました。

こういう時、Docker は環境を壊す心配をせずに試せるのでありがたい限りです。

RUN echo "ipv6" >> /etc/modules

まぁ、これも駄目だったのですが。

docker のバージョンアップ

そもそもなぜローカルのMacではビルドが成功するのだろう、と思って Docker のバージョンを調べてみました。

すると、ローカルでは 18.x 系だったのに、Ubuntu 16.04 LTS では 17.x 系でした。

なので試しに Ubuntu 上の Docker も最新にしてみることにしました。

$ sudo apt-get update
$ sudo apt-get install docker-ce

実のところあまり期待していなかったのですが、これで解決しました。

おわり

ということでトラブル対処メモでした。役に立つ人もいるかもしれないので記事にしました。

今回のケースでは Docker のバージョンアップで解決しましたが、状況によって解決策は異なるかもしれません。

Swift 4.2 で追加されるコンパイラディレクティブ(SE-0196)

何でもは知らない、知ってることだけ。

どうも、最近書き出しの挨拶を考えるのに時間を要する tobi462 です。

今日は SE-0196 で追加されたコンパイラディレクティブの紹介です。

今まで書いた Swift 4.2 の記事は以下です。

いつのまにかシリーズ化してきましたね。 (化物語みたいに)

Tl;Dr

#warning("強制アンラップはやめるべき") // コンパイル時の警告
let message = self.message!

#error("APIのアクセストークンを設定してください") // コンパイルエラー
let API_TOKEN = ""

in Objective-C

Objective-Cでは以下のようなコンパイラディレクティブが利用できました。

#warning 暫定対処、後ほど修正するべき
#error APIトークンを設定する必要あり

それぞれコンパイル時にWarning、Errorとできる仕組みです。

これによって後ほど修正すべき箇所をWarningとして警告しておいたり、ライブラリのテンプレートコード中に埋め込んでおき、実装が必須であることを明示できるというメリットがあります。

これはコメントで代替することも可能ですが、(標準の)コンパイル時において警告またはエラーとして明示することができる点がメリットになります。

in Swift 4.2

Objective-Cでサポートされていたこの機能が、Swift 4.2 でようやくサポートされました。

#warning("暫定対処、後ほど修正するべき")
#error("APIトークンを設定する必要あり")

また、#if のような他のコンパイラディレクティブとも併用して利用することも出来るようです。

#if os(iOS)
#error("macOSはサポート外です")
#endif

上記のように、あるライブラリを作成していたときに特定のプラットフォームだけサポート外としてエラーにしたいということもできるのは便利と言えるでしょう。

まとめ

やれやれ、1記事にするほどの内容でも無かったんじゃないかな、阿良々木くん。

というわけで Swift でもコンパイラディレクティブ(Warning、Error)が使えるようになったという話でした。

また次回?