コードを飛ばす——高性能Julia学習ノート(三)


前の2編のコードを飛ばす——高性能Julia学習ノート(一) コードを飛ばす——高性能Julia学習ノート(二)、高性能のJuliaコードを書く方法を紹介しました.この2編は私の最近のプロジェクトと結びつけて、簡単なテストでいろいろな言語をmonte carloアルゴリズムでpiの効率を計算します.
まず、本文は厳格な意味での性能テストとは言えず、言語の聖戦を挑発したくない.個人の能力は限られており、実現した異なる言語バージョンのコードも必ずしも最も効率的ではなく、基本的にはnaive実現である.
モンテカルロアルゴリズムに詳しくない場合は、次の2つの資料を参照してください.私は時間を無駄にしません.
  • https://zh.wikipedia.org/wiki...
  • http://www.ruanyifeng.com/blo...

  • マシンは2015年のMacPro:
    Processor: 2.5GHz Intel Core i7
    Memory: 16GB 1600 MHZ DDR3
    Os: macOS High Sierra Version 10.13.4

    JSバージョン

    function pi(n) {
      let inCircle = 0;
      for (let i = 0; i <= n; i++) {
        x = Math.random();
        y = Math.random();
        if (x * x + y * y < 1.0) {
          inCircle += 1;
        }
      }
      return (4.0 * inCircle) / n;
    }
    const N = 100000000;
    console.log(pi(N));

    結果:
    ➜  me.magicly.performance git:(master) ✗ node --version
    v10.11.0
    ➜  me.magicly.performance git:(master) ✗ time node mc.js
    3.14174988
    node mc.js  10.92s user 0.99s system 167% cpu 7.091 total

    Goバージョン

    package main
    
    import (
        "math/rand"
    )
    
    func PI(samples int) (result float64) {
        inCircle := 0
        r := rand.New(rand.NewSource(42))
    
        for i := 0; i < samples; i++ {
            x := r.Float64()
            y := r.Float64()
            if (x*x + y*y) < 1 {
                inCircle++
            }
        }
    
        return float64(inCircle) / float64(samples) * 4.0
    }
    
    func main() {
        samples := 100000000
        PI(samples)
    }

    結果:
    ➜  me.magicly.performance git:(master) ✗ go version
    go version go1.11 darwin/amd64
    ➜  me.magicly.performance git:(master) ✗ time go run monte_carlo.go
    go run monte_carlo.go  2.17s user 0.10s system 101% cpu 2.231 total

    Cバージョン

    #include 
    #include 
    #include 
    #include 
    #define SEED 42
    
    int main(int argc, char **argv)
    {
      int niter = 100000000;
      double x, y;
      int i, count = 0;
      double z;
      double pi;
    
      srand(SEED);
      count = 0;
      for (i = 0; i < niter; i++)
      {
        x = (double)rand() / RAND_MAX;
        y = (double)rand() / RAND_MAX;
        z = x * x + y * y;
        if (z <= 1)
          count++;
      }
      pi = (double)count / niter * 4;
      printf("# of trials= %d , estimate of pi is %g 
    ", niter, pi); }

    結果:
    ➜  me.magicly.performance git:(master) ✗ gcc --version
    Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
    Apple LLVM version 9.1.0 (clang-902.0.39.2)
    Target: x86_64-apple-darwin17.5.0
    Thread model: posix
    InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
    ➜  me.magicly.performance git:(master) ✗ gcc -O2 -o mc-pi-c mc-pi.c
    ➜  me.magicly.performance git:(master) ✗ time ./mc-pi-c
    # of trials= 100000000 , estimate of pi is 3.14155
    ./mc-pi-c  1.22s user 0.00s system 99% cpu 1.226 total

    C++バージョン

    #include 
    #include  //defines rand(), srand(), RAND_MAX
    #include    //defines math functions
    
    using namespace std;
    
    int main()
    {
      const int SEED = 42;
      int interval, i;
      double x, y, z, pi;
      int inCircle = 0;
    
      srand(SEED);
    
      const int N = 100000000;
      for (i = 0; i < N; i++)
      {
        x = (double)rand() / RAND_MAX;
        y = (double)rand() / RAND_MAX;
    
        z = x * x + y * y;
        if (z < 1)
        {
          inCircle++;
        }
      }
      pi = double(4 * inCircle) / N;
    
      cout << "
    Final Estimation of Pi = " << pi << endl; return 0; }

    結果:
    ➜  me.magicly.performance git:(master) ✗ c++ --version
    Apple LLVM version 9.1.0 (clang-902.0.39.2)
    Target: x86_64-apple-darwin17.5.0
    Thread model: posix
    InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
    ➜  me.magicly.performance git:(master) ✗ c++ -O2 -o mc-pi-cpp mc-pi.cpp
    ➜  me.magicly.performance git:(master) ✗ time ./mc-pi-cpp
    
    Final Estimation of Pi = 3.14155
    ./mc-pi-cpp  1.23s user 0.01s system 99% cpu 1.239 total

    Juliaバージョン

    function pi(N::Int)
      inCircle = 0
      for i = 1:N
          x = rand() * 2 - 1
          y = rand() * 2 - 1
    
          r2 = x*x + y*y
          if r2 < 1.0
              inCircle += 1
          end
      end
    
      return inCircle / N * 4.0
    end
    
    N = 100_000_000
    println(pi(N))

    結果:
    ➜  me.magicly.performance git:(master) ✗ julia
                   _
       _       _ _(_)_     |  Documentation: https://docs.julialang.org
      (_)     | (_) (_)    |
       _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
      | | | | | | |/ _` |  |
      | | |_| | | | (_| |  |  Version 1.0.1 (2018-09-29)
     _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
    |__/                   |
    
    julia> versioninfo()
    Julia Version 1.0.1
    Commit 0d713926f8 (2018-09-29 19:05 UTC)
    Platform Info:
      OS: macOS (x86_64-apple-darwin14.5.0)
      CPU: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
      WORD_SIZE: 64
      LIBM: libopenlibm
      LLVM: libLLVM-6.0.0 (ORCJIT, haswell)
    
    ➜  me.magicly.performance git:(master) ✗ time julia mc.jl
    3.14179496
    julia mc.jl  0.85s user 0.17s system 144% cpu 0.705 total

    またRust開発環境のアップグレードに問題があり、うまくいかなかったが、これまでの経験からC++とは差が少ないと思う.
    githubでは、より多くの言語が含まれている比較を見つけました.興味があるのはhttps://gist.github.com/jmoir...を参考にすることができます.LuaJITはRustとあまり差がないほど速く、Julia公式サイトのbenchmarkと比較してhttps://julialang.org/benchma...に一致しています.
    さらに、2つのGoの同時バージョンが実現されました.
    package main
    
    import (
        "fmt"
        "math/rand"
        "runtime"
        "time"
    )
    
    type Job struct {
        n int
    }
    
    var threads = runtime.NumCPU()
    var rands = make([]*rand.Rand, 0, threads)
    
    func init() {
        fmt.Printf("cpus: %d
    ", threads) runtime.GOMAXPROCS(threads) for i := 0; i < threads; i++ { rands = append(rands, rand.New(rand.NewSource(time.Now().UnixNano()))) } } func MultiPI2(samples int) float64 { t1 := time.Now() threadSamples := samples / threads jobs := make(chan Job, 100) results := make(chan int, 100) for w := 0; w < threads; w++ { go worker2(w, jobs, results, threadSamples) } go func() { for i := 0; i < threads; i++ { jobs

    結果:
    ➜  me.magicly.performance git:(master) ✗ time go run monte_carlo.1.go
    cpus: 8
    PI: 100000000 times, value: 3.141778, cost: 2.098006252s
    MultiPI: 100000000 times, value: 3.141721, cost: 513.008435ms
    MultiPI2: 100000000 times, value: 3.141272, cost: 485.336029ms
    go run monte_carlo.1.go  9.41s user 0.18s system 285% cpu 3.357 total

    効率が4倍に向上したことがわかります.なぜCPUが8個あるのに4倍しかアップしていないのでしょうか?実は私のmacproは4コアで、8はハイパースレッドから出た仮想コアで、cpu密集計算では効率を高めることはできません.この記事を参照してください:物理CPU、CPUコア数、論理CPU、ハイパースレッド.
    次は、Juliaでパラレルを利用して効率をさらに高める方法を見てみましょう.
    知識星に参加して面白い技術の話題を共有することを歓迎します.