8
votes

Permutation binaire de plusieurs valeurs 64 bits en parallèle / combinée

Cette question n'est pas à propos de "Comment est-ce que je permutation dans le bitwise", nous pouvons maintenant faire cela, ce que nous recherchons est un moyen plus rapide avec moins d'instructions du processeur, inspiré de la mise en œuvre de Bitslice de SBox dans des

Pour accélérer un code de chiffrement, nous souhaitons réduire la quantité d'appels de permutation. Les principales fonctions de chiffrement effectuent plusieurs permutations binaire basées sur des tableaux de recherche. Comme les opérations de permutation ne sont que des bittshifts,

Notre idée de base est de prendre plusieurs valeurs d'entrée, nécessitant la même permutation et les déplacer en parallèle. Par exemple, si l'entrée Bit 1 doit être déplacée vers la sortie Bit 6.

Y a-t-il un moyen de le faire? Nous n'avons aucun exemple de code en ce moment, car il n'ya absolument aucune idée de la manière de l'accomplir de manière performante.

La taille maximale de la valeur que nous avons sur nos platifères est de 128 bits, la valeur d'entrée la plus longue est de 64 bits. Le code doit être plus rapide, puis faire toute la permutation 128 fois.

edit

Voici un simple exemple de 8 bits d'une permutation < PRE> XXX

Le chiffre facilite l'utilisation de plusieurs clés d'entrée. C'est un chiffre à blocs. Le même motif doit donc être appliqué à tous les blocs de 64 bits de l'entrée.

Comme les permutations sont identiques pour chaque bloc d'entrée, nous voulons traiter plusieurs blocs d'entrée dans un Étape / Pour combiner les opérations pour plusieurs séquences d'entrée. Au lieu de déplacer 128times un bit par appel, déplaçant 1 fois 128 bits à la fois.

Edit2

Nous ne pouvions pas utiliser de threads, car nous n'avons pas pu utiliser le Code sur des systèmes embarqués sans support de filetage. Par conséquent, nous n'avons pas non plus accès aux bibliothèques externes et nous devons le garder unis c.

Solution

Après avoir testé et joué avec les réponses données que nous avons fait la manière suivante:

  • Nous mettons les bits simples de 128 valeurs 64 bits sur un tableau UINT128_T [64] *.
  • Pour la permutation, nous avons juste pour copier des pointeurs
  • Après tout, nous rétablissons la première opération et obtenez 128 valeurs permutées au dos

    Ouais, c'est vraiment si simple. Nous traitais de cette façon au début du projet, mais c'était trop lent. Il semble que nous avions un bug dans le testcode.

    Merci à tous, pour les astuces et la patience.


6 commentaires

Pouvez-vous énumérer plus d'exemples? Que «prendre plusieurs valeurs d'entrée, il faut la même permutation et les déplacer en parallèle» n'est pas tout à fait clair.


Vous dites que vous utilisez une plate-forme intégrée - laquelle? Les capacités de votre CPU vont évidemment être un facteur important ici.


Seulement pour la phase de permutation, la solution de tranchage de bits de Nemo ne semble pas être plus rapide (si je l'ai bien compris, la version B dans Ideone.com/oyoro devrait vous donner la méthode de tranchage de bits). Mais je pense qu'il a raison de dire: "Mettre en œuvre toutes les étapes de la représentation des trous de bits". Ici devrait survenir un avantage à mon avis.


@NICK Il y a une NDA, m'empêche de dire à beaucoup. Mais je pourrais vous dire que nos systèmes ont une prise en charge native pour ou , et , xor , nand , ni , adressant des modes de 8 bits à 128 bits et 256 registres 128 bits.


@Christian thx pour le travail de comparer les roulements. Nous allons essayer à nouveau avec bitslicing. Nous avions déjà essayé cela, et c'était beaucoup plus lent que dans vous exemple. Donc, bien jetez un coup d'œil s'il y a un problème avec le code.


@Thomas Il semble probable que les instructions de type SIMD soient une solution pourraient être optimisées, mais il est impossible de dire qui sont possibles sans connaître la plate-forme cible.


4 Réponses :


1
votes

Il semble difficile de faire la permutation dans un seul appel. Un cas particulier de votre problème, des bits d'inverser dans un entier, a besoin de plus d'un "appel" (qu'entendez-vous par appelez em>?). Voir Bit Twiddling Hacks de Sean pour des informations sur cet exemple.

Si Votre modèle de mappage n'est pas compliqué, vous pouvez peut-être trouver un moyen rapide de calculer la réponse :) Cependant, je ne sais pas si vous aimez cette méthode directe: p>

#include <stdio.h>

unsigned char mask[8];

//map bit to position
//0 -> 2
//1 -> 7
//2 -> 5
//...
//7 -> 6
unsigned char map[8] = {
    2,7,5,1,4,0,3,6
};


int main()
{
    int i;

    //input:
    //--------------------
    //bit 7 6 5 4 3 2 1 0
    //--------------------
    //val 0 0 1 0 0 1 1 0
    //--------------------
    unsigned char input = 0x26;

    //so the output should be 0xA1:
    //    1 0 1 0 0 0 0 1
    unsigned char output;

    for(i=0; i<8; i++){ //initialize mask once
        mask[i] = 1<<i;
    }

    //do permutation
    output = 0;
    for(i=0; i<8; i++){
        output |= (input&mask[i])?mask[map[i]]:0;
    }

    printf("output=%x\n", output);
    return 0;
}


2 commentaires

C'est la façon dont nous le faisons déjà. Et c'est ralenti si vous devez le faire avec une entrée de 100 Mo


Et votre exemple est un appel de fonction. Nous voulons faire combiner les opérations, car les bittshifts sont de coût élevé.



0
votes

Votre meilleur pari serait de regarder dans un type de schéma de filetage ... Vous pouvez utiliser un système de passage de message où vous envoyez chaque bloc à un ensemble fixe de threads ouvriers ou vous pouvez éventuellement configurer un pipeline avec non. -Locking unique producteur / file d'attente de consommateurs qui effectuent plusieurs quarts de travail de manière "synchrone". Je dis "synchrone" car un pipeline sur une CPU à usage général ne serait pas une opération de pipeline véritablement synchrone, comme si vous auriez sur un dispositif de fonction fixe, mais essentiellement pour une "tranche" donnée, chaque fil fonctionnerait sur Une étape du problème multi-étapes en même temps et vous «diffuseriez» les données source dans et hors du pipeline.


3 commentaires

Eh bien, alors vous n'allez pas obtenir de vrai parallélisme ... Aucun threads signifie que vous n'exécuterez votre code que sur un seul noyau et que le noyau unique ne peut pas faire deux choses à la fois.


Sry, j'étais un peu peu clair, je veux combiner les opérations.


La réponse Bitslicing montre comment un processeur 64 bits peut faire 64 bits en parallèle. Le truc avec tout le parallélisme est l'endroit où le trouvez-vous (threads, bit-parallèle) et comment l'exploite-t-il?



4
votes

Vous pouvez rendre le code bit-bit de Stan plus rapidement en utilisant huit tables de recherche cartographier des octets à 64 bits. Pour traiter un mot de 64 bits à partir de l'entrée, divisez-le en huit octets et recherchez chacun d'une table d'apparence différente, puis des résultats. Sur mon ordinateur, ce dernier est 10 fois plus rapide que l'approche bit-bit pour des permutations 32 bits. Évidemment, si votre système embarqué a peu de cache, alors 32 Ko 16 Ko de tables de recherche peut être un problème. Si vous traitez 4 bits à la fois, vous n'avez besoin que de 16 tables de recherche de 16 * 8 = 128 octets chacun, c'est-à-dire 2 Ko de tables de recherche.

Edit: La boucle interne pourrait ressembler à ceci: < / p> xxx


9 commentaires

Comme je dois déplacer les bits de n'importe quel endroit de n'importe quelle position, je ne pouvais pas diviser l'intrant et la sortie à 8 bits. Et nous devons utiliser peu à peu, parce que c'est un bitwise chiffre ...


Notez que l'idée est de mapper un octet dans un mot de 64 bits, juste pour permettre de déplacer des bits partout dans un mot de 64 bits. Tant que vous n'avez pas besoin de bouger un peu dans un autre mot de 64 bits, cela fonctionne bien.


Ensuite, vous devriez fournir un exemple, car je ne pouvais pas imaginer comment maintenant


J'ai ajouté un exemple de mise en œuvre de la boucle interne. La mise en place des tables de recherche est laissée comme un exercice pour le lecteur ...


Je sais comment cela fonctionne, mais lisez la dernière mise à jour sur ma question. Ce n'est pas plus rapide que de le faire de manière sérieuse. Événement Les recherches sur les cartes nécessitent plus d'instructions, puis sur le déplacement de chacune des 64 entrées en série avec des instructions de décalage fixes.


Je suppose que cela dépend du processeur. J'ai ajouté le code à la référence de Christian Ammer (en tant que version c) avec les résultats suivants: Version A Time = 0.675184; Version B Time = 1.3193; Version C Time = 0.016659


Thx pour les informations, je n'ai qu'un seul problème logique avec vous au-dessus des exemples. Comment puis-je vous assurer que les bits de sortie deviennent à la position directe. Lequel pourquoi le tableau a-t-il été préparé, vous ne pouvez donc utiliser que ou sans équipes?


Les changements sont précomptes dans la table de recherche. Si vous avez une fonction permute_word qui effectue la permutation 64 bits souhaitée, alors mappe [i] [x] = permute_word ((uint64_t) x << (8 * i)) ) sur une machine à petite Endian.


THX, nous travaillons actuellement sur une référence pour votre plate-forme, testant toutes les suggestions faites ici. Peut la victoire la plus rapide;)



2
votes

Je pense que vous recherchez peut-être un implémentation de tranchage bit . C'est ainsi que fonctionnent les impulsions des dessers les plus rapides. (Ou c'était avant que les instructions de SSE existaient, de toute façon.)

L'idée est d'écrire votre fonction de manière "bit-wise", représentant chaque bit de sortie comme une expression booléenne sur les bits d'entrée. Étant donné que chaque bit de sortie dépend uniquement des bits d'entrée, toute fonction peut être représentée de cette manière, même des choses comme des recherches d'addition, de multiplication ou de s-box. P>

L'astuce consiste à utiliser les bits d'un seul Inscrivez-vous pour représenter un seul bit à partir de plusieurs mots d'entrée em>. p>

Je vais illustrer avec une simple fonction de quatre bits. p>

supposons, par exemple, vous voulez Pour prendre des entrées de quatre bits du formulaire: p> xxx pré>

... et pour chaque entrée, calculez une sortie à quatre bits: p> xxx Pré>

Et vous voulez faire cela pour, dites, huit entrées. (OK pour quatre bits, une table de recherche serait la plus rapide. Mais il s'agit simplement d'illustrer le principe.) P>

Supposons que vos huit entrées sont les suivantes: P>

Y3 = X2;
Y2 = X3;
Y1 = X2 ^ X3;
Y0 = X1 ^ X2;


8 commentaires

Merci pour cette bonne description de Bitslicing. Mais je ne comprends pas comment nous pourrions faire un permis de compensation basé sur la matrice avec cette mise en œuvre. Bien sûr, nous utilisons déjà des bitslices pour certaines parties du code, mais je ne sais pas comment cela pourrait nous aider à nous aider à des permutations comme celle que j'ai expliquée ...


@THOMAS: Mais les permutations sont simples. Dans mon exemple, les bits X3 et X2 de chaque entrée sont échangés (permutés) pour produire la sortie correspondante ... et à nouveau, quelle que soit votre fonction (même une table de recherche), il existe certains Représentation de celui-ci en termes d'opérations booléennes sur les bits d'entrée. (La logique booléenne est universelle.)


S'il vous plaît expliquer-moi: comment puis-je déplacer 128 entrées 64 bits ce style: le bit d'entrée 1 (pour chacun des 128) doit émettre un bit 58 en 128 sorties différentes. Et bit 58 au bit 9 par exemple. Vous avez mentionné des. Prenez la permutation (pas les SBox) comme exemple;)


Vos registres sont-ils 64 bits? Entraînons ensuite 64 entrées 64 bits à la place. Premièrement, prise de bit 0 de Toutes les entrées 64 et mettez-les dans un mot de 64 bits x0. Ensuite, prenez le bit 1 de toutes les entrées 64 et mettez-les dans un mot de 64 bits x1. Et ainsi de suite jusqu'à x63. (Oui, cette partie est lente, mais vous ne le faites qu'une fois.) Maintenant, si la sortie Bit 9 doit être un bit d'entrée égal 58, il suffit de définir "y9 = x58". Etc. À la fin, Y0 tient le bit 0 de toutes les sorties 64. Y1 tient un bit 1 de toutes les sorties 64. Etc. Vous pouvez effectuer une permutation arbitraire de 64 bits sur 64 entrées avec seulement 64 instructions ... Utilisation du codage de tranche de bits


Mais je ne vois pas où cela ressort plus vite, alors faites cela pour chaque morceau, car je dois: * transformer 64 chaînes d'inBut * déplacer la commande de sortie * transformez-les en arrière


En supposant que vous preniez les intrants à travers plusieurs étapes (qui, pour Crypto, vous êtes sûrement?), Vous pouvez faire la transformation une fois au début, implémentez tous de ces étapes sur La représentation des tranches de bits, puis replacez-la une fois à la fin. C'est comme ça que le des cracker a travaillé ... DES a 16 "rounds". Ils ont mis en œuvre toutes les rondes pour travailler directement sur la forme codée de la tranche de bits. Le coût de la transformation de l'entrée et de la production était plus que prévu par le paralellisme de la mise en oeuvre des tranches de bits.


Je n'ai pas pu trouver une mise en œuvre complète Bitslice ou tout document qui en parle actuellement, peut-être que vous avez un lien? Toutes les implémentations que j'ai trouvées n'ont que Bitslice SBox


+1: J'aime l'idée de tranches de bit et j'ai essayé de comparer les solutions ( Ideone.com/oyoro ) . Il semble que l'avantage de tranchage binaire sort lorsque (comme vous l'avez mentionné), vous souhaitez combiner plus d'une fonction (non seulement permutation).