Le shebang, représenté par #!, est un en-tête d’un fichier texte qui indique au système d’exploitation (de type Unix) que ce fichier est un script (ensemble de commandes) et non un fichier binaire ; sur la même ligne est précisé l’interpréteur permettant d’exécuter ce script.
J’utilise deux ordinateurs: un sous Linux et un sous MacOS et j’ai un script qui fonctionne sous Linux et pas sur MacOS.
Voici L’extrait intéressant
#!/bin/bash
declare -A Array
L’erreur est la suivante sur MacOS : declare: -A: invalid option
Cette fonctionnalité (associative array) n’est disponible qu’à partir de bash
4.0
Et pourtant, quand je regarde ma version de bash
, c’est ok.
❯ bash --version
GNU bash, version 5.2.21(1)-release (x86_64-apple-darwin21.6.0)
Mais qu’est ce qui se passe ?
Regardons s’il existe plusieurs versions de bash
installées.
❯ which -a bash
/usr/local/bin/bash
/bin/bash
Et ben oui !
Regardons les versions
❯ /usr/local/bin/bash --version
GNU bash, version 5.2.21(1)-release (x86_64-apple-darwin21.6.0)
❯ /bin/bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin21)
Le problème s’éclaire: la version de bash
utilisé par le script n’est pas celle
de mon PATH
et n’est pas la plus récente.
On peut utiliser la commande env
au lieu d’un interpréteur de commandes pour chercher celui-ci dans le PATH
#!/usr/bin/env bash
A large number of problems involve a function that calculates the next state from the current state, denoted by f: S => S
.
Such a function, which takes elements from the same set as input and output, is called an endofunction.
Let’s consider the example of a geometric sequence with a common ratio of 2. It can be defined by the recurrence relation Un+1 = 2*Un.
Suppose we want to answer these questions:
In Scala, you can use the iterate
function on the Iterator
object to create an infinite iterator that applies a given function repeatedly to the previous result.
It takes an endofunction as a parameter. The syntax is shown below:
def iterate[T](start: T)(f: T => T): Iterator[T]
We can create the iterator with Iterator.iterate(1)(_ * 2)
, and then use the following methods to answer the questions:
it.drop(8).next()
it.indexOf(256)
it.find(_ > 100)
Iterators are a powerful tool in Scala, and many programming exercises can be elegantly solved using them. Separating business logic from iteration logic is a case of a crucial design principle called separation of concerns, and iterators make it possible to achieve this separation. For example, iterators are very useful on Advent of Code, as demonstrated by a search in my repository.
]]>In this article, we will explore a fundamental yet highly useful feature of Scala: partial functions.
A partial function (as opposed to a total function) is a function that is not defined for all possible inputs.
A partial function g: A => B
is a function for which there exist some values a
in the domain A
such that g(a)
is not defined.
Scala has a good support of partial functions.
At Devoxx France 2023, Edson Yanaga gave a talk in which he revisits the GoF design patterns using the new features of Java. Here is his implementation of the Chain of Responsability pattern. During the talk, I thought to myself that it’s even easier in Scala.
For illustration purposes, let’s take the example of the FizzBuzz kata.
Here is the implementation in Scala:
object FizzBuzz extends App {
val multipleOfThree: PartialFunction[Int, String] = {
case i if i % 3 == 0 => "Fizz"
}
val multipleOfFive: PartialFunction[Int, String] = {
case i if i % 5 == 0 => "Buzz"
}
val multipleOfBoth: PartialFunction[Int, String] = {
case i if i % 3 == 0 && i % 5 == 0 => "FizzBuzz"
}
val default: PartialFunction[Int, String] = {
case i => i.toString
}
val fizzBuzz = List(multipleOfBoth, multipleOfThree, multipleOfFive, default)
.reduce(_ orElse _)
(1 to 100).foreach(i => println(fizzBuzz(i)))
}
The Chain of Responsibility is implemented using the orElse
method to chain the calls.
I also frequently use this technique to implement my heuristics in bot programming competitions on codingame. This allows me to easily change the priorities of actions just by changing the order in the list.
Souvenez-vous, mon blog est hébergé sur un VPS. C’est donc à moi de maintenir le système à jour. Et vu que l’OS est encore un Ubuntu 16.04, je ne l’avais pas fait depuis longtemps. La mise à jour ne fonctionne pas, je décide donc de réinstaller via l’interface web de mon fournisseur. Et c’est là que les ennuis commencent. Ubuntu 20.04 refuse de s’installer et le support technique est catastrophique.
Je suis donc contraint de changer de fournisseur. Je continue de soutenir la french tech et choisis OVH. J’ai choisi l’offre la moins chère : VPS Starter (1 vCPU 2 GB RAM 20 GB disk) pour 3,6 € par mois. Après quelques minutes, j’ai un nouveau serveur fraichement installé avec Ubuntu 20.04.
Juste après l’installation, je fais la configuration minimale pour SSH :
journalctl
montre que ces erreurs disparaissent.
sshd[2823]: Invalid user admin from 186.89.246.141 port 24850
sshd[2823]: pam_unix(sshd:auth): check pass; user unknown
sshd[2823]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=186.89.246.141
sshd[2823]: Failed password for invalid user admin from 186.89.246.141 port 24850 ssh2
sshd[2823]: Connection closed by invalid user admin 186.89.246.141 port 24850 [preauth]
~/.ssh/config
(avec le nouveau port)ssh-copy-id
pour me connecter sans mot de passeJ’utilise la configuration par défaut pour /etc/nginx/nginx.conf
. Je dois juste :
/etc/nginx/sites-available/blog
/etc/nginx/sites-enabled
/var/www/yannmoisan.com
sudo mkdir -p /var/www/yannmoisan.com
sudo chown ubuntu:ubuntu /var/www/yannmoisan.com
rsync -a _site/ blog:/var/www/yannmoisan.com
systemctl reload nginx
Mon site n’était pas disponible en HTTPS. J’ai suivi la documentation pour utiliser Let’s encrypt avec NGINX. L’outil modifie la configuration NGINX; le traffic HTTP est ainsi redirigé en HTTPS. Le cadenas dans le navigateur montre bien que tout fonctionne.
HTTPS est un prérequis pour HTTP/2. J’ai donc pu l’activer simplement en modifiant la ligne listen 443 ssl;
par listen 443 ssl http2;
Le site webpagetest permet de tester la performance de son site. Ma note en sécurité est F.
DigitalOcean offre un outil pour configurer nginx. J’ai recopié les valeurs
de security.conf
qui permettent d’ajouter des headers de sécurité.
On peut utiliser httpie
pour vérifier
❯ http https://www.yannmoisan.com
HTTP/1.1 200 OK
...
Content-Security-Policy: default-src 'self' http: https: data: blob: 'unsafe-inline'
Referrer-Policy: no-referrer-when-downgrade
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Ma note est maintenant A+.
webpagetest a aussi détecté un problème sur le cache des fonts et un problème sur la compression de certains fichiers.
Après les correctifs, voici l’évolution des différents scores :
Security score | First Byte Time | Keep-alive Enabled | Compress Transfer | Compress Images | Cache static content | |
---|---|---|---|---|---|---|
avant | F | B | A | C | A | C |
après | A+ | A | A | A | A | A |
Vous pouvez consulter le résultat.
lighthouse est un autre outil populaire. Vous pouvez tester votre site sur web.dev. Un problème remonté est render-blocking resources. Pour résoudre cela, j’ai hébergé les fonts google sur mon serveur comme recommandé ici ou là. La métrique First Contentful Paint est ainsi passé de 2.5 s à 1 s
Mon blog était généré par un script shell maison. Même si cela était intéressant à faire, le code est devenu peu lisible et peu maintenable. Il existe aujourd’hui de nombreux générateurs de sites statiques et j’ai migré sur une solution éprouvée : jekyll et ainsi soigner un peu mon syndrome NIH.
Jusqu’ici, j’écrivais mes articles en HTML. Ce qui est pénible à la longue. Jekyll supportant HTML et Markdown, j’ai migré
tous mes posts en Markdown avec l’aide de pandoc
.
Et pour finir, j’ai opté pour le thème par défaut : minima. Le site s’affiche mieux sur mobile (à croire que le dev front est un vrai métier).
Mon site était disponible à travers ces 2 urls : http://www.yannmoisan.com et http://yannmoisan.com. Le support de HTTPS a encore ajouté deux nouvelles variantes : https://www.yannmoisan.com et https://yannmoisan.com. Afin d’aider les moteurs de recherche à ne pas détecter ces contenus comme dupliqués, j’ai déclaré une URL canonique sur chaque page.
<link rel="canonical" href="https://www.yannmoisan.com/foo.html"/>
L’impact devrait être visible dans la google search console.
Nous faisons actuellement face à un incident majeur au sein de notre datacentre de Strasbourg, avec un feu déclaré dans le bâtiment SBG2.
The most obvious implementation seems to be a simple loop, because most of us learned to program in iterative style.
def decompose(n: Int): List[Int] = {
var history = List[Int]()
var next = n
while (next > 0) {
history = next % 10 :: history
next = next / 10
}
history
}
What are the cons ?
Immutable programs are easier to reason about. Here, there is mutability but it remains local to the
method. Nevertheless, it makes the code a bit fragile. For example, if we switch the two lines
inside the while
instruction, the result becomes wrong.
It exists an interesting property : we can rewrite any loop with a recursive call. This refactoring, Replace Iteration with Recursion, is described in the great catalog of refactorings written by Martin Fowler.
The main trick is to pass variables that are modified inside the loop as parameters of the function.
def decompose(n: Int): List[Int] = decompose(n, Nil)
@tailrec
def decompose(n: Int, history: List[Int]): List[Int] = {
if (n > 0) {
decompose(n / 10, n % 10 :: history)
} else
history
}
In the recursive approach, two concerns are still mixed : the computation and the history.
Scala 2.13 introduced a new method Iterator.unfold
that we can take advantage of. Let’s start with
an intuition : fold
allows converting multiple values into a single value (like a sum). unfold
is the opposite : convert a single value into multiple values.
The documentation says :
def unfold[A, S](init: S)(f: (S) => Option[(A, S)]): Iterator[A]
Creates an Iterator that uses a function f to produce elements of type A
and update an internal state of type S.
So we have to determine what are elements of type A
and what is the internal state. As often, it
may be solved by following the types. We are interested in remainders, so they are elements of type
A
. And the internal state is the next number to consider.
def decompose(n: Int): List[Int] =
Iterator
.unfold(n) { n =>
if (n > 0) { Some(n % 10, n / 10) } else None
}
.toList
.reverse
There are really many ways to implement even such a trivial program. Scala offers a rich collection API and so we can replace custom code with a built-in function, which increases readability and reduces the surface for bugs.
]]>J’ai deux ordinateurs portables : un personnel (Dell XPS sous Linux) et un professionnel fourni par ma boite (Mac Book Pro), un smartphone (sous Android) et un appareil photo numérique.
Pour l’appareil photo numérique, j’enlève la carte mémoire et je la place directement dans l’emplacement prévu à cet effet de mon ordinateur portable (pour ceux qui n’ont pas de lecteur de cartes, il est possible d’en acheter un qui se branche sur le port USB). L’autre possibilité est de relier l’appareil à l’ordinateur avec un câble USB. Je copie alors les fichiers de la carte vers le disque dur de l’ordinateur. Et j’utilise alors un programme pour classer automatiquement les photos par date. J’en ai déjà parlé ici.
J’utilise mon smartphone pour téléphoner, envoyer des messages, numériser des documents, prendre des
photos et prendre des notes. Les seules données que je souhaite conserver sont les photos. J’ai
configuré mon téléphone pour sauvegarder ces données dans la carte SD. Ainsi, je peux juste insérer
la carte dans l’ordinateur pour effectuer une copie. Le corollaire est qu’il est inutile voire
dangereux de classer les photos sur le téléphone (car cela va les éparpiller en les déplaçant du
répertoire DCIM > Camera
vers Pictures
).
Avant, j’utilisais Dropbox pour synchroniser les photos de mon smartphone mais pour une raison inconnue, cela ne fonctionne plus. Et je me suis ainsi rendu compte que je n’en avais pas besoin. Le processus de copie est ainsi identique à celui de l’appareil photo.
Sur les ordinateurs, je crée/modifie/consulte des documents. J’ai besoin qu’ils soient partagés entre ces 2 ordinateurs. Une solution est de s’échanger ces documents par courriel ou de les copier sur une clé USB. Mais il est alors difficile de savoir si sur un ordinateur donné, on a bien la dernière version d’un document. Cela demande énormément de discipline. Pour résoudre ce problème, j’ai opté pour Dropbox, un service de synchronisation de fichiers dans les nuages (ce qui veut dire que mes fichiers sont physiquement copiés sur d’autres ordinateurs quelque part dans le monde).
Cependant, une mise en garde s’impose. Dropbox est un service de synchronisation et pas de sauvegarde. Si vous supprimez par mégarde un fichier, il sera supprimé partout. Par ailleurs, un avantage est d’avoir une copie des données en dehors de son domicile, ce qui permet d’être résilient à un cambriolage ou un incendie.
J’envoie aussi des courriels et j’utilise un calendrier. Mes contacts, courriels et calendriers sont synchronisés entre tous les appareils via mon compte Google. Sur l’ordinateur, j’utilise gmail et calendar directement dans le navigateur. Avant, j’utilisais une application, Thunderbird, mais l’application web me suffit.
Je sauvegarde le contenu de l’ordinateur perso sur un disque dur externe que je branche en USB pour l’occasion (j’en ai déjà parlé ici).
Pour finir, un conseil : organiser vos fichiers sans attendre et ranger les dans leur emplacement
définitif. Par exemple, votre bureau, votre dossier Téléchargements
devraient être vide. Les
documents importants reçus par mail devraient être sauvegardés dans vos documents.
Voici un schéma qui récapitule les différents transferts de données.
]]>The initial motivation is to speed up testing on a Spark project.
A frequent source of confusion is that there are multiple levels of parallelism involved.
sbt maps each test class to a task. sbt runs tasks in parallel and within the same JVM by default.
Serial execution can be forced with :
parallelExecution in Test := false
There is a second level of parallelism. If your build is a cross-project build, sbt also runs tasks from the different projects in parallel.
sbt can limit task concurrency based on tags. The task test is tagged by default and this tag is propagated to each child task created for each test class.
To restrict the number of concurrently executing tests in all projects, use:
Global / concurrentRestrictions += Tags.limit(Tags.Test, 1)
Here is a short recap
Tags.Test | parallelExecution | behaviour (cross-project / inside project) |
---|---|---|
#cores (default) | true | parallel / parallel |
#cores (default) | false | parallel / sequentially |
1 | true | sequentially / sequentially (because we limit globally to one task, i.e. one test class, at once) |
1 | false | WARNING : tests are still run in parallel across projects !!! This is because sbt currently does not tag the generated test task when parallelExecution in Test is set to false. cf #2425 |
By default, tests are executed in the same JVM as sbt.
This can be changed with :
fork in Test := true
Hence, all tests will be executed in a single external JVM. By default, tests executed in a forked JVM are executed sequentially.
This can be changed with : (the equivalent of parallelExecution)
Test / testForkedParallel := true
Moreover, in forked mode, each project will spawn its own JVM (I did not find a way to run tests from all projects in the same forked JVM, the only workaround is to create a dedicated test project and put all tests inside it).
By default, all tests are in the same group. It’s possible to change that with testGrouping
For example, if you want each test to be in its own group.
testGrouping in Test := (testGrouping in Test).value.flatMap { group =>
group.tests map (test => Group(test.name, Seq(test), SubProcess(ForkOptions()))
}
It’s possible to run multiple forked JVM at the same time :
concurrentRestrictions := Seq(Tags.limit(Tags.ForkedTestGroup, 2))
Here is a short recap
Tags.ForkedTestGroup | testForkedParallel | testGrouping | behaviour (#JVM / cross-project / inside project) |
---|---|---|---|
1 (default) | false (default) | (default) | one JVM per project / sequentially / sequentially |
2 | one JVM per project / parallel / sequentially | ||
true | one JVM per project / sequentially / parallel | ||
single | one JVM per test |
The issue with Spark is that it costs a lot to create a SparkContext
. So, some libraries allow to
reuse it across multiple tests (e.g.
spark-testing-base).
But it’s not thread-safe ! Using what we have learned in the previous sections, there are two possibilities :
Forking. But each project will have its own JVM and its own SparkContext
.
Stay in the same JVM and execute all tests sequentially (with parallelExecution = true
and Tags.Test = 1
). Warning: the configuration given
here doesn’t work in case of
multi-project.
I have created a tiny project to test all these configurations : https://github.com/YannMoisan/sbt-parallel
It’s a project with 2 subprojects and 2 test classes in each project : Foo(1|2) in project foo and Bar(1|2) in project bar. Each test prints 5 messages with a 1s delay between them. So we can see how it’s interleaved. Each message contains the class name, the index of the iteration and the pid of the JVM
Run sbt test
to see the default behaviour :
The format is ClassName[iteration][pid]
Here is the output for default behaviour
Foo1[1][39324]
Foo2[1][39324]
Bar2[1][39324]
Bar1[1][39324]
Bar1[2][39324]
Foo2[2][39324]
Bar2[2][39324]
Foo1[2][39324]
Foo1[3][39324]
Foo2[3][39324]
Bar2[3][39324]
Bar1[3][39324]
Bar1[4][39324]
Foo1[4][39324]
Foo2[4][39324]
Bar2[4][39324]
Foo2[5][39324]
Bar2[5][39324]
Bar1[5][39324]
Foo1[5][39324]
Here is the output with forked. As expected, there are 2 different pids (one by project)
Foo1[1][76860]
Foo1[2][76860]
Foo1[3][76860]
Foo1[4][76860]
Foo1[5][76860]
Foo2[1][76860]
Foo2[2][76860]
Foo2[3][76860]
Foo2[4][76860]
Foo2[5][76860]
Bar2[1][76861]
Bar2[2][76861]
Bar2[3][76861]
Bar2[4][76861]
Bar2[5][76861]
Bar1[1][76861]
Bar1[2][76861]
Bar1[3][76861]
Bar1[4][76861]
Bar1[5][76861]
Every object on the JVM has a header. It consists of a mark word and a klass pointer. On 64-bit architectures with a heap < 32G (i.e. with compressed oops), the header has a size of 12 bytes.
Java objects are 8-byte aligned by default. This can be changed with a JVM flag.
java -XX:+PrintFlagsFinal -version | grep ObjectAlignmentInBytes
intx ObjectAlignmentInBytes = 8 {lp64_product}
So, the minimum object size is 16 bytes for modern 64-bit JDK since the object has 12-byte header, padded to a multiple of 8 bytes.
The size of an object is the object header + the size of its fields + the overhead of alignment
A primitive int takes 4 bytes
A java.lang.Integer takes 16 bytes : 12 bytes for the header and 4 bytes for the field (yes, this is a huge overhead)
Apache Spark, a tool for large-scale data processing, has a class to measure the size of an object : SizeEstimator.estimate
I use ammonite, a better Scala REPL that allow to dynamically load dependencies
Let’s check the size of an Integer
@ import $ivy.`org.apache.spark::spark-sql:2.4.0`
import $ivy.$
@ import org.apache.spark.util.SizeEstimator._
import org.apache.spark.util.SizeEstimator._
@ estimate(new Integer(1))
res2: Long = 16L
So far, so good !
Let’s create a simple wrapper to store 2 int
case class IntInt(i: Int, j: Int)
The size is
int
fields : 2 * 4 = 8 bytesBut it will be awkward to create classes for all combinations of types, so let’s create a generic pair object.
@ class Pair[A, B](val a: A, val b: B)
defined class Pair
@ estimate(new Pair(1, 2))
res4: Long = 56L
On the JVM, generics and primitive types don’t play well together. Here primitive types passed to
the constructor need to be boxed to java.lang.Integer
objects.
Let’s check the number by hand. The size is
In order to reduce the overhead due to boxing, Scala has a feature called specialized.
In this case, the compiler will generate specialized classes for each combination of specialized argument types.
@ case class SpPair[@specialized(Int) A, @specialized(Int) B](a: A, b: B)
defined class SpPair
@ estimate(SpPair(1, 2))
res7: Long = 32L
Surprisingly, because the specialization is supposed to solve the overhead issue, there is still a difference with the simple wrapper.
Let’s dig into that
@ SpPair(1, 2).getClass
res8: Class[?0] = class ammonite.$sess.cmd6$SpPair$mcII$sp
The class name is SpPair$mcII$sp
and not SpPair
. it’s the class generated by the compiler.
@ SpPair(1, 2).getClass.getDeclaredFields.map(_.getType)
res9: Array[Class[?0] forSome { type ?0 }] = Array(int, int)
And this class has 2 fields of type int
. So the size should be the same as that of the simple
wrapper.
Let’s see if there is a superclass …
@ SpPair(1, 2).getClass.getSuperclass
res10: Class[?0] = class ammonite.$sess.cmd6$SpPair
There is a superclass, it’s SpPair
. Let’s have a look to the fields of the superclass
@ SpPair(1, 2).getClass.getSuperclass.getDeclaredFields.map(_.getType)
res11: Array[Class[?0] forSome { type ?0 }] = Array(class java.lang.Object, class java.lang.Object)
Unfortunately, the superclass has 2 fields of type Object
, that will always be null for a
specialized instance. Nevertheless, a reference field with a null value on the JVM take the same
space as a non-null reference.
so the size is
We can double-check with jol, another interesting tool when it comes to understanding memory
consumption. Our SpPair
class uses exactly the same mechanism as the Tuple2
class from the scala
standard library. So here is the result of jol on scala.Tuple2$mcII$sp
, the specialized instance for
pair of ints.
java -jar jol-cli-0.9-full.jar internals -cp /usr/local/Cellar/scala/2.13.1/libexec/lib/scala-library.jar 'scala.Tuple2$mcII$sp'
Instantiated the sample instance via public scala.Tuple2$mcII$sp(int,int)
scala.Tuple2$mcII$sp object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 85 0a 02 f8 (10000101 00001010 00000010 11111000) (-134083963)
12 4 java.lang.Object Tuple2._1 null
16 4 java.lang.Object Tuple2._2 null
20 4 int Tuple2$mcII$sp._1$mcI$sp 0
24 4 int Tuple2$mcII$sp._2$mcI$sp 0
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Hopefully, the instance size computed by jol is also 32 bytes. It shows java.lang.Object
fields _1
and _2
from the superclass and int
fields from the subclass _1$mcI$sp
, _2$mcI$sp
.
Let’s see a last solution with an array.
@ SizeEstimator.estimate(Array(1,2))
res15: Long = 24L
The size is
Structure | Size |
---|---|
Simple Wrapper | 24 |
Generic Pair | 56 |
Specialized Pair | 32 |
Array | 24 |
If you instantiate a lot of objects, it’s worth knowing the tradeoffs of each solution.