htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

scoop cleanup * はいいぞ

Windows を開発機にしているユーザーの100%が導入している1と言われる Scoop ですが、インストールされているアプリケーションのアップデートが溜まってくると割とストレージを圧迫します。

そんなときは scoop cleanup * として過去のバージョンを消してしまうと効果があります。

これで

f:id:htsign:20200127211048p:plain

これが

f:id:htsign:20200127211240p:plain

こうなる。

f:id:htsign:20200127211253p:plain

なお、私の場合は入れているアプリケーションは以下です。

f:id:htsign:20200127211636p:plain

なお、 scoop cache rm * も効果があります。私はやりませんが。
いや別にデメリットがあるわけじゃないんですけどね。

余談ですが、前にいた会社では Scoop 経由で他にもたくさん入れていたのでこれ以上に効果がありました。


  1. 私調べ (N=1)

nim / rust / pony の速度比較

※ 最初に申し上げておきますが、とても浅はかな比較です。

Rust は言わずもがな、「最も愛されているプログラミング言語」4年連続1位1の言語です。
データの生存期間を型で管理することで、GCなしにメモリセーフなプログラミングを可能にしてますね。
トレイトやマクロなどもあり、表現力も豊富で開発も盛んなので注目ですね。
実行速度もちょくちょく C++ に勝ったり負けたりと、非常に高速なことで知られます。

Nim は柔軟な書き方ができる言語で、最近お気に入りです。
ついこの間、めでたく v1.0 を迎えて安定期に入りました🎉
遅延リストのリテラルがないのが残念です。マクロはあるのでその気になれば自力実装はできそうですが、現時点では私には Nim 力が足らず無理です。
Nim で書かれたコードは一度 C のコードに変換されてから2、C のコンパイラでさらにネイティブコードにコンパイルされます。

Pony は昨日3初めて知った言語ですが、割と前からあるみたいですね。
言語構文レベルでアクターモデルを採用しているのが、たぶん最大の特徴です。
Rust に似て変数の所有権のようなもの4を型に持たせることで、アクターモデルでありながらデータのコピーを極力減らすことができ、高速かつ安全なプログラミングを可能にしているようです。
補完にまで対応した IDE やエディタ拡張が見当らないので、それがネックですね…。
まだセルフホストには至っていない模様。

環境

WSL Ubuntu on Windows 10

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$ rustc --version
rustc 1.39.0 (4560ea788 2019-11-04)
$ nim --version
Nim Compiler Version 1.0.2 [Linux: amd64]
Compiled at 2019-10-22
Copyright (c) 2006-2019 by Andreas Rumpf

git hash: 193b3c66bbeffafaebff166d24b9866f1eaaac0e
active boot switches: -d:release
$ ponyc --version
0.33.0-98c36095 [release]
compiled with: llvm 7.0.1 -- cc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Defaults: pic=true

比較

なるべく公平になるようにコードを書いているつもりです。

FizzBuzz

コード
Rust
fn fizz_buzz(n: i32) {
  let s =
    match (n % 3, n % 5) {
      (0, 0) => "FizzBuzz".to_string(),
      (0, _) => "Fizz".to_string(),
      (_, 0) => "Buzz".to_string(),
      _ => n.to_string(),
    };
  println!("{}", s);
}

fn main() {
  for x in 1..=100 {
    fizz_buzz(x);
  }
}
Nim
proc fizzBuzz(n: int) =
  let (x, y) = (n mod 3, n mod 5)
  let s =
    if (x, y) == (0, 0): "FizzBuzz"
    elif x == 0: "Fizz"
    elif y == 0: "Buzz"
    else: $n
  echo s

for x in 1..100:
  fizzBuzz x
Pony
use "collections"

actor FizzBuzz
  let _env: Env
  
  new create(env': Env) =>
    _env = env'
  
  be fizz_buzz(x: I32) =>
    let s =
      match (x % 3, x % 5)
      | (0, 0) => "FizzBuzz"
      | (0, _) => "Fizz"
      | (_, 0) => "Buzz"
      else x.string() end
    _env.out.print(s)

actor Main
  new create(env: Env) =>
    let fb = FizzBuzz(env)
    for x in Range[I32](1, 101) do
      fb.fizz_buzz(x)
    end
コンパイル速度
Rust
$ time -p rustc -Copt-level=3 -Clto -Cpanic=abort fizzbuzz.rs
real 2.23
user 1.96
sys 0.39
Nim
$ time -p nim c -d:release --opt:speed --hints:off fizzbuzz
CC: stdlib_io.nim
CC: stdlib_system.nim
CC: fizzbuzz.nim
real 2.03
user 1.70
sys 0.48
Pony
$ time -p ponyc --verbose=0
real 2.84
user 2.03
sys 0.81
Rust Nim Pony
real 2.23 2.03 2.84
user 1.96 1.64 2.12
sys 0.39 0.54 0.70

コンパイル速度はそこまで差が出ないようです。
※ ただし、Nim はコンパイル結果をキャッシュするので、同じプロジェクトであれば2度目以降のコンパイルが非常に高速になります。 --forceBuild / -f オプションを付けるかキャッシュを削除すると1度目と同じ条件になります。今回は毎回キャッシュを削除しました。
※ Rust は最適化オプションをいくつか外すことで、成果物のファイルサイズを犠牲(3倍近く)にかなり高速(4分の1以下)になりました。

ファイルサイズ
Rust
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 928400 11月 21 21:58 fizzbuzz*
Nim
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 88864 11月 21 21:59 fizzbuzz*
Pony
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 163176 11月 21 21:59 fizzbuzz*
Rust Nim Pony
928,400 bytes (907KB) 88,864 bytes (87KB) 163,176 bytes (159KB)

※ KB 表記の端数は四捨五入

Rust が圧倒的にデカいですね。
Rust のバイナリがデカい理由はデバッグシンボルが残っているからかと思われます。最適化オプション有効にしてるのに…。
ここではコマンド単体で完結することを前提とするのでこれを最終結果とします。

実行速度
Rust
$ time -p ./fizzbuzz > /dev/null
real 0.01
user 0.00
sys 0.01
Nim
$ time -p ./fizzbuzz > /dev/null
real 0.00
user 0.00
sys 0.00
Pony
$ time -p ./fizzbuzz > /dev/null
real 0.02
user 0.00
sys 0.01
Rust Nim Pony
real 0.01 0.00 0.02
user 0.00 0.00 0.00
sys 0.01 0.00 0.01

まぁ当然ですが、負荷が軽すぎてほとんど差が出ないですね…。

45番目のフィボナッチ数

※ 45番目なのは計算時間的にちょうどよさそうなので。

コード
Rust
fn fibonacci(n: i64) -> i64 {
  match n {
    1 | 2 => 1,
    _ => fibonacci(n - 2) + fibonacci(n - 1),
  }
}

fn main() {
  println!("{}", fibonacci(45))
}
Nim
func fibonacci(n: int64): int64 =
  case n
  of 1, 2: 1.int64
  else: fibonacci(n - 2) + fibonacci(n - 1)

echo fibonacci(45)
Pony
primitive Fibonacci
  fun fib(n: I64): I64 =>
    match n
    | 1 | 2 => 1
    else fib(n - 2) + fib(n - 1) end

actor Main
  new create(env: Env) =>
    env.out.print(Fibonacci.fib(45).string())
コンパイル速度

※ 実行コマンドは FizzBuzz のとほとんど変わらないので割愛

Rust Nim Pony
real 2.20 2.00 1.19
user 1.75 1.64 0.89
sys 0.45 0.48 0.29

なぜか Pony が FizzBuzz の場合と比べてちょっと速くなりました。

ファイルサイズ
Rust Nim Pony
924,024 bytes (902KB) 84,288 bytes (82KB) 158,696 bytes (155KB)

こちらも相変わらず Rust がダントツにデカいです。

実行速度
Rust Nim Pony
real 2.88 3.45 3.52
user 2.87 3.45 3.48
sys 0.01 0.00 0.03

さっきと比べて少し差が出ましたね。
Rust、やはり速い。

並列性を上げて計算すれば Pony も速くなったりするのでしょうか。


以上、Pony はまだまだ発展途上ですし、Rust もバージョン上がるごとに最適化が進んだりするので、本当にガバガバ比較です。
結局のところ好みで選べばいいんじゃないかと思います。
並列計算が目的なら Erlang や Elixir などの選択肢もあります。が、あれはプロセス単位での並列なのでちょっと事情が違うかも。


  1. https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages

  2. いわゆるトランスパイル

  3. 投稿時間が日を跨いでしまったので…。

  4. Pony では Reference Capabilities と呼ばれます。

Promise を再帰させるとどうなるか

ただの興味本位です。

以下のコードを各環境で動かしていきます。

const p = n => new Promise(resolve => { console.log(n); resolve(p(n + 1)); });
p(1);

なるべく公平を期すため、ブラウザの場合は about:blank にて実行します。

環境

PC1:

種別 詳細
OS Windows 10
CPU AMD Rizen 5 1600
MEM DDR4 PC4-24000 16GB x2 (空き 20000MB程度)

PC2:

種別 詳細
OS macOS 10.14.6 (Mojave)
CPU Intel Core i5 5250U
MEM LPDDR3 PC3-12800 4GB (空き1000MB程度)

結果

環境 再帰回数 備考
Edge 44 1000 20回置きにウェイトが入る。終了時は特に出力なし
Safari 3674 コンソールにはエラーが表示されず、
ブラウザページにポップアップでMaximum call stack size exceeded.と表示され終了
Firefox 69 (PC1) 582 too much recursion の例外で終了
Firefox 69 (PC2) 2946 too much recursion の例外で終了
Chrome 77 (PC1) 1934 RangeError: Maximum call stack size exceeded の例外で終了
Chrome 77 (PC2) 1929 RangeError: Maximum call stack size exceeded の例外で終了
node 10.16.3 (PC1) 1818 エラーを吐かずにクラッシュして終了
node 10.16.3 (PC2) 1847 RangeError: Maximum call stack size exceeded の例外で終了
node 12.11.1 (PC1) 1806 エラーを吐かずにクラッシュして終了
node 12.11.1 (PC2) 1835 RangeError: Maximum call stack size exceeded の例外で終了

この規則性のない結果から、おそらく Promise における再帰については厳密な仕様はなく実装依存であることが推定されますが、仕様書のどの辺りを見ればよいのか分からず…。
同じバージョンでも環境によって結果が異なるので、メモリ等の状態によっても変わるのかなと思いますが、ブラウザのソースコード読む気力もなく、「とりあえず Promise の再帰はなるべく避けよう」という辺りで落ち着けておきます。

URLからトラッキングのアレを削除するやつ書いた

モチベーションは Neat URL がだいぶ前から動かなくなっていた*1から。
似たようなアドオンも軒並み死んでいるように思うので自分で作りました。

とりあえず単純なトラッキングタグ(って言うのかな?)は削除できるようにしてあり、最終的な目標としては Neat URL と同等の機能を持たせてあげたい。

グリモンスクリプトなので、 Edge, Firefox, Chrome, Safari 等のグリモン系アドオンがあり

'URL' in window && 'URLSearchParams' in window

が真となる環境であれば問題なく使えるはずです。
なんなら Firefox for Android であればスマートフォンでも使えます*2

インストール

gist.github.com

*1:Issueにそれらしいのがないので、他のアドオンとの干渉によるおま環かもしれません。

*2:執筆時点では証明書問題で動きませんが、じきに回復します。

npm scripts で OS ごとに処理を分ける

npm scriptsclean ってコマンドを登録しようとしたんですよ。

普通なら rimraf を使うところなんですが、 cleanの過程で node_modules ディレクトリも削除して綺麗にしたい。
かと言って npm i -g rimraf など、環境依存になるので以ての外。

rm コマンドは *NIX 環境に依存してしまう。
rd コマンドは Windows環境に依存してしまう。

さて困った。

そこで閃きました。

"scripts": {
  "clean": "node -e \"process.exit(process.platform === 'win32' ^ 1)\" && rd /s /q node_modules || rm -rf node_modules"
}

こうします。

これならば、process.platform を評価して win32 だった場合とそれ以外で処理を分けることができます。

true ^ 1 // ==> 0 false ^ 1 // ==> 1 となり、シェルはこれをエラーコードと見るため、 &&|| で繋げることによって処理が分かれます。