dimanche 20 mai 2012

Fonctions postgresql, heuristiques

Reprenons l'exemple d'hier, mais cette fois-ci avec 2 tables de 10M lignes chacune, et une requête un tout petit peu plus rigolote, puisqu'elle limite la jointure aux 10 premières lignes de la première table. Pas de surprises, le planificateur de requêtes voit que la limite permet d'aller chercher les éléments de la deuxième table via leur index, et propose une requête efficace, avec un temps d'exécution de 12 ms.

create table data1(a integer not null);
insert into data1 select generate_series(1, 10000000);
create index data1_idx on data1(a);
create table data2(a integer not null);
insert into data2 select generate_series(1, 10000000);
create index data2_idx on data2(a);

select *
from (select * from data1 limit 10) d1
join data2 d2 on d1.a = d2.a

Soyons un peu plus pervers, et prenons maintenant la limite depuis une autre table, empêchant ainsi le planificateur de voir que d1 peut être en grande partie ignorée. Ouch! Le plan en prend un coup, et le temps d'exécution explose à presque 4 secondes.

create table data_limit(l integer not null);
insert into data_limit values(10);

select *
from (select * from data1 limit (select * from data_limit)) d1
join data2 d2 on d1.a = d2.a

Si l'on sait que notre limite va être la plupart du temps petite, comment peut-on aider notre planificateur à choisir la bonne requête? Une possibilité est de passer par une fonction, et de lui passer le "hint" qui va bien, c'est à dire en indiquant le nombre de lignes auxquelles l'on s'attend via le mot-clé "rows" (notez que par défaut, Postgres suppose que la fonction renvoie 1000 lignes). Et tout d'un coup, le plan redevient raisonnable, et la requête rapide:

create function get_limited_data1()
returns table(a integer)
rows 10
as
$$
 select * from data1 limit (select * from data_limit);
$$ language sql;

select *
from get_limited_data1() d1
join data2 d2 on d1.a = d2.a

Il peut être utile de passer par une fonction pour ajuster les heuristiques du planificateur de requêtes.

Attention cependant! Si la fonction est inlinée, le planificateur perdra ses heuristiques, et pourra revenir à un plan moins efficace:

create function get_limited_data1()
returns table(a integer)
stable
rows 10
as
$$
 select * from data1 limit (select * from data_limit);
$$ language sql;

select *
from get_limited_data1() d1
join data2 d2 on d1.a = d2.a

Forcer la volatilité la plus haute permet d'empêcher l'inline de la fonction, et donc de forcer les heuristiques du planificateur.

Fonctions postgresql, inlining

Regardons d'un peu plus près les fonctions Postgres et leurs conséquences sur les performances. Créons deux tables d'entiers munies chacune d'un index, l'une faisant 2M de lignes, et l'autre seulement 10.

create table data1(a integer not null);
insert into data1 select generate_series(1, 10000000, 5);
create index data1_idx on data1(a);
create table data2(a integer not null);
insert into data2 select generate_series(1, 10);
create index data2_idx on data2(a);

Joignons les 2 tables, et oh, miracle, le planificateur de requête décide d'utiliser l'index sur la grande table. Temps d'exécution, 12 ms.

select *
from data1 d1 join data2 d2 on d1.a = d2.a;

Maintenant, utilisons plutôt des fonctions, qui retournent le contenu de la table.

create function get_data1() 
returns table(a integer)
as
$$
 select * from data1;
$$ language sql;

create function get_data2()
returns table(a integer)
as
$$
 select * from data2;
$$ language sql;

select *
from get_data1() d1 join get_data2() d2 on d1.a = d2.a;

Le plan est beaucoup moins sympathique: le planificateur ayant perdu toute information sur les index disponibles ne peut que faire une jointure bourrine, pour une exécution qui dure 2.3 secondes. Comment arranger cela?

Une fonction Postgres possède un certain nombre d'attributs qu'il est possible d'ajuster pour donner plus d'information au planificateur de tâches. L'un de ces attributs est la volatilité de la fonction. Par défaut, toute fonction est volatile, ce qui empêche le planificateur de faire aucune optimisation, mais une fonction ne contenant que des "select" peut être passé à "stable", ce qui permet au planificateur de l'inliner. Le résultat est sans appel:

create function get_data1() 
returns table(a integer)
stable
as
$$
 select * from data1;
$$ language sql;

create function get_data2()
returns table(a integer)
stable
as
$$
 select * from data2;
$$ language sql;

select *
from get_data1() d1 join get_data2() d2 on d1.a = d2.a;

L'on est revenu au premier plan, car le planificateur a tout inliné et a donc pu utiliser sa connaissance des index pour revenir au temps d'exécution initial.

Il est la plupart du temps extrêmement utile d'attribuer la bonne volatilité à une fonction afin de permettre au planificateur de requête de l'optimiser au mieux.

mercredi 9 mai 2012

Mon nouveau téléphone

C'est avec une petite larme au coin de l’œil que j'abandonne mon Nokia 6500c. C'était une brave bête, stylée, indestructible, et plutôt ergonomique, bien qu'un poil buggée. Je ne saute qu'à moitié vers le smartphone, puisque je suis maintenant l'heureux utilisateur d'un C3 "Touch & Type", qui comme son nom l'indique est muni à la fois d'un clavier et d'un écran tactile.

Au final, et après un petit peu de personnalisation, je retrouve toutes mes marques.

Cependant, y transférer mes SMS n'a pas été de tout repos. Laissez moi vous conter un peu tout ça.

Tout d'abord, je décide d'utiliser l'option Bluetooth. Il me faut m'y reprendre 3 ou 4 fois avant de comprendre comment configurer chaque téléphone pour qu'ils acceptent de se causer, mais ça fini par passer. Mais ce n'était que partie remise, puisqu'au milieu de la (longue) transmission, il plante. Je réessaye, il plante encore.

Prochaine étape: passer par le PC. Certes, mais où est la suite Nokia pour Linux? Nulle part, on dirait. Malgré les demandes répétées de quelques barbus irréductibles, Nokia ne fournit qu'une solution Windows, et les quelques applications OpenSource qui prétendent y arriver refusent obstinément de découvrir mon téléphone.

Ça fait mal, mais bon, rebootons, et installons l'OVI suite. Ah, et puis les drivers Nokia qui doivent être téléchargés depuis Windows Update. Ah, et puis il faut rebooter la machine. Ah, et puis ne faut pas brancher le téléphone trop tôt, sinon l'application est perdue. Au moment où je m'apprêtais à sacrifier un poulet, ça se connecte enfin. Youpie! Je synchronise, et au bout d'un autre (long) moment, mes contacts, mes photos et mes SMS sont sur la machine. J'y branche alors mon nouveau téléphone, et je synchronise. Malheureusement, c'est là que l'on ne s'est pas bien compris, avec la machine: elle a juste téléchargé les moitié de messages qui étaient déjà sur le téléphone, conséquence de la copie Bluetooth partielle, et m'a pourri ma boite en passant la moitié des messages à la date d'aujourd'hui.

J'ai bien ensuite perdu 2 heures à tenter de nettoyer pour resynchroniser. Il devait se souvenir de la date du dernier message téléchargé, parce qu'il ne voulait plus rien savoir et ne transmettait plus aucune donnée. Finalement, j'ai découvert qu'il était possible de faire du "glisser-déposer" pour forcer la transmission des messages vers la machine, puis de recommencer pour renvoyer vers le nouveau téléphone... Mais ce mode prend encore plus de temps, au rythme de 2 ou 3 messages à la seconde.

Finalement, tout y est. Les messages multimédia apparaissent au début au lieu de venir dans l'ordre alphabétique, mais je survivrai. Pour moi, c'est la conséquence d'applications trop intelligentes: je n'arrive pas à comprendre à l'avance les opérations que le système va effectuer, et quand je me plante, je dois alors me battre pour retrouver les fonctionnalités de base (copie ça ici) qui me permettent de réparer... Voyons cependant le bon côté des choses: l'OVI suite permet d'exporter ses SMS au format CSV. J'ai l'intention de tout coller dans une base de données, et de tirer quelques statistiques d'une inutilité crasse. Vous aurez été prévenus!

mardi 24 avril 2012

Warning gcc

Un warning bizarre de gcc. En cherchant sur la mailing list, il existe un bug similaire qui a été résolu par le passé, mais on dirait que celui là courrait encore dans gcc 4.4 (corrigé en revanche dans 4.6). Ce cas assez tordu cause un warning se plaignant que certains chemins n'ont pas de return:

// test.cpp
// Compile with:
// g++ -c test.cpp -O0 -o test -W -Wall

// Compiling with -O3 fixes the warning

#include 
#include 
 
int a()
{
  std::string str; // Removing this line fixes the warning
  int state = 1;
  if(state != 1 && state != 2) // Only having 1 condition fixes the warning
  {
    throw std::runtime_error("abc");
    //return 0; // Replacing the throw by return 0 fixes the warning
  }
  else
  {
    return 0;
  }
}

mardi 10 avril 2012

Skyrim

La voilà, la raison de mon manque d'enthousiasme à poster ces derniers temps. Avec un jeu aussi énorme dans mes pattes, mes vacances y passent à la vélocité d'un dragon souffrant d'aérophagie.

Ce n'est cependant pas une revue standard à laquelle je vais me livrer, puisque Internet vous a déjà dit que ce jeu était excellent, mais à une petite revue de certains mécanismes de gameplay que je trouve particulièrement bien pensés.

L'alchimie est bien plus amusante: au lieu de simplement attendre de monter en grade pour découvrir quelles sont les propriétés de tel ou tel ingrédient, il faut les manger! À haut niveau, l'on pourra découvrir plus de propriétés à chaque dégustation, mais puisque les propriétés sont également dévoilées lors d'un mélange réussi, l'on étend ses connaissances au fur et à mesure de nos expérimentations. L'on notera également l'amélioration de l'interface: alors que dans Oblivion, il fallait revoir chaque élément pour trouver des combinaisons, Skyrim liste clairement les potions pour lesquels on a les ingrédients disponibles.

Notons également la possibilité de forger ses armes et armures, et d'améliorer ses créations ou l'existant. Il est particulièrement agréable de porter une armure que l'on a peaufiné soi-même.

La création des objets enchantés est intéressante: l'on apprend des enchantements en détruisant des objets enchantés, pour ensuite en ré-enchanter d'autres. Pour chaque objet, il y a donc un choix à faire: le vendre pour en retirer des sous, le détruire pour ré-enchanter un autre objet plus pratique (comme un casque ou une armure) mais avec le risque que l'effet soit moins puissant à bas niveau, ou l'utiliser tel quel.

Le système de quêtes est toujours aussi bien pensé: les quêtes mineures dirigent le joueur vers les emplacement où les quêtes majeures sont disponibles: ainsi, l'alchimiste vous demandera d'aller prendre des nouvelles auprès du centurion, lequel débloque la longue série des quêtes de l'empire. La responsable de la scierie perdue au milieu des montagnes vous demander d'aller porter un mot à la ville proche, vous donnant une raison d'aller y jeter un œil.

L'on regrettera cependant l'interface utilisateur qui sent la console de jeux. Le concept d'équipements favoris est pratique, mais j'aurais aimé la possibilité d'utiliser les raccourcis claviers pour aller directement chercher les armes. Il faut également parfois de battre un peu avec la souris et le clavier lors des dialogues, les zones cliquables étant parfois mal fichues.

J'y retourne!

jeudi 22 mars 2012

Je me suis mis à Python

Mais j'ai du m'arrêter après 3 lignes, parce que je me suis fait router sur une autre tâche. Ceci dit, en 3 lignes, j'ai découvert:

  • Qu'il existe un mode Emacs tout à fait remarquable, permettant notamment d'exécuter tout ou partie du buffer, un peu comme le mode Emacs d'OCaml

  • Qu'il existe une bibliothèque pour lire les fichiers CSV, simple et complète

  • Qu'il est trivial d'exécuter des commandes shell depuis le programme

  • Mais qu'il va quand même falloir que je m'habitue à ne rien avoir à déclarer

Bon début, donc.

jeudi 15 mars 2012

DB2 ou Sybase?

C'est le choix cornélien qui me tombe dessus au boulot.

D'un côté, DB2, que nous utilisons depuis fort longtemps, mais dont les performances sont plutôt mauvaises (rien à voir à priori avec le moteur en lui même, c'est plutôt une histoire de performances du stockage réseau), à tel point que nous avons peut-être aujourd'hui perdu un important client, excédé de nos problèmes de performances. Ajoutons également que les DBAs nous facturent en interne une petite fortune, et l'on comprend pourquoi l'on songe à bouger.

De l'autre, Sybase, qui a l'énorme avantage de coûter quelque chose comme 20 fois moins au bas mot, grâce à la ristourne négociée par le département en échange d'un achat massif de licenses. Les performances pourraient être meilleures (?), mais les fonctionnalités semblent moins à la hauteur. L'absence de requêtes récursives est un problème qui pourrait être particulièrement difficile à émuler, et complexifier de fait la couche applicative. L'exécution de requêtes concurrentes pourrait être moins efficace. Et enfin, je n'ai toujours pas réussi à faire fonctionner leur bibliothèque ODBC en 64 bits.

Je n'aurais pas eu d'hésitations à proposer Postgresql, mais malheureusement ce n'est pas une option dans notre monde merveilleux. Tant pis, on fera avec.