comp.lang.ada
 help / color / mirror / Atom feed
From: Maciej Sobczak <see.my.homepage@gmail.com>
Subject: C++ threads vs. Ada tasks - surprised
Date: Sun, 16 Aug 2009 14:34:11 -0700 (PDT)
Date: 2009-08-16T14:34:11-07:00	[thread overview]
Message-ID: <f02a0d10-acaa-47d3-ade8-2e7bd4030bd8@d4g2000yqa.googlegroups.com> (raw)

In another thread (pun intended) the discussion on language design
with regard to threads/tasks got obstructed, so I'm starting a new
one.

An important point that was discussed there was the potential gain
from having tasking built-in the language. I have argued that there is
none *functional* gain (there is an obvious gain in readability,
though), or rather that all compiler tricks can be performed with
standard API as well.

As I'm a pragmatist and had a couple of free minutes to spend, I have
written a very simple "benchmark" that uses a shared buffer with
capacity 1 (a unit buffer) and two tasks, one putting values into the
buffer and one consuming them. The idea was to test the performance of
synchronization and condition checks.
A special value in the buffer is used to finish the whole program (the
"poison pill" concept from another thread) and since only two
different values are needed, a Boolean type was used.
The number of iterations is given as an argument to the program (no
error checking is made as it is not the point here).

I have also written an equivalent program in C++ with the direct use
of POSIX threads API (again, no checks, etc.).

To be frank: I have expected to see the C++ version being a hell lot
of faster, as the Ada tasking model is conceptually much more complex
and therefore I have expected tons of overhead there.
My expectations were wrong, as the Ada version proved to be ~2x faster
on my machine (with GNAT 4.4.0 and g++ 4.4.0, taken from the same
package, both versions compiled with -O2).

I have no idea how to explain this other than with the "task morph"
that is allowed by standard to happen when calling protected entries,
so that the entry call is actually executed not by the task that is
calling and waiting, but rather by the task that happened to open the
relevant barrier. This could, in principle, reduce the number of
thread switches.
I would very much welcome an insight from some GNAT implementers to
confirm if that was actually possible here.

After this exercise I have no choice (frankly) than to admit that
built-in threading support is better *also* in terms of performance.
Damned benchmarks... ;-)

For the sake of completeness, here are the complete sources for both
versions. I welcome any suggestions for improving them, there is
always some possibility that I got something totally wrong...
(note: error checking was omitted by intention and both programs
*must* be called with a single positive parameter as the number of
iterations)

-- Ada version:
with Ada.Command_Line;
with Ada.Text_IO;

procedure Test is

   Iterations : Positive := Positive'Value
     (Ada.Command_Line.Argument (1));

   protected Buffer is
      entry Put (X : in Boolean);
      entry Get (X : out Boolean);
   private
      Value : Boolean;
      Full : Boolean := False;
   end Buffer;

   protected body Buffer is
      entry Put (X : in Boolean) when not Full is
      begin
         Value := X;
         Full := True;
      end Put;
      entry Get (X : out Boolean) when Full is
      begin
         X := Value;
         Full := False;
      end Get;
   end Buffer;

   task Producer;
   task body Producer is
   begin
      for I in 1 .. Iterations - 1 loop
         Buffer.Put (False);
      end loop;
      Buffer.Put (True);
   end Producer;

   task Consumer;
   task body Consumer is
      X : Boolean;
      Count : Natural := 0;
   begin
      loop
         Buffer.Get (X);
         Count := Count + 1;
         exit when X;
      end loop;
      Ada.Text_IO.Put_Line
        ("Executed " & Natural'Image (Count) & " iterations");
   end Consumer;

begin
   null;
end Test;

// C++ version (for POSIX systems):
#include <iostream>
#include <sstream>
#include <pthread.h>

class Buffer
{
public:
    Buffer()
    {
        pthread_mutex_init(&mtx_, NULL);
        pthread_cond_init(&full_, NULL);
        pthread_cond_init(&empty_, NULL);

        is_full_ = false;
    }

    ~Buffer()
    {
        pthread_mutex_destroy(&mtx_);
        pthread_cond_destroy(&full_);
        pthread_cond_destroy(&empty_);
    }

    void put(bool x)
    {
        pthread_mutex_lock(&mtx_);
        while (is_full_)
        {
            pthread_cond_wait(&empty_, &mtx_);
        }

        value_ = x;
        is_full_ = true;

        pthread_cond_signal(&full_);
        pthread_mutex_unlock(&mtx_);
    }

    void get(bool & x)
    {
        pthread_mutex_lock(&mtx_);
        while (not is_full_)
        {
            pthread_cond_wait(&full_, &mtx_);
        }

        x = value_;
        is_full_ = false;

        pthread_cond_signal(&empty_);
        pthread_mutex_unlock(&mtx_);
    }

private:
    bool value_;
    bool is_full_;

    pthread_mutex_t mtx_;
    pthread_cond_t full_;
    pthread_cond_t empty_;
};

Buffer buf;

int iterations;

void * producer(void *)
{
    for (int i = 0; i != iterations - 1; ++i)
    {
        buf.put(false);
    }
    buf.put(true);

    return NULL;
}

void * consumer(void *)
{
    int count = 0;
    bool x;
    while (true) {
        buf.get(x);
        ++count;
        if (x)
        {
            break;
        }
    }

    std::cout << "Executed " << count
        << " iterations" << std::endl;

    return NULL;
}

int main(int argc, char * argv[])
{
    std::istringstream ss(argv[1]);
    ss >> iterations;

    pthread_t producer_th;
    pthread_t consumer_th;

    pthread_create(&producer_th, NULL, producer, NULL);
    pthread_create(&consumer_th, NULL, consumer, NULL);

    pthread_join(producer_th, NULL);
    pthread_join(consumer_th, NULL);
}

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

Database Access Library for Ada: www.inspirel.com/soci-ada



             reply	other threads:[~2009-08-16 21:34 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-08-16 21:34 Maciej Sobczak [this message]
2009-08-17  7:12 ` C++ threads vs. Ada tasks - surprised Stephen Leake
2009-08-17  8:14   ` Maciej Sobczak
2009-08-17 19:44   ` vlc
2009-08-17  8:04 ` Frederik Sausmikat
2009-08-17  8:17   ` Maciej Sobczak
2009-08-17  8:28   ` Tomek Wałkuski
2009-08-17  9:39     ` Frederik Sausmikat
2009-08-17  9:44       ` Ludovic Brenta
2009-08-17 11:12 ` John McCabe
2009-08-17 15:20 ` John B. Matthews
2009-08-18 20:36 ` Ira Baxter
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox