Informatique

CRC32 optimization for ARM Cortex M4

Sunday, 15 June 2025
|
Écrit par
Grégory Soutadé

At work I played with an ultra low power SoC powered by a single core ARM Cortex M4. To check some data integrity, we have to use CRC32 but there is no hardware peripheral to speed up computation and ARMv6 doesn't have special instruction for this (it starts from Armv8.1). After some researches, I was surprised not to find any optimized implementation on Internet. So, I wrote it by myself and the result is quite impressive : my version is ~50% faster ! Some figures (on ~30KB of data) :

  • -Os compilation : 19.4 milliseconds
  • -02 compilation : 15.2 milliseconds
  • -0s + optimizations : 8.2 milliseconds

Here, we have to consider that Cortex M4 doesn't have Data nor Instruction cache and memory accesses are done on a SRAM. Compilation is done in thumb mode with GCC 9.

Original version is the one from Wang Yaofu licensed under Apache2. It's quite simple and very academic. C primitives doesn't allows to optimize this algorithm so much because CRC has to be processed byte by byte, so we have to do some assembly !

I used multiple optimization tricks :

Use all registers available

The idea is to play with registers from r0 to 10 and not be limited to r0-r5 as commonly used.

Unroll loop

Avoid to break CPU pipeline by doing some checks + jump. Code is bigger and repetitive, but faster. We may write macro to reduce source code, not my choice here.

Do memory burst instead of unitary access

Especially when there is no cache, memory burst accesses are really faster. Here we load 4*32 bits at a time and keep all data into registers. Bytes outside burst window are computed using non optimized version.

Use shifts and rotates within load and eor instructions

ARM instructions allows to shift registers values within load and eor (and some other instructions) without having to do it in a separate line.

Avoid pipeline register lock

When it's possible, we can invert assembly lines to avoid working on the same registers on consecutive instructions (and thus avoid to lock them).

Update condition flags in sub instruction

Use subs variant to update EQ flag and avoid to check it for 0 in a separate instruction.

Do aligned accesses

In the calling function there is some code to inject "s" as a 32 bits aligned pointer (extra bytes processed by standard code).

Here is the optimized function. Whole C file can be found here

/**
 * Optimized version of _update_crc32 for 16 bytes blocks
 */
static void _update_crc32_opt16(const unsigned char *s, unsigned int len)
{
    /* unsigned int i; */

    /* for (i = 0;  i < len;  i++) { */
    /*     crc32val = crc32_tab[(crc32val ^ s[i]) & 0xFF] ^ ((crc32val >> 8) & 0x00FFFFFF); */
    /* } */

    /*
      r0 -> s
      r1 -> len
      r2 -> crc32val
      r3 -> crc32tab
      r4 -> curval[0]
      r5 -> (crc32val ^ s[i]) & 0xFF
      r6 -> crc32_tab[(crc32val ^ s[i]) & 0xFF]
      r7 -> curval[1]
      r8 -> curval[2]
      r9 -> curval[3]
     */
    __asm__ volatile (
        "mov r0, %1\n"
        "mov r1, %2\n"
        "mov r2, %3\n"
        "mov r3, %4\n"

        "push {r7, r8, r9}\n"

        "crc32_opt16_loop:\n"
        "ldm r0!, {r4, r7, r8, r9}\n"

        // curval[0]
        "eor r5, r2, r4\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[1]        
        "eor r5, r2, r7\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[2]        
        "eor r5, r2, r8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[3]        
        "eor r5, r2, r9\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"

        // Last two lines inverted
        "subs r1, r1, #16\n"
        "eor r2, r6, r2, lsr #8\n"

        "bne crc32_opt16_loop\n"

        "pop {r7, r8, r9}\n"
        "str r2, %0\n"
        : "=m" (crc32val)
        : "r" (s), "r" (len), "r" (crc32val), "r" (crc32_tab)
          // Missing r7-r9, manually save it
        : "r0", "r1", "r2", "r3", "r4", "r5", "r6"
        );
}

Code has to be compiled with minimum -O1 or -Os option

For comparison, the (quite good) code generated by GCC 12 with -Os, working on a single byte :

 570:   4288            cmp     r0, r1
 572:   d100            bne.n   576 <_update_crc32+0x12>
 574:   bd30            pop     {r4, r5, pc}
 576:   6814            ldr     r4, [r2, #0]
 578:   f810 3b01       ldrb.w  r3, [r0], #1
 57c:   4063            eors    r3, r4
 57e:   b2db            uxtb    r3, r3
 580:   f855 3023       ldr.w   r3, [r5, r3, lsl #2]
 584:   ea83 2314       eor.w   r3, r3, r4, lsr #8
 588:   6013            str     r3, [r2, #0]
 58a:   e7f1            b.n     570 <_update_crc32+0xc>

Not so far from mine, but main weakness is result write at each loop, which is really time consuming.

Libgourou v0.8.7

Sunday, 02 March 2025
|
Écrit par
Grégory Soutadé

Reminder : Libgourou is an open source ADEPT protocol implementation (ePub DRM management from Adobe) that helps download ACSM files on Linux system (and remove DRM).

Libgourou v0.8.7 is out. I just realized that I missed to announce v0.8.6 release and the mysterious v0.8.5 has just been dropped... There is only few updates since v0.8.4, mainly small bugfixes :

  • Use of $HOME environment variable if available instead of static /home/XXX
  • Fix a use after free for sendHTTPRequest()
  • Remove EBX object (that contains DRM information) when removing DRM from PDF
  • Handle empty names in adept_load_mgt

I initialy did not planed to release v0.8.7 so early, but libzip has been updated into Debian (v4 -> v5). So I felt it was a good opportunity to provide updated binary release. The nice thing with this version is that I received 3 external contributions, which is great !

You can find source code and binaries in my forge

Le coût de l'intelligence artificielle

Sunday, 09 February 2025
|
Écrit par
Grégory Soutadé

L'intelligence artificielle est un sujet récurrent ces derniers temps. Entre la génération d'image réaliste, la (plus ou moins grande) pertinence de ChatGPT, le modèle soi-disant ultra performant et à bas coût de DeepSeek, le projet de méga centre de calcul Stargate, l'échec de la première version publique de Lucie, la concurrence dans le domaine est acharnée, avec des investissements qui vont de pair.

D'ailleurs, on ne devrait pas parler d'une intelligence artificielle, mais de plusieurs. Chacune avec son modèle et sa finalité propre. C'est exactement comme pour le cerveau qui est découpé en différentes zones avec des domaines spécialisés (langage, mémoire, vision ...). Mais dans le cas du cerveau, nous avons tous les domaines en même temps !

Pourtant, quel que soit le domaine, l'intelligence artificielle n'est pas juste un modèle informatique. Avant de pouvoir être efficace, il faut réaliser toute une phase d'apprentissage à partir de données existantes. Sur ce point, le web représente une véritable aubaine, une mine d'or de données de type divers et varié. Ce n'est pas pour rien que Microsoft a racheté Github en 2018 pour la modique somme de 7,5 milliards de dollars... Il peut ainsi accéder à, désormais, 500 millions de projets ! En ce qui concerne les données multimédia (particulièrement les images), l'apprentissage est plus complexe car il faut des personnes pour "annoter" ce que contient l'image, c'est à dire spécifier dans un carré la nature d'un élément (une personne, une voiture ...). Sur ce point, les premières annotations à grande échelle ont été réalisées via les CAPTCHA afin de "prouver" que l'utilisateur n'est pas un robot. Aujourd'hui, il existe dans des pays où la mains d'œuvre est peu chère (notamment en Asie du Sud Est) des centres professionnels où les personnes sont payées pour annoter des millions d'images.

Tout cela est relativement transparent pour les utilisateurs finaux, qui se contentent d'envoyer des requêtes via le "prompt".

Dans la dernière version d'IWLA, j'ai implémenté la fusion des robots via leur identifiant ("compatible"). Quelle ne fut pas ma surprise de voir qu'en à peine 6 jours, le robot GPTBot/1.2 a téléchargé 19,5GB de contenu depuis mon site, alors que ce dernier n'évolue que très peu. Je suppose donc que GPTBot opère une ré indexation complète à chaque changement. L'utilisation de toute cette bande passante est là encore invisible pour l'utilisateur final, mais constitue, avec le stockage des données et son traitement, une consommation d'énergie très importante.

C'est pourquoi j'ai décidé de bloquer purement et simplement toutes les requêtes venant d'openai, comme je l'avais fait pour le très gourmand robot de Microsoft (bingbot).

Voici le script que j'utilise et qui est dans mon crontab. Il va analyser les logs du serveur Apache et bloquer les IP qui correspondent à un mot :

#!/bin/bash

function ban_ip()
{
    echo "Ban $1"
    nft add rule inet BAN_RU input ip saddr $1 drop
}

function ban_file()
{
    file=$1
    token=$2
    prefix_len=$3
    fields=""
    postfix=""
    case $prefix_len in
        "8")  fields="1";       postfix=".0.0.0";;
        "16") fields="1,2";     postfix=".0.0";;
        "24") fields="1,2,3";   postfix=".0";;
        "32") fields="1,2,3,4"; postfix="";;
        *) echo "Error: prefix_len must be 8, 16, 24 or 32"
           return;;
    esac
    ips=`cat $file|grep $token|cut -f2 -d' '|sort|uniq|cut -d'.' -f$fields|uniq`
    for ip in $ips ; do
        ban_ip "${ip}${postfix}/${prefix_len}"
    done
}

if [ -z "$1" -o $1 = "-h" -o $1 = "--help" ] ; then
    echo "$0 token prefix_len (8,16,24,32)"
fi

ban_file /var/log/apache2/access.log.1 $1 $2

Il se base sur le pare-feu Linux netfilter configuré comme tel :

nft create table inet BAN_RU
nft add chain inet BAN_RU input "{{ type filter hook input priority filter; }}"

La table en question est BAN_RU, déjà utilisée pour bloquer les IP venant de Russie. Le script ne supporte que des IPv4, mais je ne vois pas de connexion IPv6 de la part de robots aussi gourmands.

IWLA 0.8

Tuesday, 04 February 2025
|
Écrit par
Grégory Soutadé

Capture d'écran IWLA

Version 0.8 of IWLA (Intelligent Web Log Analyzer written in Python) is now released. While looking at the Changelog, I realized that IWLA is now 10 years old ! It's crazy to see that it's still so useful for me, I use it everyday. There is no real alternatives to it if we consider statistic analysis without cookies/embedded javascript. Only AWSTATS do the same, but it's one big unmaintainable PERL file. This is why I created my own tool which is, I think, more accurate and fine tuned. IWLA is my most actively developed personal project, but this not the only one to be old. I also developed gPass (12 years old), KissCount (15 years old), Denote (10 years old) and Dynastie (13 years old). For these projects, I only do maintenance, but I still use them a lot. For KissCount, I think it should be re wrote from scratch with a cleaner architecture. For Dynastie, there is no real alternative, any current static site generator has a dynamic/web frontend, but I think backend should be migrated to something else (more fast). Denote is quite simple and perfect for my needs.

Going back to IWLA, the changelog is :

Core

  • Awstats data updated (8.0)
  • Sanitize HTTP requests before analyze
  • Fix potential division by 0

Plugins

  • Add rule for robot : forbid "1 page and 1 hit"
  • Try to detect robots by "compatible" strings
  • Move feeds and reverse_dns plugins from post_analysis to pre_analysis
  • Move reverse DNS core management into iwla.py

HTML

  • Add domain and number of subscribers for feed parser

Config

  • Add "multimedia_re" filter to detect multimedia files by regular expression
  • Add "no_merge_feeds_parsers_list" configuration value
  • Add "robot_domains" configuration value
  • Add "ignore_url" configuration value

A demo instance (for forge.soutade.fr) is available here

Drycat

Sunday, 01 December 2024
|
Écrit par
Grégory Soutadé

J'annonce le lancement officiel de Drycat.net !

Je me suis rendu compte à posteriori, que ce nom était utilisé par une marque d'aspirateurs industriels... Il est d'ailleurs assez peu parlant quant à sa finalité (du moins au premier abord). En effet, Drycat est site web permettant le partage de secret entre plusieurs personnes. Un secret (une phrase ou un fichier) va être découpé en plusieurs parties et, selon la configuration, il faudra réunir plus ou moins de parties pour arriver à reconstituer le secret original.

Exemple, avec un secret que l'on découpe en 4 parties, mais dont 3 seulement sont nécessaires pour le recomposer :

Ainsi, le partage de "pouvoir" est assez souple en fonction du nombre de parties générées et à qui on les affecte. Dans notre exemple, il faudra absolument que la première personne soit présente, mais elle ne pourra pas seule reconstituer le secret.

La théorie mathématique derrière Drycat fut décrite par Adi Shamir. Elle se base sur la reconstruction d'un polynôme d'ordre k (k étant le seuil) par interpolation. Bien qu'ancienne, elle reste pour autant très peu répandue, alors que je la trouve particulièrement intéressante. D'autant plus qu'Adi Shamir n'est autre qu'un des co créateurs du système RSA (système cryptographique asymétrique), utilisé un peu partout en informatique, notamment pour la couche de sécurité HTTPS.

Drycat se base sur l'implémentation JavaScript ouverte Amper5and, dont un site de démonstration est disponible ici. Alors, pourquoi avoir crée un site tiers supplémentaire ? Pour une question d'ergonomie et de facilité d'utilisation.

En effet, je voulais réaliser un site facile à utiliser par tout le monde, et surtout les personnes qui ne sont pas très techniques. La configuration nécessite de connaître quelque bases, mais avec Drycat, la récupération du secret est quasi automatique. Ainsi, les points forts sont :

  • Ergonomie qui va à l'essentiel
  • Utilisation des QRCode et de l'envoie par mail
  • Recombinaison automatique du secret une fois que toutes les parties sont rentrées
  • Chiffrement des fichiers réalisé directement depuis l'interface (mais en local sur l'ordinateur de l'utilisateur)
  • Hébergement des fichiers sur le serveur

En ce qui concerne le dernier point, les fichiers envoyés sur le serveur sont forcément chiffrés, de sorte que personne ne pourra les lire sans la clé de déchiffrement. Clé connue uniquement en réunissant les différentes parties.

Un autre ajout majeur (et inédit) de Drycat est la possibilité, pour le chiffrement d'un fichier, de rendre une partie obligatoire. Ainsi, si le seuil est atteint sans que toutes les parties obligatoires ne soient rentrées, le déchiffrement sera invalide.

Pour le moment, il faut simplement être enregistré sur le serveur pour pouvoir y téléverser des fichiers, avec certaines restrictions (taille du fichier, durée de vie du fichier). La fonctionnalité d'envoi par mail étant réservé à des comptes payants. Cette partie payante sera mis en œuvre selon la demande des utilisateurs car cela nécessite la création d'une société. Mais le but premier est que les gens puissent s'amuser en toute autonomie.