Scala and Akka actors with Prime Sieves

I had the chance to develop the problem of prime sieves in Scala. I took two approaches; (1) Scala Actors and (2) Akka Actors in Scala. The code for both is quite similar.

Scala version

import scala.actors.Actor

case class sieve(n: Int)
case class finish(n: Int)

class Sieve(val p: Int) extends Actor {

  var next: Sieve = null

  def _sieve(n: Int) = {
    if (isPrime(n)) {
      if (next != null) {
        next ! sieve(n)
      } else {
        next = new Sieve(n)
        next.start
        //println(n + " is prime")
      }
    }
  }

  private def isPrime(n: Int): Boolean = {
    val d = n / p
    val r = n - d * p
    if (r != 0) true else false
  }

  def act = {
   loop {
   react {
case sieve(n: Int) => _sieve(n)
case finish(n: Int) => {
if (next != null) {
next ! finish(n)
} else {
val time = System.currentTimeMillis - Main.start
println(n + "," + time)
// System.exit(0)
}
}
   }
   }
  }

}

object Generator {

  def generate(n: Int): Unit = {
    val p2 = new Sieve(2)
    p2.start
    for (i <- 3 to n) {
     p2 ! sieve(i)
    }
    p2 ! finish(n)
  }
}

object Main {

  var start = System.currentTimeMillis

  def main(args: Array[String]): Unit = {
for (i <- 1 to 10) {
start = System.currentTimeMillis
Generator.generate(i * 20000)
}
  }
}

Akka version

import akka.actor._
import akka.actor.Actor._

case class sieve(n: Int)
case class finish(n: Int)

class SieveAkka(val p: Int) extends Actor {

  var next: ActorRef = null

  def _sieve(n: Int) = {
    if (isPrime(n)) {
      if (next != null) {
        next ! sieve(n)
      } else {
        next = actorOf(new SieveAkka(n))
        next.start
        // println(n + " is prime")
      }
    }
  }

  private def isPrime(n: Int): Boolean = {
    val d = n / p
    val r = n - d * p
    if (r != 0) true else false
  }

  def receive = {
    case sieve(n: Int) => _sieve(n)
    case finish(n: Int) => {
     if (next != null) {
     next ! finish(n)
     } else {
     val time = System.currentTimeMillis - MainAkka.start
     println(n + "," + time)
     // System.exit(0)
     }
    }
    
  }

}

object GeneratorAkka {

  def generate(n: Int): Unit = {
    val p2 = actorOf(new SieveAkka(2))
    p2.start
    for (i <- 3 to n) {
     p2 ! sieve(i)
    }
    p2 ! finish(n)
  }
}

object MainAkka {

var start = System.currentTimeMillis

def main(args: Array[String]): Unit = {
for (i <- 10 to 10) {
start = System.currentTimeMillis
GeneratorAkka.generate(i * 20000)
}
}
}

Then I performed executions of the applications for n = 20`000-200`000. I used a dual core machine with 2GB of main memory. The performance of Akka is quite interesting. I also monitored the executions with VisualVM and it turned out for the execution Akka uses at most 28 threads for all the actors.

Scala vs Akka in prime sievesIt’s very interesting to see how this can be explained that the time for computations gradually converge. I think this is an observation on how Scala/Akka actors are scalable; i.e. when the number of actors increase, the cost of resources/computations are affected in the same order.

Update on 02/21/2012. I took another approach in running the examples. Instead of having a main loop to generate all the prime sieves, each time the program was passed an argument to generate the prime sieves for. It turned out that the results now make more sense, however, I have no idea on how this could change this behavior. A snapshot of the Akka performance is as follows which complies with being close to exponential growth.

Akka performance for prime sieves with N=3.8M

Multicore Programming and Object-Oriented Languages

I had a literature study on how different object-oriented languages provide programming techniques for multicore programming. The outcome was presented as a research report and a presentation talk.

مطالعه‌ای بر روی برنامه‌نویسی چند‌هسته‌ای در زبان‌های شی‌گرا انجام دادم که نتیجه‌‌ش به صورت یک گزارش و یک ارائه در این زمینه در‌آمد.

 

Design and implementation of a programming language on virtual machines

Regardless of the target runtime, a language is first specified [1] with a set of formal syntax and semantics usually known as the grammar of the language. The most important goal of a language specification is to create a mutual grounds for understanding the language, the discussion and its implementation. Accordingly, a language implementation is essentially creating another software system to run the programs that are expressed according to the specification [2]. There are two general schemes in this regards, namely, translation and interpretation. In translation, the program in directly translated into the underlying machine understandable code while in interpretation the program is basically run line by line through a runtime environment. Moreover, some languages such as Java take advantage of both methods; here comes the concept of a virtual machine [3].

Classically, a language comes in with a set of tools including a compiler or an interpreter. The programmer writes a program and then compiles the program into the machine-understandable code that is directly executable. However, in the case of VM languages, in the first pass, the programmer, the programmer translates (compiles) the code into the intermediate bytecode [4] understandable for the virtual machine. In the second pass, the virtual machine is responsible to run the bytecode on the underlying platform which is a form of interpretation approach. The first pass is the same and produces the same byte code on all platforms; so, the programming language becomes platform-independent. However, for the language designers, this creates another task of implementing a virtual machine for all target platforms and OS’s.

Now, VM languages such as Java have created a good platform in a way that they have become a primary target in design and implementation of new programming languages. It means that instead of implementing the new language directly to the machine code, designers tend to create output based on the language that is compatible with JVM; so the JVM will take care of the execution of the program. Here rises two different approaches in this regard:

  1. Directly create bytecode for JVM
  2. Create Java sources and then compile them into bytecode for JVM

Case Study: Groovy

Groovy is a dynamic runtime language that supports and favors functional programming paradigm and now is widely used in domain specific applications. It is interesting to take a look at its design and implementation. Groovy takes  good use of ANTLR [5] that is a set of tools for language processing. For instance, Groovy language grammar and syntax is specified using ANTLR grammar syntax language. Based on this grammar, ANTLR can produce various tools including a lexer and a parser. The lexer and parser are both of the fundamental elements required to write a compiler for each language. Another advantage of ANTLR is that it gives the option to what to produce with respect to the grammar provided. One of the options is the abstract syntax tree (AST) [6] that is an intermediate data structure very useful during the parsing and compiling of a source code [7]. Groovy compiler, after it has scanned the source code of the program, receives an instance of AST for the program that is provided through a parser plugin generated by ANTLR. Briefly, the compilation of a source code unit in Groovy is as follows:

  1. Scanning the source and parsing it using the parser plugin generated by ANTLR based on the grammar
  2. Obtaining the AST instance for the source code unit
  3. Applying additional phases such as code optimization, code semantic analysis and verification
  4. Generating output

The whole process is heavily based on Visitor pattern [8]. In step 4, the Groovy compiler uses visitor pattern and another library called ASM [9] to generate bytecode for JVM. ASM is a library that helps manipulate or dynamically generate bytecode; i.e. “.class” files that are instruction sets for JVM. In Groovy compiler, the AST instance is visited throughout, and in each node of the tree, as each node is known to have a specific representation as the JVM bytecode, thus after visiting all the nodes in the AST, the root of the tree can collect all the bytecode for the whole source unit. This is a very neat and modular way of creating a language on top of Java Virtual Machine. In addition, Groovy compiler also has the option to generate the Java source along the bytecode. It is straightforward that having the AST instance, there are a number of things that can easily done.

Case Study: Scala

Scala is a dynamic functional scalable language based on Java. In contrast with Groovy, they both run on JVM. However, speaking of language implementation, Scala takes another interesting approach. As Scala is a dynamic functional language, it takes advantage of this feature in the compiling the source units. Specifically, Scala introduces its own parser actually declared in Scala language. The parsers declares all the syntax rules that are defined in the language; there is also a repository for all the rules in Scala language [10]. Apart from this, Scala introduces a compiler and an interpreter for the language.

Finally, the case studies show that are things to decide apart from the classical ones in language implementation on top of a virtual machine such as JVM. Groovy and Scala each takes a different approach; while different, each shows to have its advantages and applications.

References

  1. [1]: http://en.wikipedia.org/wiki/Programming_language_specification
  2. [2]: http://en.wikipedia.org/wiki/Programming_language_implementation
  3. [3]: http://en.wikipedia.org/wiki/Virtual_machine
  4. [4]: http://en.wikipedia.org/wiki/Bytecode
  5. [5]: http://www.antlr.org/
  6. [6]: http://en.wikipedia.org/wiki/Abstract_syntax_tree
  7. [7]: http://www.antlr.org/wiki/display/ANTLR3/Interfacing+AST+with+Java
  8. [8]: http://en.wikipedia.org/wiki/Visitor_pattern
  9. [9]: http://asm.ow2.org/
  10. [10]: http://code.google.com/p/scala-rules/

برنامه نویسی چند‌هسته‌ای – Multicore Programming

در مسیر پیدا‌کردن موضوعی برای پایان‌نامه‌ی فوق لیسانس، به طور خیلی اتفاقی با موضوعی آشنا شدم به نام برنامه‌نویسی چند‌هسته‌ای که چند سالیه موضوع داغی برای تحقیقات در زمینه‌ی زبان‌های برنامه‌نویسی و تولید و توسعه‌ی نرم‌افزار محسوب می‌شه. این داستان از این‌جا جالبه که ایده‌های برنامه‌نویسی موازی از سال‌ها پیش مطرح بود اما در همون زمان به دلیل نداشتن امکانات سخت‌افزاری و قدرت پردازشی لازم روش‌های برنامه‌نویسی و زبان‌های برنامه‌نویسی با دیدگاه تک‌واحدی بودن پردازنده شروع به رشد کردند. از سال ۲۰۰۰ به بعد که سخت‌افزار‌ها از نظر قدرت پردازشی به شدت پیشرفت کردند و مفهوم چند‌هسته‌ای بودن یک پردازنده الان خیلی عادی می‌آد، چندسالیه که این موضوع قدیمی دوباره به روی میز اومده و خیلی‌ها به دنبال این هستند که چه طور نرم‌افزار باید برای این دوره آماده بشوند.

مقایسه‌ی مشابهی در این زمینه وجود داره که زمانی که ساختار و چارچوب شی‌ءگرا مطرح شده و دنیای نرم‌افزار هم به این سمت رفت که از این ساختار بیشتر استفاده کنه، تاریخ نشون می‌ده که بخشی از موضوع به پیاده‌سازی فرهنگ استفاده از این ساختار مربوط بوده؛ به این معنی زمانی طول کشیده تا برنامه‌نویسان در مرحله اول متقاعد بشوند که این ساختار بهتر از ساختار‌های ساخت‌یافته و قدیمی‌تر بودند و در مرحله این تحول ایجاد شد که تقریباً تمام برنامه‌نویسان از ابتدایی که شروع به فکر و نوشتن برای یک نرم‌افزار می‌کنند، راه‌حل‌ها و ایده‌های خود را در این چارچوب ارائه بدهند. در مورد برنامه‌نویسی چند‌هسته‌ای هم در حال حاضر به نظر می‌آید که این تردید در جامعه‌ی برنامه‌نویس وجود دارد و زمانی طول خواهد کشید تا در آینده با این دیدگاه به سراغ حل مسائل و نوشتن نرم‌افزار بروند.

از سوی دیگر، بررسی راه‌حل‌های موجود نشان می‌دهد که تلاش‌های فراوانی در این زمینه صورت گرفته‌است؛ این تلاش‌های اکثراً یا به شکل زبان‌های جدید مبتنی برزبان‌های شیءگرا هستند یا به شکل کتابخانه‌های تکمیلی برای زبان هدف. نیز، دیدگاه‌های جدیدی هم در این زمینه مطرح‌ شده مانند استفاده از مدل Actor یا Software Transactional Memory که هر دو دارای خوبی‌ها و بدی‌هایی هم هستند.از طرف دیگر، دیدگاه‌های توصیفی (Declarative) در مقابل دستوری (Imperative) هم خود مایه‌ای برای تفاوت در تلاش‌های تحقیقاتی در این زمینه می‌باشد. نکته‌ی جالب این است که تقریباً تمام تحقیقات این موضوع به اتفاق از برنامه‌نویسی وظیفه‌گرا (Functional Programming) حمایت می‌کنند.

یکی از چالش‌های مطرح در این شاخه، انتقال (Migration) سیستم‌های حال حاضر در شرکت‌های بزرگ به بستر‌های چند‌هسته‌ای است. اهمیت این چالش در این است که خیلی‌ها به دنبال روش‌هایی هستند که یا بدون تغییر نرم‌افزار یا با کم‌ترین تغییرات این انتقال بستر را انجام دهند. به همین دلیل، به طور مثال، تلاش‌های خیلی وسیعی در این زمینه بر روی بستر جاوا (Java) انجام شده که به طور مثال این موضوع در زبانی مانند اسکالا (Scala) و با استفاده از Actorها و یا در کتابخانه‌ای مانند Multiverse از طریق Software Transactional Memory مورد تحقیق قرار گرفته‌است. در همین راستا، این موضوع نیز جالب است که زبان‌های برنامه‌نویسی در چه سطحی از دستورالعمل‌ها گرفته تا اشیاء برنامه این فناوری را پشتیبانی می‌کنند که هر کدام کاربرد‌های خود را دارند.

در این زمینه کتابی به نام The Art of Multiprocessor Programming کتابی خوب با محتوای مناسب برای آشنایی عمیق با این موضوعات می‌باشد.