強まっていこう

あっちゃこっちゃへ強まっていくためのブログです。

FizzBuzz を無駄にベンチマークしてみた By Nim、golang、Rust、Crystal、その他

FizzBuzz 2つのパターン

Fizz Buzz - Wikipedia

3で割り切れる場合は Fizz、5で割り切れる場合は Buzz、3と5で割り切れる場合は FizzBuzz、それ以外は数字を表示すると言う FizzBuzz の書き方って大きく分けて2種類ありますよね。

その2つのパターンを粗暴なプログラマーにオススメな言語だけど殆どの人達が見たこともないであろう Nim で書いてみます。


まぁやってることが簡単なんで言語が違えどわかりますよね。

1つは愚直に全ての条件を書いてしまうパターン。

let n = 600_000
for i in 1..n:
  if i mod 3 == 0 and i mod 5 == 0: echo "FizzBuzz"
  elif i mod 3 == 0: echo "Fizz"
  elif i mod 5 == 0: echo "Buzz"
  else: echo i

もう1は文字列連結を使って条件の被りを回避するパターン。

let n = 600_000
for i in 1..n:
  var str = ""
  if i mod 3 == 0: str = "Fizz"
  if i mod 5 == 0: str &= "Buzz"
  if str == "": echo i else: echo str

Wikipedia の例だと数値を出力文字列に代入しちゃってますが、なるべく軽くするために代入を避けるべく条件分岐で数値と文字列を吐き分けるているのがクソどうでも良いこだわりだったりします。

自分的にはこちらの方が好みなんですよね。なんか同じ条件を書くのが嫌なんですよ。わかります?この感覚。
前者の方はどうしてもバカっぽく見えちゃいます。見づらいですし、条件変える時に2箇所いじる必要ありますし。良い事無い気がするんですよね。

でも前者はモジュロ演算が2個多いとは言え文字列連結が無い分はえぇんじゃなかろうか?

気になっちゃったのでベンチしてみました。

いざベンチ

独断と偏見で前者を「愚直」、後者は「エレガント」として表記しています。

1回目

言語 愚直 エレガント
C 0.06 0.07
Crytal 0.33 0.34
Perl 0.35 0.38
Nim 0.37 0.40
Rut 0.44 0.48
PHP 0.51 0.48
golang 0.59 0.76
Ruby 0.61 0.75
Node.JS 13.59 13.73

2回目

言語 愚直 エレガント
C 0.07 0.08
Crytal 0.35 0.36
Perl 0.35 0.36
Nim 0.37 0.38
Rut 0.46 0.49
PHP 0.49 0.47
golang 0.62 0.72
Ruby 0.60 0.84
Node.JS 13.23 13.36

うーん、さすがに前者の方が若干早いですね・・・っておいPerl

なんであんたが Crystal と競って Nim と Rust、golang より早いねん。コンパイルされてるやつらが負けるとか無いでしょ。
と言うか C との速度差はなんですか。不甲斐なさ過ぎでしょう。Rust なんて PHP といい勝負しちゃってますし golang に至っては負けちゃってます。あぁ情けなや。
Ruby と Crystal は同じコードですがさすがに倍以上 Crystal が早いですね。ですが Crystal はコンパイル速度が気絶するほど遅いんですよねぇ・・・そこがネック。

つぅか Node.js よ、お前どうなっとんじゃ。しゃれにならんでしょ、これ。v8.0.0 でやってたのでバージョン上げりゃ速くなるかなと思い v8.2.1 に上げてみましたが変わらず、と言うかむしろちょっと遅くなると言うね。

何がダメなんだろうかと思ってちょっと調べたら console.log がダメな模様。process.stdout.write に変えてみました。

言語 愚直 エレガント
Node.JS(write) 5.81 5.45
Node.JS(write) 5.44 4.92

おおー!倍速くなった!ってやっぱおせぇし。他と比べて10倍遅いですからね。こう言う処理は Node.js 使うなって話ですね。

しかも他と違い愚直よりもエレガントの方が早いと言う良くわからない状況になりましたが、んなこたどうでも良いです。しょうもないレベルで遅いですから。

golang は []byte に append するともっと遅くなっちゃいました。流石に文字列が短いと + 演算子の方が強いです。

まとめ

今回ベンチしてわかったことは

  • 愚直に書いたらほんの少し早いかもね
  • Perl が意外とこう言う処理はえ
  • Nim、Crystal、Rust もっとがんばれよ
  • Node.js は使い所によっては相当ポンコツ
  • C はやはり帝王

どっちの書き方が云々よりも予想していなかった実行速度の優劣に驚きましたね。

コード

今回ベンチ取ったコードを載せておきます。

C

#include <stdio.h>

int main(void) {
  int n = 600000;
  int i = 1;
  for (i = 1; i <= n; i++) {
    if (i % 3 == 0 && i % 5 == 0) {
      printf("FizzBuzz\n");
    }
    else if (i % 3 == 0) {
      printf("Fizz\n");
    }
    else if (i % 5 == 0) {
      printf("Buzz\n");
    }
    else {
      printf("\%d\n", i);
    }
  }
  return 0;
}
#include <stdio.h>

int main(void) {
  int n = 600000;
  int i = 1;
  for (i = 1; i <= n; i++) {
    char str[9] = "";
    if (i % 3 == 0) {
      sprintf(str, "Fizz");
    }
    if (i % 5 == 0) {
      sprintf(str, "%sBuzz", str);
    }
    if (*str) {
      puts(str);
    }
    else {
      printf("%d\n", i);
    }
  }
  return 0;
}

Crystal + Ruby

n = 600000
(1..n).each do |i|
  if i % 3 == 0 && i % 5 == 0
    puts "FizzBuzz"
  elsif i % 3 == 0
    puts "Fizz"
  elsif i % 5 == 0
    puts "Buzz"
  else
    puts i
  end
end
n = 600000
(1..n).each do |i|
  str = ""
  str = "Fizz" if i % 3 == 0
  str += "Buzz" if i % 5 == 0
  if str == ""
    puts i
  else
    puts str
  end
end

Perl

#!/usr/bin/perl

use strict;

my $n = 600_000;
for (1..$n) {
  if ($_ % 3 == 0 and $_ % 5 == 0) {
    print "FizzBuzz\n";
  }
  elsif ($_ % 3 == 0) {
    print "Fizz\n";
  }
  elsif ($_ % 5 == 0) {
    print "Buzz\n";
  }
  else {
    print "$_\n";
  }
}
#!/usr/bin/perl

use strict;

my $n = 600_000;
for (1..$n) {
  my $str = '';
  $_ % 3 == 0 and $str = "Fizz";
  $_ % 5 == 0 and $str .= "Buzz";
  print $str ? "$str\n" : "$_\n";
}

Nim

let n = 600_000
for i in 1..n:
  if i mod 3 == 0 and i mod 5 == 0: echo "FizzBuzz"
  elif i mod 3 == 0: echo "Fizz"
  elif i mod 5 == 0: echo "Buzz"
  else: echo i
let n = 600_000
for i in 1..n:
  var str = ""
  if i mod 3 == 0: str = "Fizz"
  if i mod 5 == 0: str &= "Buzz"
  if str == "": echo i else: echo str

Rust

fn main() {
  let n = 600000;
  for i in 1..n {
    if i % 3 == 0 && i % 5 == 0 {
      println!("FizzBuzz"); 
    }
    else if i % 3 == 0 {
      println!("Fizz"); 
    }
    else if i % 5 == 0 {
      println!("Buzz"); 
    }
    else {
      println!("{}", i); 
    }
  }
}
fn main() {
  let n = 600000;
  for i in 1..n {
    let mut str = "".to_owned();
    if i % 3 == 0 {
      str.push_str("Fizz");
    }
    if i % 5 == 0 {
      str.push_str("Buzz");
    }
    if str.len() > 0 {
      println!("{}", str);
    }
    else {
      println!("{}", i);
    }
  }
}

PHP

#!/usr/bin/php
<?php

$n = 600000;
for ($i = 1; $i <= $n; $i++) {
  if ($i % 3 == 0 && $i % 5 == 0) {
    echo "FizzBuzz\n";
  }
  elseif ($i % 3 == 0) {
    echo "Fizz\n";
  }
  elseif ($i % 5 == 0) {
    echo "Buzz\n";
  }
  else {
    echo "$i\n";
  }
}
#!/usr/bin/php
<?php

$n = 600000;
for ($i = 1; $i <= $n; $i++) {
  $str = '';
  if ($i % 3 == 0) {
    $str = "Fizz";
  }
  if ($i % 5 == 0) {
    $str .= "Buzz";
  }
  echo $str != "" ? "$str\n" : "$i\n";
}

golang

package main

import (
  "fmt"
)

func main() {
  n := 600000;
  for i := 1; i <= n; i++ {
    if i % 3 == 0 && i % 5 == 0 {
      fmt.Printf("FizzBuzz\n")
    } else if i % 3 == 0 {
      fmt.Printf("Fizz\n")
    } else if i % 5 == 0 {
      fmt.Printf("Buzz\n")
    } else {
      fmt.Printf("%d\n", i)
    }
  }
}
package main

import (
  "fmt"
)

func main() {
  n := 600000;
  for i := 1; i <= n; i++ {
    str := ""
    if i % 3 == 0 {
      str = "Fizz"
    }
    if i % 5 == 0 {
      str += "Buzz"
    }
    if str != "" {
      fmt.Printf("%s\n", str)
    } else {
      fmt.Printf("%d\n", i)
    }
  }
}

Node.js

var n = 600000;
for (var i = 1; i <= n; i++) {
  if (i % 3 == 0 && i % 5 == 0) {
    console.log("FizzBuzz");
  }
  else if (i % 3 == 0) {
    console.log("Fizz");
  }
  else if (i % 5 == 0) {
    console.log("Buzz");
  }
  else {
    console.log(i);
  }
}
var n = 600000;
for (var i = 1; i <= n; i++) {
  var str = "";
  if (i % 3 == 0) {
    str = "Fizz";
  }
  if (i % 5 == 0) {
    str += "Buzz";
  }
  console.log(str ? str : i);
}

Node.js(write)

var n = 600000;
for (var i = 1; i <= n; i++) {
  if (i % 3 == 0 && i % 5 == 0) {
    process.stdout.write("FizzBuzz\n");
  }
  else if (i % 3 == 0) {
    process.stdout.write("Fizz\n");
  }
  else if (i % 5 == 0) {
    process.stdout.write("Buzz\n");
  }
  else {
    process.stdout.write(i + "\n");
  }
}
var n = 600000;
for (var i = 1; i <= n; i++) {
  var str = "";
  if (i % 3 == 0) {
    str = "Fizz";
  }
  if (i % 5 == 0) {
    str += "Buzz";
  }
  process.stdout.write(str ? str + "\n" : i + "\n");
}

コンパイルオプション

gcc -o fizzbuzz_c fizzbuzz.c
nim c -d:release -o:fizzbuzz_nim fizzbuzz.nim
crystal build --release -o fizzbuzz_cr fizzbuzz.cr
rustc -O -o fizzbuzz_rs fizzbuzz.rs
go build -o fizzbuzz_go fizzbuzz.go