In Go I can create goroutines like this (EDITED as reported by kelu-thatsall's answer):
// test.go
package main
import (
"fmt"
"os"
"strconv"
"sync"
"runtime"
)
func main() {
var wg sync.WaitGroup
if len(os.Args) < 2 {
os.Exit(1)
}
k, ok := strconv.Atoi(os.Args[1])
if ok != nil {
os.Exit(2)
}
wg.Add(k * 1000)
for z := 0; z < k*1000; z++ {
go func(x int) {
defer wg.Done()
fmt.Println(x)
}(z)
if z%k == k-1 {
// @mattn: avoid busy loop, so Go can start processing like BEAM do
runtime.Gosched()
}
}
wg.Wait()
}
The result in Go 1.8.0 (64-bit):
# shell
$ go build test.go ; for k in 5 50 500 5000 50000 500000; do echo -n $k; time ./test $k > /dev/null; done
5
CPU: 0.00s Real: 0.00s RAM: 2080KB
50
CPU: 0.06s Real: 0.01s RAM: 3048KB
500
CPU: 0.61s Real: 0.12s RAM: 7760KB
5000
CPU: 6.02s Real: 1.23s RAM: 17712KB # 17 MB
50000
CPU: 62.30s Real: 12.53s RAM: 207720KB # 207 MB
500000
CPU: 649.47s Real: 131.53s RAM: 3008180KB # 3 GB
What's the equivalent code in Erlang or Elixir? (EDITED as reported by patrick-oscity's comment)
What I've tried so far is the following:
# test.exs
defmodule Recursion do
def print_multiple_times(n) when n <= 1 do
spawn fn -> IO.puts n end
end
def print_multiple_times(n) do
spawn fn -> IO.puts n end
print_multiple_times(n - 1)
end
end
[x]=System.argv()
{k,_}=Integer.parse(x)
k=k*1000
Recursion.print_multiple_times(k)
The result in elixir 1.4.2 (erts-8.2.2):
# shell
$ for k in 5 50 500 5000 50000 ; do echo -n $k; time elixir --erl "+P 90000000" test.exs $k > /dev/null; done
5
CPU: 0.53s Real: 0.50s RAM: 842384KB # 842 MB
50
CPU: 1.50s Real: 0.62s RAM: 934276KB # 934 MB
500
CPU: 11.92s Real: 2.53s RAM: 1675872KB # 1.6 GB
5000
CPU: 122.65s Real: 20.20s RAM: 4336116KB # 4.3 GB
50000
CPU: 1288.65s Real: 209.66s RAM: 6573560KB # 6.5 GB
But I'm not sure if the two are equivalent. Are they ?
EDIT Shortened version as mudasobwa's comment does not give correct output
# test2.exs
[x]=System.argv()
{k,_}=Integer.parse(x)
k=k*1000
1..k |> Enum.each(fn n -> spawn fn -> IO.puts n end end)
The result for k in 5 50 500 5000 50000 ; do echo -n $k; time elixir --erl "+P 90000000" test.exs $k | wc -l ; done
:
5
CPU: 0.35s Real: 0.41s RAM: 1623344KB # 1.6 GB
2826 # does not complete, this should be 5000
50
CPU: 1.08s Real: 0.53s RAM: 1691060KB # 1.6 GB
35062
500
CPU: 8.69s Real: 1.70s RAM: 2340200KB # 2.3 GB
373193
5000
CPU: 109.95s Real: 18.49s RAM: 4980500KB # 4.9 GB
4487475
50000
erl_child_setup closed
Crash dump is being written to: erl_crash.dump...Command terminated by signal 9
CPU: 891.35s Real: 157.52s RAM: 24361288KB # 24.3 GB
Not testing 500m for elixir because it took too long and +P 500000000
argument is bad number of processes
I'm sorry guys but I'm not convinced that this code in Go is really working as expected. I'm not an expert, so please correct me if I'm wrong. First of all it prints z
which it seems is a current value of it in global scope (usually k*1000
) https://play.golang.org/p/a4TJyjKBQh
// test.go
package main
import (
"fmt"
"time"
)
func main() {
for z:=0; z<1000; z++ {
go func(x int) { // I'm passing z to the function with current value now
fmt.Println(x)
}(z)
}
time.Sleep(1 * time.Nanosecond)
}
And also if I comment out Sleep
the program will exit before even starting any goroutines (at least it doesn't print out the results). I would be happy to know if I'm doing something wrong, but from this simple example it seems the problem is not with Elixir
, but Go
code provided. Some Go
gurus out there?
I've also run some test on my local machine:
go run test.go 500 | wc -l
72442 # expected 500000
go run test.go 5000 | wc -l
76274 # expected 5000000