Soirée FastConnect avec Software AG / Terracotta

Le Mardi 22 Janvier 2013 s’est déroulée une soirée FastConnect, et nous avons eu le plaisir d’accueillir Software AG / Terracotta pour nous parler de EhCache / BigMemory.

Personnellement, je trouve que cette solution répond à pas mal de besoins que je rencontre chez les clients.
De plus, la force de la solution est de pouvoir commencer avec un “petit cache” à l’aide d’EhCache (opensource, et qui s’intègre avec Hibernate ou Spring), puis bénéficier du cache “off-heap” avec BigMemoryGO, puis le rendre distribué avec BigMemoryMax.

600_198449722

A titre d’information, cette solution a été mise en production chez un client pour de la détection de fraude en temps réel, avec un volume de 16To en mémoire! Le mot “BigData” prend tout son sens.

Capture d’écran 2013-01-24 à 16.14.58

Je ne vous en dit pas plus, et je vous laisse découvrir EhCache / BigMemory à travers la documentation officielle.

GigaSpaces Document API and JAXb

I wrote this article and the plugin that ships with it last year when GigaSpaces 8.0 was released and realized that I never finally published both the sources and article so here it is!

GS8 introduced a new way to store data in a flexible manner: the space document.

Document is an evolutive object storage that allow users to dynamically add or remove properties and even dynamically add indexes on any property.

Some of my clients uses XML to model their data and where quite happy to be able to seamlessly be able to make evolve their Space object as easily (or even more easily) as their XML schema.

As this need sounded quite interesting to me I build a small plugin for JAXb that allows to generate Document compliant classes directly from your XML Schemas.

You can download the source code and test it from here: https://github.com/fastconnect/jaxb-gigaspaces

21 juin 11 – Soirée GigaSpaces au NoSQL User Group

GigaSpaces XAP ! Ce nom vous dit quelque chose ? Certainement car c’est ce que vous allez apprendre si vous êtes inscrit(e) à la session de formation publique GigaSpaces XAP.

Mais si vous n’êtes pas (encore) inscrit(e) à la formation et vous voulez savoir de quoi il s’agit ? Je vous invite alors à venir à la soirée GigaSpaces au NoSQL User Group qui sera assurée par Gauvain, le directeur de notre département Conseil et Solutions.

Ci-dessous les coordonnées de l’événement :

Lieu : OCTO Technology, 50 Avenue des champs Elysees, 75008 PARIS
Date : 21 juin 2011 – 19h
Pas d’inscription  nécessaire



Venez nombreux !

Formation publique GigaSpaces XAP en Juin : et pourquoi pas vous ?

Vous êtes développeur, architecte, chef de projet, journaliste spécialisé, blogger, passionné par les architectures distribuées et/ou les concepts Cloud, ou simple curieux ?

Vous êtes dans un des cas suivants :

  • Vous avez toujours rêvé de comprendre comment on peut construire des applications à hautes performance aptes à monter en charge aisément; c’est à dire qui ne nécessitent pas de réinventer la roue à chaque fois que votre application doit évoluer, en nombre d’utilisateurs, en volumes de données, en nombre de requêtes…
  • Vous percevez que la technologie GigaSpaces XAP peut vous aider dans votre projet, mais vous avez besoin d’en savoir plus, parce que quand même ça parait trop beau…
  • On vous a demandé de monter en compétences sur GigaSpaces XAP, mais les quelques centaines de pages de la doc vous effraient, et vous ne savez pas par quel bout attaquer la bête.
  • Vous voulez comprendre pourquoi ces allumés chez FastConnect pensent que c’est une alternative à moindre coût avec un meilleur rapport prix/performance, aux plates-formes JEE (accompagnée de leur lot de composants additionnels et de coût d’intégration)
  • Vous vous demandez qui sont les personnes qui se cachent derrière ce blog et vous viendriez bien voir ça de vos propres yeux…
    • Et vous n’avez pas peur de faire évoluer votre perception des architectures distribuées !

      N’hésitez plus un seul instant, et venez suivre la formation publique, officielle, sur la plate-forme GigaSpaces XAP. Cette formation est réalisée dans le cadre du programme GigaSpaces University et est animée par des experts FastConnect confirmés et certifiés par GigaSpaces !

      Pour vous inscrire, rien de plus simple, c’est là : http://www.amiando.com/GigaSpacesPublicTrainingEUJune2011.html

      Cette formation sera animée par Luc, notre gourou sur les architectures distribuées qui compte à son actif quelques dizaines de missions d’expertise dans ce domaine depuis plusieurs années autour des problématiques de montée en charge, de performance, de répartition de charge, de résilience, et j’en passe.

      Enfin, FastConnect étant organisme de formation officiel, cette session de formation peut rentrer dans le cadre du programme de formation de votre entreprise. Contactez nous à training ( a t ) fastconnect.fr pour plus d’informations à ce sujet.

      N’attendez pas pour vous inscrire. Le nombre de places est limité.

      Au plaisir de vous voir !

      Optier annonce l’obtention d’un brevet pour sa technologie ACT

      Comme l’a annoncé récemment notre président dans ses voeux pour la nouvelle année 2011, nous continuons le développement de nos activités sur des solutions innovantes avec de nouveaux partenaires technologiques.

      Cette fois-ci les mots “solutions innovantes” s’appliquent parfaitement à Optier qui vient d’annoncer l’obtention d’un brevet pour sa technologie Active Context Tracking.

      ACT est un composant clé de la solution Business Transaction Management d’Optier  qui apporte une approche orientée métier en plus de l’Application Perfomance Management. En effet Optier BTM est capable de proposer :

      • La découverte automatique de la topologie applicative à travers laquelle passent les transactions
      • Le mapping transaction technique, transaction métier qui représente une précieuse aide à la communication entre IT/métier (et application owner), et permet la mise en place de SLAs orientés métier

      Cette solution fournit alors une vue complète sur les transactions de l’infrastructure, permettant ainsi d’isoler et de détecter les problèmes de performance de transactions métier, y compris leur cause et leur impact sur l’activité.

      En résumé, ci-dessous quelques fonctionnalités importantes de cette solution :

      • Garantir la qualité des flux transactionnels dans les applications et dans l’infrastructure IT, sans goulets d’étranglement ni interruption de service, pour une expérience utilisateur améliorée
      • Présenter la vision ‘bout-en-bout’ de toutes les transactions business réelles, à travers tous les tiers, 24h/7
      • Identifier en continu et automatiquement les relations entre les composants IT et les services métier
      • Donner la priorité aux transactions IT en fonction des besoins métier
      • Optimiser l’IT pour réduire les coûts tout en répondant aux objectifs des niveaux de services

      OpTier a obtenu des brevets pour sa technologie ACT également dans l’Union Européenne, la Chine et l’Australie. D’autres sont en attente dans d’autres pays.

      Pour plus d’information sur cette solution, je vous invite à visiter les pages suivantes :
      http://www.optier.com/products_technology.aspx
      http://www.optier.com/btm-compared-to-apm.aspx

      Java Finalization is Bullshit

      The purpose of this article is to speak about some poorly understood mechanisms of the Java Virtual Machine. If you already know all these mechanisms, you should contact our RH to work at FastConnect ;-)

      So please, consider the following program. If we run it with a java heap limited to 8Mb (thanks to the option -Xmx8m), it will fail with an ‘OutOfMemoryException’. But it will happen some time after 1000 loops ; other times after only 100 loops. It seems random.

      class Padding
      {
      	private byte[] padding;
      	Padding() {
      		this.padding = new byte[1024*1024]; //1Mbyte
      	}
      	protected void finalize() throws Throwable {
      		this.padding[0] = this.padding[1];
      	}
      }
      
      public class Main
      {
      	public static void main(String[] args) throws Exception {
      		int cpt = 0;
      		try {
      			for(;; ++cpt) {
      				new Padding();
      			}
      		} catch (OutOfMemoryError oom) {
      			System.err.println("oom after "+cpt+" loops.");
      			throw oom;
      		}
      	}
      }
      

      Here a little quiz. Can you explain this behavior? Is there a leak in the program? Why the number of iterations before the OOM is random?

      Before reading the following, I suggest you try to run this little program by yourself. Maybe you will have to increase the size of the padding array to reproduce the described behaviour.

      The first question is not very difficult. The random behavior is due to the garbage collector. To provide good performance, recent Java VMs manage the memory at the same time as your code is executed. That’s why if the Java heap is not big enough it is possible to have an OOM even if there is no leak, even if the marked objects are always smaller than the java heap…

      That’s why you must never be stingy with memory. A java program needs to have a lot of free memory to maximise safe and efficient memory management.

      The next questions are more tricky. If we remove the ‘finalize method’, the program works fine, even with a very small heap. Can you explain why?

      class Padding
      {
      	private byte[] padding;
      	Padding() {
      		this.padding = new byte[1024*1024]; //1Mbyte
      	}
      }
      
      public class Main
      {
      	public static void main(String[] args) throws Exception {
      		for(;;) {
      			new Padding();
      		}
      	}
      }
      

      Again, I suggest you try to run this program. If you open ‘jconsole’ or ‘jvisualvm’, you should see a very abnormal memory activity: the curve is flat (but few very rare accidents). So, I have two questions:

      • Why the curve of this very simple program is so flat?
      • Why the existence of a ‘finalize method’ disrupts the memory management.

      As a java expert, I expect you already read (several times) the documentation about the java garbage collector, its tuning and its algorithms.

      The default GC uses a generational algorithm where the allocation in Eden generation is a simple stack allocation. With our simple program, Padding objects are never promoted from the young generation to the tenured generation during the collection. It means that objects are fastly allocated in stack. At some point later the Copy GC is triggered and it copies nothing. In this particular – but not rare – situation the java memory management is more efficient than the C++ malloc because allocations and deallocations are held in stack and batched.

      I hope my explanations were clear. If not, you should read the following article: Java theory and practice: Urban performance legends, revisited.

      But we have not answered the last and most tricky question: why the existence of a ‘finalize method’ disrupts the memory management. A hint is in the title of this article. Please consider the following program:

      class Padding
      {
      	static java.util.Set<Padding> retention = new java.util.HashSet<Padding>();
      	private byte[] padding;
      	private int id;
      	Padding(int id) {
      		this.id = id;
      		this.padding = new byte[1024*1024]; //1Mbyte
      	}
      	protected void finalize() throws Throwable {
      		System.err.println("finalize is called for Padding[id="+id+"]");
      		retention.add(this);
      	}
      }
      
      public class Main
      {
      	public static void main(String[] args) throws Exception {
      		int cpt = 0;
      		try {
      			for(;; ++cpt) {
      				new Padding(cpt);
      			}
      		} catch (OutOfMemoryError oom) {
      			System.err.println("oom after "+cpt+" loops.");
      			throw oom;
      		}
      	}
      }
      

      Again I suggest you test it several times with different heap size (-Xmx64m for example). Its behaviour is very stable: it fails all of the time at the same moment.

      On blogs and forums, I often read “finalize is called when the object is reclaimed”. This is wrong! When a finalizable object is no more referenced, the JVM adds it to the JVM’s finalization queue. At some point later, the JVM’s finalizer thread will dequeue it, call its ‘finalize method’, and record that its finalizer has been called. It is only when the garbage collector rediscovers that the object is unreachable that it reclaims it.

      This behaviour is a problem: it makes the code of the Garbage Collector very complex, it is error prone for the JVM providers and it consumes a lot of memory. It is the main reason why the Finalization is not in the Java specification for mobile phones and (credit) card.

      Elsewhere there is no good usage of the Java Finalization. For example, if you close sockets thanks to a ‘finalize method’, you will have a big problem if you have a lot of free memory but not enough file descriptors. Garbage Collectors are good for managing the managed memory and only that.

      You can refer to this article which explains how finalization works and how not to use it.

      Cyril Martin (mcoolive).

      Les fonctions récursives terminales

      Il y a quelques temps déjà, je discutais avec un collègue dans ce lieu de socialisation qu’est la machine à café. Nous ne parlions pas de notre travail, mais nous parlions tout de même d’informatique. Nous eûmes l’échange suivant :

      • Les langages fonctionnels, c’est nul ; les fonctions récursives ça peut casser la pile d’exécution.
      • Bah non, si tu utilises une fonction récursive terminale…
      • Une quoi ?

      Les mots ne sont pas exacts, mais je crois être fidèle à nos intentions. Depuis j’ai eu d’autres occasions de vérifier que cette notion de programmation était souvent très mal connue. D’où mon idée d’écrire un article sur le sujet.

      Qu’est-ce qu’une fonction récursive terminale ?

      Une fonction récursive terminale est une fonction qui lorsqu’elle fait appel à elle-même, le fait en tout dernier. Concrètement il n’y a pas de calcul entre l’appel récursif et l’instruction return. Le terme anglais est « tail-end recursion ».

      Il s’agit donc d’une manière d’écrire votre fonction récursive.

      À quoi ça sert ?

      Cela sert à produire du code efficace, clair, et qui ne risque pas de faire déborder la pile d’exécution.

      D’un point de vue algorithmique, une fonction récursive terminale est équivalente à une simple boucle. Écrire ainsi vos fonctions récursives permettra au compilateur d’optimiser efficacement le code produit.

      Dans un langage impératif, le programmeur a souvent l’idée d’utiliser une boucle, donc la question est moins importante. En revanche, dans un langage fonctionnel comme OCaml ou parallelC#, qui vous invite par sa syntaxe et son esthétisme à user de fonctions, il est primordial d’écrire des fonctions terminales.

      Un exemple commenté : la fonction ‘factorielle’ en langage C

      Pour illustrer mon propos, j’ai choisi de commenter plusieurs implémentations de la fonction « factorielle », écrites en langage C.

      La plupart des programmeurs (dé)formés par des années de pratique du C, préfèrent utiliser une boucle for :

      int factorial(int n) {
          int accu=1;
          for(; n; --n) accu *= n;
          return accu;
      }
      

      Ce code fonctionne parfaitement bien. Il a l’unique défaut d’être finalement assez éloigné de la définition mathématique de la factorielle, c’est-à-dire que la plupart des gens le jugeront peu naturel.

      Considérons maintenant une implémentation plus naturelle, très proche de la définition apprise sur les bancs d’école. C’est aussi la façon dont la plupart des programmeurs débutants écrivent cette fonction :

      int factorial (int n) {
          if (n == 0) return 1;
          else return n * factorial(n - 1);
      }
      

      Cette implémentation récursive est assurément très mauvaise. Elle consomme beaucoup de mémoire en pile, donc c’est lent et ça peut aussi se terminer prématurément si on l’appelle avec un paramètre trop grand (stack overflow). La version récursive terminale pallie précisément ce problème :

      int factorial (int n, int accu) {
          if (n == 0) return accu;
          else return factorial(n - 1, n * accu);
      }
      

      C’est une tail-end recursion car l’appel récursif est le dernier statement. Vous avez certainement remarqué que pour ce faire, j’ai dû introduire un paramètre supplémentaire ; ceci est discuté plus loin.

      Comment ça marche ?

      Comme vous le savez certainement, lorsque l’on effectue un appel de fonction, la fonction appelante stocke sur la pile d’exécution les paramètres de la fonction appelée et l’adresse de retour. La fonction appelée utilise ses paramètres pour effectuer ses calculs et, à la toute fin, saute à l’adresse précédemment sauvegardée. On revient alors dans le code de la fonction appelante qui dépile avant de continuer…

      Sachant cela, il est facile de comprendre qu’un grand nombre d’appels de fonctions imbriqués fait grossir la pile, et que les fonctions récursives représentent un risque important de débordement de pile (stack overflow).

      L’optimisation opérée par le compilateur consiste à produire un code qui, lors de l’appel récursif, au lieu d’empiler… et de dépiler plus tard, va remplacer les paramètres actuels et simplement boucler. Ceci est possible dans le cas particulier d’une fonction récursive terminale car il n’y a plus rien à faire dans la fonction appelante.

      En fait cette optimisation n’est pas propre aux fonctions récursives. Toute fonction se terminant par un appel de fonction (appel récursif ou pas) est susceptible de profiter de la même optimisation. On parle de sibling call ou tail call.

      Un dessin valant mieux que des mots, voici un exemple d’optimisation de tail call sur une machine windows x86 : la méthode foo se termine par un appel à la méthode bar, donc on libère la place réservée pour l’exécution de foo avant d’exécuter bar.

       As soon as foo          While foo        When foo is about to
         is called            is executing        tail call to bar
      
                            |              |                     
                            +--------------+                     
                            |              |                     
                            |              |                     
                            | foo's frame  |                     
                            |              |      |              |
                            |              |      +--------------+
      |              |      |              |      |return address|
      +--------------+      +--------------+      +--------------+
      |return address|      |return address|      |              |
      +--------------+      +--------------+      |              |
      |              |      |              |      |              |
      |  stack args  |      |  stack args  |      |  stack args  |
      |    of foo    |      |    of foo    |      |    of bar    |
      |              |      |              |      |              |
      +--------------+      +--------------+      +--------------+
      |              |      |              |      |              |
      |              |      |              |      |              |
      |              |      |              |      |              |
      |   Frame of   |      |   Frame of   |      |   Frame of   |
      | foo's caller |      | foo's caller |      | foo's caller |
      |              |      |              |      |              |
      |              |      |              |      |              |

      Ce dessin suggère un algorithme assez grossier pour libérer la pile de foo. En fait le compilateur peut faire quelque chose de très subtile grâce à un calcul de dépendances des variables et expressions. On produit alors un code en tout point semblable à la meilleure des boucles (un décompilateur ne pourra pas faire la différence).

      Pour ce faire, le compilateur doit effectuer des analyses compliquées. Ces analyses sont biens connues : factorisation, élimination du code mort, une allocation optimales des registres, l’analyse d’échappement, le calcul de causalité (élimination des synchronisations inutiles, invalidation des caches mémoires), et bien d’autres. Cependant elles font toujours l’objet d’une recherche intense. Le défi est d’être capable d’effectuer une optimisation de qualité dans un temps raisonnable (utilisable dans un JIT) et/ou en consommant très peu de mémoire (carte à puce, téléphone). Mais je me perds dans mes digressions, revenons à notre sujet principal.

      A-t-on raison de parler d’optimisation ?

      Certains auteurs contestent, à tort, le terme d’« optimisation ». L’argument avancé est qu’un programme qui se termine par une erreur stack overflow n’a visiblement pas le même comportement qu’un programme qui se termine en retournant un résultat (ou qui ne se termine pas).

      En effet, pour être correcte une optimisation ne doit pas modifier la sémantique d’un programme (et ce n’est pas toujours simple, cf. la gestion de la sécurité, les interactions avec le ramasse miettes, la gestion des exceptions et des signaux etc.) Mais en algèbre de compilation on considère toujours que la machine cible possède une mémoire infinie. Sans cela, la plupart des optimisations ne pourraient pas être effectuées par le compilateur au prétexte que cela pourrait prévenir une erreur au runtime.

      Voici un exemple pour les réfractaires :

      a = 1 + 1 + 1 + 1; // ça fait 4
      

      La plupart des compilateurs effectuent les calculs à la compilation quand c’est possible. Pourtant, ce faisant on change effectivement le comportement du programme à l’exécution car il est possible que l’évaluation de cette expression provoque un stack overflow dans certaines conditions.

      Donc oui c’est une optimisation ; le terme consacré est Tail Call Optimization (TCO).

      Que faire du paramètre supplémentaire ?

      Comme je l’ai déjà fait remarquer dans l’exemple de la factorielle, pour écrire une fonction récursive sous forme terminale, il est souvent nécessaire de modifier sa signature en y ajoutant un paramètre.

      Que faire de ce paramètre ?

      • On peut le considérer comme une astuce qu’il convient de cacher. Dans notre exemple, le paramètre accu joue le même rôle que la variable de boucle res.
      • On peut aussi le considérer comme un paramètre qui fait sens et qui améliore notre fonction. Ainsi dans notre exemple, le second paramètre accu sert à choisir la valeur du cas de base (factorielle(0)).

      En général, on souhaite masquer ce paramètre en trop. On définit alors deux fonctions : la première que l’on cache et qui prend deux paramètres, la seconde qui appelle la première et expose la signature voulue. Pour faire cela, il existe diverses méthodes selon le langage (et les paradigmes) utilisé.

      On peut définir la fonction récursive dans la fermeture (closure) de la fonction principale. C’est une technique couramment utilisée dans les langages fonctionnels.

      (* Exemple en OCaml *)
      let factorielle = function n -
          let rec __factorielle = function n - function accu -
              if n = 0
              then accu
              else __factorielle (n-1) (n*accu)
          in __factorielle n 1
      ;;
      

      Plus classique, on peut restreindre la portée de la fonction récursive. Ainsi en C, on peut utiliser le mot static pour empêcher une fonction d’être exposée dans l’interface du module.

      // Exemple en C
      static int __factorielle (int n, int accu) {
          if (n == 0) return accu;
          else return __factorielle(n-1, n*accu);
      }
      int factorielle (int n) {
          return __factorielle(n, 1);
      }
      

      La plupart des langages objets offrent des modificateurs d’accès. En Java, on utilisera les modificateurs public et private :

      /* Exemple en Java */
      public Mathematiques {
          private static __factorielle(int n, int accu) {
              if (n == 0) return accu;
              else return __factorielle(n-1, n*accu);
          }
          public static factorielle (int n) {
              return __factorielle(n, 1);
          }
      }
      

      Certains langages, tels que Python ou C#, permettent de donner une valeur par défaut à un paramètre. Ceci est particulièrement utile dans notre cas.

      # Exemple en Python
      def factorielle(n, accu=1):
          if n == 0: return accu
          else: return factorielle(n-1, n*accu)
      
      

      Évidemment il n’y a pas une seule bonne manière de faire. Vous devrez juger quelle est la technique la plus pertinente, la mieux adaptée à votre cas. C# est sans doute le langage qui offre le plus de possibilités.

      Est-ce que toutes les fonctions récursives peuvent être écrites sous une forme terminale ?

      Oui, toute. Les preuves : les théories de la calculabilité, la logique combinatoire, combinateur de point fixe.

      Un second exemple commenté : les nombres de Fibonacci

      Considérons ensemble une fonction un peu plus difficile à écrire : la fonction qui calcule les nombres de Fibonacci.

      Pour écrire la fonction de Fibonacci sous forme récursive terminale, nous avons besoin d’utiliser non pas un, mais deux paramètres supplémentaires (accu0 et accu1). Ceci est à rapprocher du fait que l’implémentation itérative utilise deux variables de boucle (res0 et res1) et du fait que la définition récursive donne deux cas de base (fibonacci(0) et fibonacci(1)).

      # Pour changer, voici du Ruby
      # Nombre de Fibonacci, implémentation naïve
      def fibonacci_naive(n)
          if n == 0
              0
          elsif n == 1
              1
          else
              fibonacci_naive(n-1) + fibonacci_naive(n-2)
          end
      end
      
      # Nombre de Fibonacci, implémentation avec une boucle
      def fibonacci_boucle(n)
          res0, res1 = 1, 0 # Pour n'avoir qu'un seul test, on commence au rang -1
          (1..n).each do res0, res1 = res1, res0+res1 end
          res1
      end
      
      # Nombre de Fibonacci, implémentation récursive terminal
      def fibonacci_recursive(n, res0=1, res1=0)
          if n == 0
              res1
          else
              fibonacci_recursive(n-1, res1, res0+res1)
          end
      end
      

      La fonction de fibonacci est particulière à bien des égards. L’implémentation naïve, outre le fait de ne pas être terminale, présente le défaut de nous faire calculer de nombreuses fois les mêmes valeurs (par exemple Fibonacci(4) = Fibonacci(3) + Fibonacci(2) et Fibonacci(3) = Fibonacci(2) + Fibonacci(1), donc nous calculons deux fois Fibonacci(2)…)

      Ici, l’implémentation récursive terminale apporte beaucoup plus qu’une bonne gestion de la pile d’exécution. C’est souvent le cas. Ceci ne signifie pas qu’il faille systématiquement utiliser des fonctions récursives terminales (par exemple, je trouve plus lisible d’utiliser une syntaxe for each pour parcourir une collection). Mais je crois que l’étude des fonctions terminales est très formatrice, et qu’elle aide les développeurs à concevoir des algorithmes concis et optimaux.

      Est-ce que ça marche pour tous les langages ?

      Malheureusement non !

      En fait, d’un point de vue technique, cela dépend non pas du langage mais du compilateur. Cependant, on peut remarquer que certains langages, tels que F# et OCaml, définissent ce comportement dans leur spécification.

      Pour les langages fonctionnels, ne pas optimiser correctement les appels terminaux est généralement considéré comme un bogue. C’est pleinement justifié car ces langages promeuvent l’utilisation de fonctions récursives. En revanche, la chose est plus discutée pour les autres langages. D’ailleurs, nous allons voir que les plateformes Java et .NET ne sont pas très satisfaisantes sur ce point.

      Faisons ensemble un petit tour d’horizon parmi les technologies les plus utilisées.

      Langage C

      La norme C ne dit rien. Mais tous les bons compilateurs (VS, gcc) proposent une option pour effectuer cette optimisation. Voici un extrait de l’aide de gcc, le fameux compilateur C :

      -foptimize-sibling-calls
      Optimize sibling and tail recursive calls. Enabled at levels -O2, -O3, -Os.

      Notons au passage que le choix des options de compilation est vraiment très important. Beaucoup de programmeurs ont l’habitude de compiler le programme de release avec l’option « -O2 » et de déboguer avec les options « -O0 -g ». Ce faisant on change le comportement du programme et il est possible de provoquer une erreur stack overflow qui n’était pas l’objet des tests.

      Plateforme Java

      Java pour les nuls

      Une des techniques essentielles de Java est l’utilisation d’un langage intermédiaire appelé bytecode Java. Un programmes Java est transformé (traduit, compilé) par le JDK en bytecode Java qui pourra être exécuté (interprété ou compilé à la volée et exécuté) par toutes les JVMs.

      Un bytecode est un langage neutre, facile à parser et à projeter sur les architectures courantes. Le bytecode Java est un langage à pile exposant certaines abstractions caractéristique du paradigme objet tels que les appels de méthodes virtuelles et statiques.

      L’optimisations des tail calls en Java

      Le JDK peut optimiser les fonctions récursives terminales en les transformant en simple boucle. Mais il est beaucoup plus intéressant d’optimiser tous les appels de méthodes terminaux (tail calls) car c’est un cas plus général qui traite aussi la récursivité croisée et la programmation par continuation. Mais le JDK ne peut pas optimiser les tail calls en général car le bytecode Java n’offre pas les instructions nécessaires (on ne manipule pas directement la pile et on ne peut pas faire de jump n’importe où). Ceci ne peut donc être fait que par la JVM lors du chargement des classes.

      Dites moi ce dont vous avez besoin et je vous expliquerai comment vous en passer

      The Java Language Specification ne dit rien au sujet des tail recursive calls.

      Chaque vendeur de Java VM fait ce qu’il veut. La JVM fournie par Sun, qui est de fait l’implémentation de référence, n’effectue pas cette transformation au grand damne des faiseurs de langages dynamiques fondés sur Java (script Groovy, JRuby, etc.)

      public class Main {
          public static void main(String[] args) { main(args); }
      }
      

      Si vous exécutez le programme ci-dessus sur une JVM de Sun, il se terminera toujours rapidement et lamentablement par une exception Stack Overflow. Heureusement certaines implémentations de Java gère les appels de méthodes terminaux, c’est le cas notamment de la JVM d’IBM.

      Sun ne communique pas beaucoup autour de ce sujet. On peut tout de même voir que ce problème est noté en very very very low priority (Votez pour ce bug !) Tant que que Sun/Oracle ne supportera pas officiellement les tail recursive calls, je crains qu’il ne nous faille renoncer à ce type de programmation en Java.

      Voici quelques billets très intéressants sur le sujet : java closures controversy, non java languages on jvm et tail calls in the VM.

      Plateforme .NET

      Pour les mêmes raisons qu’en Java, l’optimisation des tail recursive calls peut être effectuée aussi bien par le compilateur que par la plateforme d’exécution (CLR). En revanche, l’optimisation des tail calls ne peut être effectuée que par le CLR, au moment du chargement des bibliothèques.

      L’optimisation des tail calls étant requis pour traiter correctement la récursivité croisée et, plus généralement, les continuations, choses couramment utilisée dans les langages fonctionnels, la plateforme .NET qui se veut être une plateforme multi-langages et multi-paradigmes, doit optimiser correctement les tail calls.

      Voici ce que dit la documentation de CIL (le langage intermédiaire commun utilisé par .NET) :

      CIL ECMA 335 spec “use a tail. prefix to specify that the current stack frame should be released before transferring control. If the call would transfer control to a method of higher trust than the original method the stack frame will not be released.”

      Malheureusement, le comportement de la plateforme .NET de Microsoft est loin d’être satisfaisant. Pour des raisons qui me sont obscures (ordre des paramètres sur la pile, compatibilité binaire avec du code natif, concurrence avec le GC), la plate-forme .NET ne sait pas, en général, optimiser correctement les tail calls.

      Voici une liste des conditions requises pour que le JIT du CLR2 effectue une TCO :
      http://blogs.msdn.com/davbr/pages/tail-call-jit-conditions.aspx. J’ai été étonné par certaines de ces limitations qui me semblent mal fondées. Microsoft s’est visiblement montré très défensif dans les deux premières versions du CLR.

      Concrêtement les comportements du compilateur et de la VM dépendent de l’architecture de la machine. L’implémentation de la CLR pour x86, au lieu d’une savante manipulation, utilise une fonction magique et affreusement lente qui décale le sommet de pile (cf. le dessin au début de l’article). Par conséquent, la plupart des compilateurs .NET ne produisent jamais le préfixe tail. et les compilateurs de langages fonctionnels tels que F# ou parallelC# transforment les fonctions récursives terminales en boucles. Mais il s’agit plus d’un workaround qu’autre chose. En effet cela ne traite pas les cas de co-récursivités et plus généralement de la correcte optimisation d’un programme écrit par continuation.

      L’implémentation pour x64 est meilleure. En générale, tous les arguments d’une fonction peuvent être stockés dans les registres processeurs car ils sont beaucoup plus nombreux sur cette architecture. Grâce à cela, les performances des tail calls sont acceptables. Par conséquent, sur x64, le CLR effectue des TCO même quand le prefix tail. n’a pas été spécifié dans le code CIL, si les conditions requises sont réunies…

      Microsoft a bien conscience du problème. Un effort significatif a été apporté à l’optimisation des tail calls dans la CLR4 sur x86_64. Maintenant les optimisations faciles sont toujours effectives et, pour les rares cas restant, on utilise la fonction magique dont je parlais tantôt afin de toujours tenir compte du prefix .tail dans le code IL.

      Oyez, programmeur. F# fonctionne correctement (enfin) sur CLR4/x86_64 !

      Remarquez que l’optimisation correcte des tail calls a été l’occasion pour Microsoft d’améliorer son compilateur en supprimant certaines actions inutiles (copie de buffer de values, etc.) Je me répète sûrement : la récursivité aide les développeurs, mêmes les très bons, à concevoir des algorithmes concis et optimaux.

      Python

      Guido van Rossum, le créateur et leader du langage de programmation Python a publié un billet horrible où il tente d’expliquer pourquoi les TCO sont le mal et ne devraient surtout jamais être effectuées en Python.

      Certains des arguments avancés sont tout simplement faux. Vous trouverez quelques arguments bien construits dans les commentaires de cet autre billet.

      Python est un langage qui a été pensé pour être facile à utiliser, et non pas pour produire des programmes rapides. En cela je comprends parfaitement que les TCO ne soient pas une priorité, mais il n’y a aucune raison valable pour s’y opposer par principe.

      Ruby

      La spécification de Ruby ne dit rien au sujet des tail recursive calls.

      Yukihiro Matsumoto (le créateur de Ruby) semble être favorable au TCO, mais, il n’a pas souhaité forcer toutes les implémentations de Ruby à le supporter. Voilà une position qui me semble sage ;-) .

      YARV, qui est l’interpréteur Ruby officielle, supporte le TCO depuis la version 1.9. Notons que c’est très récent et que l’option, considérée encore instable, est désactivée par défaut (il est nécessaire de commenter une ligne et de recompiler l’interpréteur).

      Quant à JRuby et Ruby.NET, ils dépendent évidemment des capacités de la plateforme sous-jacentes..

      Ceci signifie que vos programmes Ruby ne devraient pas se fonder sur le TCO, car dans ce cas ils ne seront pas portables sur toutes les Ruby VM.

      New paradigms for concurrent programming: Transactional Memory

      Java and other mainstreams programming languages rely on shared state concurrency for concurrent programming. Race conditions between different threads are prevented using synchronization structures.
      Traditionally in Java synchronized blocks along with wait and notify methods are used but Java 5 introduces new easier concepts.

      Still it is quite difficult to implement correctly those patterns and there is no way to prove the code is correct.

      New paradigms are slowly introduced intending to simplify or replace locking constructions.
      This article will focus on Transactional Memory.

      Transactional Memory

      Transactional memory is an alternative to lock based synchronization relying on shared states.
      This is achieved by applying the concept of transaction and ACID properties to your programming structures. Key points here are Atomicity and Isolation. Concurrent executions effects are equivalent to individual serial executions.

      The main idea is to wrap a block of code in a transaction which is committed at the end. Read/write memory accesses can be dealt differently (optimistic/pessimistic locking) depending on concurrency control algorithm. If a conflict is detected during the execution of the atomic block by a thread the whole operation is roll-backed and retried. Details on retries strategies are implementation specific.

      Biggest advantage of this technology is simplicity. Programmer doesn’t have to care about how to implement safe concurrent access (dealing with locks, wait..) but only identify which block of code should be treated as atomic.
      Better performances are not ensured but correctness is.

      More detailed explanation on transactional memory can be found here, here, here and here.

      Usage

      In most of languages/libraries transactional memory support will be little intrusive by relying on special keyword or meta-data. For language without built-in transactional memory support this can be achieved by using bytecode manipulation.

      Regular synchronized block

      
      public void test() {
        synchronized(map1) {
          synchronized(map2) {
            value = map1.remove("key");
            map2.put("key", value+1);
          }
        }
      }
      
      

      Here you have to synchronize modification of 2 structures. This can be hard to correctly implement especially if several methods have to synchronize on the same structure. In this particular sample you have to take a particular care about potential deadlock which might be introduce if you don’t acquire/release locks in the same order.

      Transactional memory

      
      @Atomic
      public void test() {
        value = map1.remove("key"); 
        map2.put("key", value+1);
      }
      
      

      Transactional memory ensures that your atomic block of code will be executed atomically. Still there is no magic and you need to identify atomic blocks of code.
      Moreover you don’t have to think about locking optimization, which is hard especially when multiple structure access have to be synchronized from different execution path. Because everything is handled by the language (or library) smart optimization might be applied and you are not responsible for this.

      This technique does not solve all concurrency problems but certainly helps for some of them. Still you will have to rely on Semaphore, Barrier and other advanced structures for more complex scenarii.

      Language support

      Haskell

      Haskell was the first language to have builtin support for STM. Details can be found here.

      Java

      Atomic types, locks (Java 5) and Fork/Join (Java 7) are lock-free structures using compare and swap algorithm. Implementation is similar to transactional memory but for a single element. Details about their implementation choice can be found here and here.

      To benefit from atomicity capacity you will have to use a full transactional memory implementation.

      A number of Java implementation are available:

      Multiverse’s author discusses similarities between TL2 and Multi Version Concurrency Control (MVCC) algorithms .

      .Net

      Microsoft also provides STM support with STM.NET.

      Fortress

      Fortress language supports transactional memory as part of its semantic. Current implementation relies on DTSM2 but plan is to switch to Hybrid Transactional Memory (mix of hardware and software implementation) when available.

      On top of atomicity, most of fortress syntaxic structures (loop for instance) are executed in parallel by multiple threads. While powerful this introduces complexity as a simple loop adding numbers will have to manipulate atomic structures.

      Clojure

      Clojure supports STM based on MVCC.

      Hardware Transaction Memory

      In the near future transactional memory paradigm will be implemented at hardware level providing even better performance. Rock processor from SUN (recently released paper describes HTM features) and Vega 3 from Azul implement it. Other approaches have been documented here and here.

      Cons

      Transactional memory is not the panacea and some critics (here and here) appeared recently.
      Others techniques aiming at facilitating concurrent programming will be discussed later.

      Java Profiling tool survey

      As part of our consulting activity, we need a tool to help us track performance issues.
      A profiler seems to be the perfect suit for this. So I performed a small survey to find the best tool for our needs.

      Mainly we are interested in:

      • method calls profiling
      • threads contentions profiling including with latest java.util.concurrent
      • memory snapshot comparison and dynamic memory usage monitoring

      Most of available tools are based on instrumentation which might slightly modify performances of your program (known as Heisenbug). For this survey we neglected this impact as main performance issues are still visible when using instrumentation based profiling.

      For a more complete description of impacts of instrumentation refer to this discussion.

      Here are my remarks regarding some of the main tools available, both commercial and open source.

      VisualVM 1.1.1 and Netbeans profiler

      • No details on blocked threads.
      • History and threads states not useful. (http://profiler.netbeans.org/issues/show_bug.cgi?id=55211)
      • No support for java.util.concurrent

      Netbeans profiler adds the concept of profiling points and provides integration with source code.
      Other concepts are similar.

      Yourkit (8.0.13)

      • Supports automatic deobfuscation (http://www.yourkit.com/docs/80/help/summary.jsp)
      • Keep traces of all exceptions thrown
      • No j.u.c support (http://forums.yourkit.com/viewtopic.php?f=2&t=2901). Can be simulated with wall time feature but not easily.

      JProfiler (5.2.2)

      • Used by our customers.
      • Monitor profiling supports java.util.concurrent (reported as blocked threads when waiting).
      • Poor documentation.
      • No forum.

      Appperfect Java Profiler

      • Apparently needs a specific agent (only available under Linux?) to profile a remote application.
      • Not very clear from documentation.

      JRat

      • Pretty basic, no dynamic profiling
      • Interesting statistics on exception thrown (rates)

      dynaTrace

      • Apparently demo version not available for France. Didn’t investigate.

      JXInsight

      • No evaluation version available.
      • Didn’t have any answer from sales representative.

      So far my tool of choice is JProfiler, mainly because it supports java.util.concurrent features. I would rather switch to Yourkit if this was supported out of the box for the ease of use and exception feature.

      Concurrency and performance

      Massively concurrent programming will be the future of development. No doubt about that. But manipulating lots of threads sharing resources is not an easy task.
      Evenso you managed to make your program correct – and considering the lack of tools or methodology to ensure it this is tricky – you have to take a look at performance.

      Using multi thread programming technics is not a garanty to have better performances. Far from it. Recently on customer site I managed to improve performance more than an order of magnitude just by minimizing synchronization cost.

      Here are a few simple tips to help you benefit from all your cores:

      • Minimize synchronized block length and synchronized method usage
      • Replace synchronized keyword by Lock
      • Replace Object.wait and Object.notify by Condition
      • Limit safe objects usage when not needed (like StringBuffer, old collection implementations)
      • Rely on java 5 concurrency features

      This last point certainly is the most important. Java 5 introduced lots of concurrency related structures.

      Atomic* types allows to manipulate safely primitive like object. Much more efficient than having to synchronize every access to, say, your global statistics. See javadoc for more details.

      ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet and others offer safe optimized collection. More lightweight than relying on Collections.synchronized* methods. See javadoc for more details.

      New libraries adding more concurrent structures are now available, here are a few:

      Interresting new metaphores to split complex tasks into independant subtasks should be added in Java 7.