At least, it helped someone…

Yesterday, I was comparing french cloud game hosting service Clan of the Cloud with one of its most important competitors GameSparks for some side projects. What a surprise when I found portions of my code from this post was used in the official documentation of GameSparks without a single credit to me or to Cheese Burgames.

Despite the fact the related bug was fixed in Unity long ago before the release of the article, the author does not warn users that our workaround is not the official supported way to trigger notification support on Unity.

Even if my blog is not a popular place on the internet, it seems my solution has been useful to someone (and it confirms WordPress.com is a very good platform for search engine discovery). It just would have been really cooler to add a mention.

Unity Pro Tip #2: iOS 8 Push Notifications

UPDATE: This bug has been fixed in Unity 4.5.5p1 (released October 16th, 2014) and in Unity 4.6.0p1 (released december 4th, 2014).

Now iOS 8 is out, we have to release updates for our Unity apps. Here at Cheese Burgames, we are preparing the first update for our first game, Echoes: Deep-sea Exploration. But during our pre-release tests, I noticed that remote notifications were not working anymore. Why that?

The answer is in the deprecation of the UIApplication’s registerForRemoteNotificationTypes: method in iOS 8.

Internally, NotificationServices.RegisterForRemoteNotificationTypes calls it. And now, it does not produce any result. Instead, you will see this message in the Xcode debug console:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Nothing less, nothing more. And as of the currently available version of Unity (4.5.4f1) or in patch releases, there is no workaround. But we have a solution!

The Quick Fix

To resolve this issue, you have to use an iOS plugin and a script.

First, create a file called CBG_NotificationServices.mm in your Assets/Plugins/iOS folder (create this folder if it does not exist yet). Here is the content of this file:

#import <UIKit/UIKit.h>

extern "C" {
    void CBG_RegisterForRemoteNotifications(int types) {
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:Nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
 
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}

After that, create a C# script called CBGNotificationServices.cs in your Assets folder (or anywhere else you store your player scripts) and fill it with these lines:

using UnityEngine;
using System.Runtime.InteropServices;

public class CBGNotificationServices {

#if UNITY_IOS
    [DllImport ("__Internal")]
    private static extern void CBG_RegisterForRemoteNotifications(int types);
 
    public static void RegisterForRemoteNotificationsTypes(RemoteNotificationType types) {
        if (!Application.isEditor) {
            string sysInfo = SystemInfo.operatingSystem;
            sysInfo = sysInfo.Replace("iPhone OS ", "");
            string[] chunks = sysInfo.Split('.');
            int majorVersion = int.Parse(chunks[0]);
            if (majorVersion >= 8)
                CBG_RegisterForRemoteNotifications((int) types);
            else
                NotificationServices.RegisterForRemoteNotificationsTypes(types);
        }
    }
#endif
 
}

Now, anywhere you are using NotificationServices.RegisterForRemoteNotificationsTypes(), you can simply replace it by:

CBGNotificationServices.RegisterForRemoteNotificationsTypes(/* Your remote notification types here */);

And you’re done!

This solution is a temporary one. It is working in our app but we recommend very careful testing before using it in a release. The bug has been reported and I hope the Unity team will soon provide a permanent fix.

Unity Pro Tip: Hierarchy Alphabetical Sort

If you are an experienced Unity user, you may have noticed that the sort order of the hierarchy window has changed in Unity 4.5, which now uses what’s called a Transform Sort. In fact, changes are even deeper than that as Unity Technologies offers now the option to customize this sort order. This can be made through editor scripts, providing each one custom sort type. First, if you want to revert to the previous behavior, add this script to an Editor folder in your Assets:

using UnityEngine;
using UnityEditor;

public class AlphaSortAlphabeticalSort { }

UnityEditor.AlphabeticalSort is a built-in class. I wonder why this sort is not available out of the box. But, that’s not the point here. Once this script is compiled by Unity, you should see a new drop-down list in the Hierarchy window, allowing you to choose the sort order you want to use. Unity - Hierarchy Alpha Sort That is only the first step in this new world of custom sort types. If you want to provide your own unique sort type, you can dive into the UnityEditor.BaseHierarchySort class documentation to learn more.

Benchmarking PHP – Partie 3

Voici la suite du premier et du deuxième article de cette série sur les performances de PHP.

PHP est un langage particulièrement permissif et tolérant, qui continue notamment de fonctionner correctement même si une variable n’a pas été définie (à l’exception des propriétés d’objet).
En effet, PHP n’émet qu’un simple avertissement (de type E_NOTICE) dans ces cas-là et continue son exécution normalement.

Le code suivant :

$b = 1 + $a;

produira simplement le message :

Notice: Undefined variable: a in test.php on line 2

La grande tolérance à ce genre d’erreurs de PHP et de mauvaises habitudes de programmation ont entraîné la banalisation de ces messages et il est généralement admis de ne pas en tenir compte. PHP propose en effet de filtrer les messages d’erreur par type. Ainsi, la commande suivante est préconisée par de nombreuses distributions de PHP :

error_reporting(E_ALL ^ E_NOTICE);

Comme son nom l’indique vaguement, cette commande indique à PHP de rapporter toutes les erreurs, à l’exception du type E_NOTICE.

Mais le fait de ne pas rapporter ces erreurs n’a-t-il aucune autre conséquence sur l’exécution de PHP elle-même ?

Etat des lieux

Lorsqu’une erreur est déclenchée dans le code PHP, quel que soit son niveau (et donc y compris pour les erreurs de type E_NOTICE), un appel à la fonction native trigger_error() est exécuté.
Il est de notoriété publique que l’exécution de cette fonction soit lente.

Ne pas rapporter les erreurs concernées entraîne-t-il un gain de temps au niveau de l’exécution ?

Le protocole de test

Pour ce test, j’ai choisi d’exécuter les cas suivants :

  1. ajouter 10 millions de fois la valeur d’une variable non définie,
  2. exécuter la même addition sans rapporter les erreurs au journal global des erreurs de PHP,
  3. exécuter la même addition mais en testant à chaque itération l’existence de la variable,
  4. exécuter l’addition avec une variable définie

Tous les cas ont été exécutés avec la commande :

error_reporting(E_ALL ^ E_NOTICE);

Résultats

Les résultats sont édifiants :

Variable non définie + log des erreurs : 7.55 s
Variable non définie + pas de log des erreurs : 7.29 s
Variable non définie mais testée : 1.08 s
Variable définie : 1.09 s

La conclusion est simple. Les déclenchements de la fonction trigger_error() dans les cas de variables non définies prennent beaucoup de temps. Le code source s’exécute ici 7 fois plus lentement qu’avec une variable dument définie.

PHP incite-t-il au laxisme ?

Mon avis sur la question est tranché : Oui.

En effet, l’autre inconvénient de ne pas rapporter les erreurs de type E_NOTICE est de laisser passer des erreurs parfois dommageables.

L’exemple ci-dessous ne crée aucune erreur « visible » et pourtant n’apporte pas la solution attendue :

error_reporting(E_ALL ^ E_NOTICE);

function valide_si_conforme($txt) {
    return (empty($texte) || preg_match('/^[a-z]+$/', $txt));
}
echo valide_si_conforme('123456789');

En effet, la fonction valide_si_conforme() devrait renvoyer un résultat positif uniquement si le paramètre $txt est vide ou ne contient que des lettres minuscules.

L’appel :

valide_si_conforme('123456789')

devrait renvoyer 0 ou false.

Il n’en est rien et le script renvoie 1 ou true. L’erreur qui inscrit le paramètre $texte en lieu et place de $txt dans l’appel à empty() déclenche une erreur fonctionnelle mais PHP n’en tient pas compte et la détection de ce problème est alors laissée à l’appréciation du développeur. Et si celui-ci n’a pas la discipline ou l’expérience suffisante pour savoir où il faut et où il ne faut pas tester une valeur, de nombreuses erreurs sont susceptibles d’apparaître.

Mais alors que faire ?

La logique voudrait que toutes les erreurs soient rapportées et que les variables soient testées, tant sur leur valeur que sur leur existence.
Cette démarche m’a personnellement évité beaucoup de problèmes par le passé ou permis de corriger bon nombre de bugs dans des codes sources tiers.

Code du test

<?php
    error_reporting(E_ALL ^ E_NOTICE);
    header("Content-Type: text/plain; charset=UTF-8");

    printf("Variable non définie + log des erreurs : ");
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable non définie + pas de log des erreurs : ");
    ini_set('log_errors', 0);
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable non définie mais testée : ");
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++) {
        if (isset($a))
            $b += $a;
    }
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable définie : ");
    $start = microtime(true);
    $b = 0;
    $a = 1;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);
?>

Benchmarking Java – Tableaux contre Vecteurs

Java fait partie de ces langages de programmation qui offrent beaucoup d’outils d’aide au développement sensés faciliter et améliorer nos productions. Cependant, certains d’entre eux sont parfois utilisés de manière contre-productive.

Les Vecteurs

Un « vecteur » désigne un ensemble d’éléments stockés de manière contigüe les uns à la suite des autres. On appelle également les vecteurs des « tableaux ». Les tableaux sont des types primitifs des langages de programmation et n’offrent souvent en eux-mêmes que peu d’outils aidant à leur gestion. Il faut en général passer par des fonctions spécifiques.

Java intègre une autre approche en proposant des classes utilitaires (dans le package java.util) dont la classe Vector qui reproduit l’illusion d’un vecteur en ajoutant tout en tas de fonctionnalités améliorées (réallocation dynamique de la taille du tableau, insertion en milieu de tableau, …).

Mais comme nous allons le voir, tout cela a un coût.

Le test

Pour évaluer les performances des Vector Java contre les tableaux primitifs du langage, je procède à l’addition des 100 000 premiers entiers (0 inclus) et ce 10 000 fois successives.

Les résultats sont sans appel :

Entiers directs - Compteur classique :
======================================
Somme obtenue : 4999950000
Durée totale : 339 ms
Durée moyenne d'une itération : 0,033900 ms

Entiers directs - Boucle Java :
======================================
Somme obtenue : 4999950000
Durée totale : 601 ms
Durée moyenne d'une itération : 0,060100 ms

Vecteur - Compteur classique :
======================================
Somme obtenue : 4999950000
Durée totale : 41245 ms
Durée moyenne d'une itération : 4,124500 ms

Vecteur - Itérateur :
======================================
Somme obtenue : 4999950000
Durée totale : 41500 ms
Durée moyenne d'une itération : 4,150000 ms

Vecteur - Boucle Java :
======================================
Somme obtenue : 4999950000
Durée totale : 41874 ms
Durée moyenne d'une itération : 4,187400 ms

Les Vector sont jusqu’à 122 fois plus lents que les tableaux primitifs.

Mais pourquoi ?

Les tableaux primitifs sont capables de stocker l’ensemble des types proposés par Java, des entiers primitifs aux Object. On accède donc directement à l’élément primitif sans conversion de type et sans appel de fonction superflu.

En revanche, les classes génériques comme Vector n’acceptent que des Object ou leurs dérivés (dans notre cas, des objets de la classe Integer). Notre addition provoque donc des allocations d’objets et des conversions vers les types primitifs du langage qui sont très coûteux en temps, et cela réduit donc considérablement les performances.

Mais cela n’est pas tout, la classe Vector effectue tout un tas de vérification lors de l’accès aux éléments et c’est cette étape qui est très gourmande en ressource de calcul. En effet, en reproduisant le test dans notre tableau en remplaçant des entiers primitifs par des objets Integer, les Vector sont toujours 30 fois plus lents.

Conclusion

  • Si votre programme nécessite des performances très élevées, privilégiez des tableaux classiques
  • Si votre programme nécessite des variations fréquentes dans la taille du tableau alloué, envisagez éventuellement des Vector si la première règle n’est pas contredite.

Code du test

import java.util.Iterator;
import java.util.Vector;

public class Benchmark {

    public static final int NB_INTS = 100000;
    public static final int NB_PASS = 10000;
    
    public static void main(String[] args) {
        int[] directInts = new int[NB_INTS];
        Vector<Integer> intVector = new Vector<Integer>(NB_INTS);
        
        // Remplissage
        for (int i = 0; i < NB_INTS; i++) {
            directInts[i] = i;
            intVector.add(i);
        }
        
        // Parcours du tableau en utilisant un compteur classique
        long start = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c = 0; c < directInts.length; c++)
                sum += directInts[c];
        }
        long elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Entiers directs - Compteur classique :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du tableau en utilisant la boucle Java
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c : directInts)
                sum += c;
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Entiers directs - Boucle Java :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant un compteur classique
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c = 0; c < intVector.size(); c++)
                sum += intVector.elementAt(c);
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Compteur classique :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant un itérateur
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            Iterator<Integer> it = intVector.iterator(); 
            while (it.hasNext())
                sum += it.next();
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Itérateur :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant une boucle Java
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c : intVector)
                sum += c;
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Boucle Java :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
    }

}

PHP – Jongler avec les dates

Lors de mes recherches pour une modification de WordPress, je suis tombé par hasard dans la documentation de PHP sur la fonction date_parse_from_format(). Cette fonction propose de décomposer une date à partir de sa représentation en chaîne de caractères. Cependant, à la différence de strtotime(), il est possible de passer en paramètre le format de la date à décomposer. Il est donc désormais possible de retrouver les éléments constitutifs d’une date (heure incluse) plus facilement qu’auparavant.

Exemple

Avant lorsque l’on souhaitait transposer une date SQL en date « française », on pouvait procéder comme suit :

$maDateSQL = '2011-07-18';
preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $maDateSQL, $regs);
$maDateFr = sprintf('%d/%d%d', $regs[3], $regs[2], $regs[1]);

Avec date_parse_from_format(), le code est tout de suite plus agréable à lire (de plus, certaines vérifications plus poussées sont effectuées au passage car le code ci-dessus ne vérifie que la présence de chiffres et pas leur cohérence) :

$maDateSQL = '2011-07-18';
$arr = date_parse_from_format('Y-m-d', $maDateSQL);
$maDateFr = sprintf('%d/%d/%d', $arr['day'], $arr['month'], $arr['year']);

Problème

Je disais précédemment qu’il était « désormais possible » d’utiliser cette fonction. A vrai dire, pas tout à fait. date_parse_from_format() n’est disponible qu’à partir de PHP 5.3 !

A vrai dire, lorsque l’on a la maîtrise de son hébergement, il s’agit d’un détail. Mais le passage à PHP 5.3 a des conséquences si l’on migre depuis la version 5.2 et tout le monde n’y est pas encore passé. De plus, lorsque l’on travaille avec un logiciel comme WordPress, massivement utilisé dans le monde, il faut pouvoir s’adapter au plus grand nombre et ne pas se limiter au haut du panier.

Résolution

Du coup, j’ai recodé la fonction directement en PHP et elle rend à peu près les mêmes résultats que la fonction native.

Le code source est disponible sous licence GPL version 2 et téléchargeable ici (requiert PHP 4.0.6 ou version ultérieure, c’est-à-dire à peu près tout le monde).

Benchmarking PHP – Partie 1

Pour ceux qui ne connaîtrait pas encore PHP, je vous laisse découvrir sa définition sur le site officiel.

Comme tous les langages de scripting, PHP est un outil très permissif et configurable à volonté qui facilite grandement le travail des développeurs.
Cependant, la permissivité des outils est parfois source de mauvaises habitudes pour les développeurs et ce qui peut apparaître comme étant une fonctionnalité intéressante pour gagner du temps, s’avère en fait être un problème en terme de performance au final.

Le premier benchmark de cette série porte sur les différentes façons d’accéder à une entrée de tableau associatif sans vérifier son existence au préalable ou, au contraire, en vérifiant son éligibilité selon plusieurs méthodes.

Protocole de test

Pour tester les performances de chacun des cas suivants, nous tentons d’accéder une entrée inexistante ‘id’ du tableau associatif $_GET et vérifions que sa valeur est différente de 0. Pour faire apparaître d’éventuelles différences dans nos tests, ils seront effectués sur un million d’itérations.

Voici, les différents tests qui seront effectués dans ce benchmark :

1. Aucune protection

Dans le premier test, nous n’effectuons aucun test d’existence de l’entrée dans le tableau. Le code se résume donc à :

if ($_GET['id'] != 0)

2. Protection avec array_key_exists() :

Dans le deuxième test, nous vérifions que la clé ‘id’ existe au sein du tableau $_GET grâce à la fonction array_key_exists(). Le code se résume cette fois-ci par :

if (array_key_exists('id', $_GET) && $_GET['id'] != 0)

3. Protection avec isset() :

Dans le troisième test, nous vérifions simplement si $_GET[‘id’] a été défini avant de tester sa valeur grâce à la fonction isset(). Le code est cette fois-ci :

if (isset($_GET['id']) && $_GET['id'] != 0)

4. Test avec empty() :

Dans le dernier test, nous vérifions uniquement si la variable $_GET[‘id’] est vide ou non grâce à la fonction empty(). Le code du test est donc :

if (!empty($_GET['id']))

Résultats

Voilà les résultats de l’exécution des tests :

Nombre d'itérations : 1000000

Aucune protection : 0.652613162994 ms
Protection avec array_key_exists() : 0.448069095612 ms
Protection avec isset() : 0.246340990067 ms
Protection avec empty() : 0.248647928238 ms

Nos grands gagnants sont donc isset() et empty().

Comme pouvait le laisser supposer l’introduction de cet article, ne pas protéger ses tests de variable est permis par PHP mais entraîne une réduction de performances. Le surcoût de vouloir accéder à une variable qui n’existe pas est que PHP déclenche une erreur de type E_NOTICE. Le réglage par défaut de PHP induit que cette erreur ne sera pas reportée à l’écran pour l’utilisateur via un message d’erreur, mais le déclenchement de cette erreur est bien réel.

Le cheminement de PHP dans ce cas-là est le suivant (qu’il s’agisse du gestionnaire d’erreur par défaut ou non) :

  1. Utilisation d’une entrée de tableau associatif qui n’existe pas
  2. Déclenchement d’une erreur
  3. Récupération de l’erreur par le gestionnaire d’erreur
  4. Test du niveau de reporting actuel via error_reporting()
  5. Affichage ou non d’un message d’erreur

On constate donc que ce chemin est certes le plus court pour le développeur, mais est relativement long pour PHP.

Certes, l’exécution d’un million d’itérations dure moins d’une seconde, il est donc tout à fait légitime de négliger cet aspect, mais gardons à l’esprit qu’un simple !empty($_GET[‘id’]) est trois fois plus rapide pour PHP, pour une longueur de code similaire et pour le même résultat.

En changeant cette habitude permissive, nous devrions pouvoir améliorer les performances générales de nos codes sources. Même si cela ne constitue qu’un infime gain, sur un environnement concurrentiel à très forte demande, cela peut contribuer à une meilleure qualité de service.

Note : comme le montre le code ci-dessous, les tests originaux ont été effectués sur un tableau vide. Les mêmes tests ont été effectués sur un tableau de 100000 entrées (ne contenant toujours pas l’entrée recherchée) et les valeurs sont similaires.

Code du test

<?php
    header('Content-type: text/plain; charset=UTF-8');
    set_time_limit(0);
    error_reporting(E_ALL ^ E_NOTICE);

    define('ITERATION_COUNT', 1000000);
    printf("Nombre d'itérations : %d\n\n", ITERATION_COUNT);

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if ($_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Aucune protection : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (array_key_exists('id', $_GET) && $_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec array_key_exists() : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (isset($_GET['id']) && $_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec isset() : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (!empty($_GET['id']))
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec empty() : {$elapsed} ms\n";
?>