{"id":16372508,"url":"https://github.com/mgwidmann/elixir-scala-starvation-test","last_synced_at":"2026-03-13T20:30:18.309Z","repository":{"id":21597902,"uuid":"24918049","full_name":"mgwidmann/elixir-scala-starvation-test","owner":"mgwidmann","description":"A simple test between starving actors in Elixir and Scala","archived":false,"fork":false,"pushed_at":"2014-10-08T00:54:11.000Z","size":120,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-31T17:23:06.493Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mgwidmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-10-08T00:36:55.000Z","updated_at":"2022-08-26T08:19:23.000Z","dependencies_parsed_at":"2022-08-21T05:40:13.194Z","dependency_job_id":null,"html_url":"https://github.com/mgwidmann/elixir-scala-starvation-test","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgwidmann%2Felixir-scala-starvation-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgwidmann%2Felixir-scala-starvation-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgwidmann%2Felixir-scala-starvation-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgwidmann%2Felixir-scala-starvation-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgwidmann","download_url":"https://codeload.github.com/mgwidmann/elixir-scala-starvation-test/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239921877,"owners_count":19718842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T03:11:36.634Z","updated_at":"2026-03-13T20:30:18.234Z","avatar_url":"https://github.com/mgwidmann.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"Starvation\n==========\n\nMy personal interests lies with Elixir, but recently work has pushed our team towards Scala (and away from Ruby wtf?).\n\nSo I thought I'd check into potential actor starvation and how they work between the two actor based systems. The test is to make an actor come alive, +1 a counter, send the new count value to a newly spawned actor and have the current actor then sleep waiting. To make it scale terribly, the maximum count value will be used as the sleep time.\n\n## So heres the code, Elixir first of course!\n\n```elixir\ndefmodule Starvation do\n\n  def count(max, start \\\\ now) when is_integer(max) do\n    spawn fn -\u003e\n      receive do\n        i when max \u003e= i -\u003e\n          IO.puts \"#{inspect self} Received #{i}\"\n          actor = count(max, start)\n          send(actor, i + 1)\n          IO.puts \"#{inspect self} Sleeping for #{max} ms\"\n          :timer.sleep(max)\n          IO.puts \"#{inspect self} Actor exiting...\"\n        i -\u003e\n          done = now\n          IO.puts \"Done at #{i} in just #{(done - start)} s\"\n      end\n    end\n  end\n\n  def now, do: :calendar.datetime_to_gregorian_seconds(:calendar.universal_time)\n\nend\n```\n\nFire up iex, and start this thing off! We increase the number of processes allowed so we don't crash... 10 million is overkill but it should do!\n\n```bash\n$ iex --erl \"+P 10000000\" -S mix\nErlang/OTP 17 [erts-6.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]\n\nCompiled lib/starvation.ex\nGenerated starvation.app\nInteractive Elixir (1.0.0) - press Ctrl+C to exit (type h() ENTER for help)\niex(1)\u003e counter = Starvation.count(1_000_000)\n#PID\u003c0.88.0\u003e\niex(2)\u003e send(counter, 0)\n#PID\u003c0.88.0\u003e Received 0\n0\n#PID\u003c0.90.0\u003e Received 1\n#PID\u003c0.88.0\u003e Sleeping for 1000000 ms\n#PID\u003c0.90.0\u003e Sleeping for 1000000 ms\n#PID\u003c0.91.0\u003e Received 2\n#PID\u003c0.91.0\u003e Sleeping for 1000000 ms\n... Removed for brevity ...\n#PID\u003c0.1245.61\u003e Received 999999\n#PID\u003c0.1245.61\u003e Sleeping for 1000000 ms\n#PID\u003c0.1246.61\u003e Received 1000000\n#PID\u003c0.1246.61\u003e Sleeping for 1000000 ms\nDone at 1000001 in just 45 s\niex(3)\u003e\n```\n\nAwesome! 45 seconds my iMac with 8 cores!\n\n## Ok Scala, time to step up to the plate!\n\nBasically the same thing, only Scala likes to use objects for actors, so its done slightly differently. (Note: I'm still new to Scala, so if anyone sees any improvements please let me know, I know this is probably the wrong place to ask but just sayin)\n\n```scala\nimport scala.actors.Actor\nimport scala.actors.Actor._\n\nclass Starvation(max: Int, start: Long) extends Actor {\n  def act() = {\n    receive {\n      case i : Int if max \u003e= i =\u003e\n        print(Thread.currentThread + \": Received \" + i)\n        val s = new Starvation(max, start)\n        s.start\n        s ! i + 1\n        println(\" ... starting new actor and sleeping for \" + max + \"ms\")\n        Thread.sleep(max)\n        println(Thread.currentThread + \" actor exiting...\")\n      case i : Int =\u003e\n        println(Thread.currentThread + \": Done in \" (System.currentTimeMillis() - start) + \"ms\")\n    }\n  }\n}\n```\n\nOk, in the scala terminal:\n\n```bash\n$ scala\nWelcome to Scala version 2.11.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_65).\nType in expressions to have them evaluated.\nType :help for more information.\n\nscala\u003e :paste\n// Entering paste mode (ctrl-D to finish)\n\n( Pasted above code in here)\n\nval s = new Starvation(1000000, System.currentTimeMillis())\ns.start\ns ! 0\n\n// Exiting paste mode, now interpreting.\n\nwarning: there was one deprecation warning; re-run with -deprecation for details\nThread[ForkJoinPool-2-worker-15,5,main]: Received 0 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-11,5,main]: Received 1 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-7,5,main]: Received 2 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-3,5,main]: Received 3import scala.actors.Actor\nimport scala.actors.Actor._\ndefined class Starvation\ns: Starvation = Starvation@4641439f\n ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-31,5,main]: Received 4 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-27,5,main]: Received 5 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-23,5,main]: Received 6 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-19,5,main]: Received 7 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-1,5,main]: Received 8 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-29,5,main]: Received 9 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-25,5,main]: Received 10 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-21,5,main]: Received 11 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-17,5,main]: Received 12 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-13,5,main]: Received 13 ... starting new actor and sleeping for 1000000ms\n\nscala\u003e Thread[ForkJoinPool-2-worker-9,5,main]: Received 14 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-5,5,main]: Received 15 ... starting new actor and sleeping for 1000000ms\n```\n\nOk so it stopped at 15 and won't go any higher (which is conveniently number of cores * 2 since we started with 0), it just hangs for a while. All the threads are locked to the actors and everything is waiting on the sleeps (which is over 15 minutes, ugh).\n\nScala allows two different types of receiving messages, receive and react. The major difference is receive locks the actor to the thread because it maintains the current stack frame. So using react should theoretically fix the problem.\n\nUsing react:\n\n```\nThread[ForkJoinPool-2-worker-29,5,main]: Received 0 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-15,5,main]: Received 1 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-25,5,main]: Received 2 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-21,5,main]: Received 3 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-17,5,main]: Received 4 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-9,5,main]: Received 5 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-13,5,main]: Received 6 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-1,5,main]: Received 7 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-19,5,main]: Received 8 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-5,5,main]: Received 9 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-11,5,main]: Received 10 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-7,5,main]: Received 11 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-3,5,main]: Received 12 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-31,5,main]: Received 13 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-27,5,main]: Received 14 ... starting new actor and sleeping for 1000000ms\nThread[ForkJoinPool-2-worker-23,5,main]: Received 15 ... starting new actor and sleeping for 1000000ms\n```\n\nAgain... Stuck at 15. At this rate, with 1,000,000 actors to go through, processing at a rate of 16 at a time (assuming the processing time is actually nothing). 1 million milliseconds is 16.6667 minutes so:\n\n#### (1,000,000 actors / 16 at a time) * 16.6667 minutes = 1,041,668.75 minutes which converts to 1.98055 years!\n\nRunning the same test for 1,000 being the max count: Elixir finishes in 4 seconds and Scala sluggishly finishes in a pathetic 62 seconds.\n\nI'm aware this comparison may be a bit unfair in that it could be caused by the simple fact in how sleeping works between the two virtual machines. I'm unsure however what a good way to compare the two inserting an artificial delay in the actors when they for whatever reason decide to hog CPU time. With that said, I believe the difference being that the Erlang VM is aware of its actors and so can easily pause them at any point of execution regardless of what its doing (sleep or something else). The JVM and Scala by extension, must wait for the actor to terminate before moving onto another actor, which means once actor contention gets above number of cores * 2, throughput will drop dramatically (or maybe even completely stall). This seems like a surprisingly small limit and may be configurable as I haven't checked.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgwidmann%2Felixir-scala-starvation-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgwidmann%2Felixir-scala-starvation-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgwidmann%2Felixir-scala-starvation-test/lists"}