Alternative plus rapide aux boucles imbriquées?

85

J'ai besoin de créer une liste de combinaisons de nombres. Les nombres sont assez petits donc je peux utiliser byteplutôt que int. Cependant, il nécessite de nombreuses boucles imbriquées afin d'obtenir toutes les combinaisons possibles. Je me demande s'il existe une manière plus efficace de faire ce que je recherche. Le code jusqu'à présent est:

var data = new List<byte[]>();
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
    data.Add(new [] {a, b, c, d, e, f, g, h, i, j, k, l, m});
}

J'envisageais d'utiliser quelque chose comme a BitArraymais je ne sais pas comment je pourrais l'intégrer.

Toutes les recommandations seraient grandement appréciés. Sinon, c'est peut-être le moyen le plus rapide de faire ce que je veux?

EDIT Quelques points rapides (et excuses, je ne les ai pas mis dans le message d'origine):

  • Les nombres et leur ordre (2, 3, 4, 3, 4, 3, 3, etc.) sont très importants, donc utiliser une solution telle que Générer des permutations à l'aide de LINQ n'aidera pas car les maximums dans chaque `` colonne '' sont différent
  • Je ne suis pas mathématicien, alors je m'excuse si je n'utilise pas correctement les termes techniques comme «permutations» et «combinaisons» :)
  • Je ne dois remplir toutes ces combinaisons à la fois - je ne peux pas saisir un ou l' autre , selon un indice
  • L'utilisation byteest plus rapide que l'utilisation int, je le garantis . Il est également bien meilleur sur l'utilisation de la mémoire d'avoir plus de 67 millions de tableaux d'octets plutôt que de
  • Mon objectif ultime ici est de rechercher une alternative plus rapide aux boucles imbriquées.
  • J'ai envisagé d'utiliser la programmation parallèle, mais en raison de la nature itérative de ce que j'essaie d'accomplir, je n'ai pas pu trouver un moyen de le faire avec succès (même avec ConcurrentBag) - mais je suis heureux d'avoir tort :)

CONCLUSION

Caramiriel a fourni une bonne micro-optimisation qui réduit le temps des boucles, j'ai donc marqué cette réponse comme correcte. Eric a également mentionné qu'il est plus rapide de pré-attribuer la liste. Mais, à ce stade, il semble que les boucles imbriquées soient en fait le moyen le plus rapide possible de le faire (déprimant, je sais!).

Si vous voulez essayer exactement ce avec quoi j'essayais de comparer StopWatch, optez pour 13 boucles comptant jusqu'à 4 dans chaque boucle - cela fait environ 67 millions de lignes dans la liste. Sur ma machine (i5-3320M 2,6 GHz), il faut environ 2,2 s pour faire la version optimisée.

benpage
la source
1
Essayez d'utiliser linq et si vous utilisez un processeur multicœur, alors Parrallel.for
Jalpesh Vadgama
1
basé sur ce que je vois, ce ne sont pas les permutations mais les combinaisons de quelques très petits ensembles (2-4 éléments) est-ce vrai ou voulez-vous vraiment toutes / certaines permutations d' un ensemble?
Carsten
Je suppose que vous avez déjà cherché sur bing.com/search?q=c%23+permutation+enumerable et pour une raison quelconque (non mentionnée dans l'article), vous vous êtes prononcé contre les réponses existantes telles que stackoverflow.com/questions/4319049/ ... options que vous avez examinées et que vous avez refusées pour améliorer cette question.
Alexei Levenkov
3
S'il s'agit de performances: vous pouvez préallouer la liste (constructeur) et dérouler quelques boucles, mais je pense que c'est à ce sujet ... à part le précalcul et le stockage de ces nombres. Les boucles (overhead) sont probablement les plus coûteuses de toutes, car il y a peu d'opérations à l'intérieur du corps.
Caramiriel
5
@benpage: Pourquoi avez-vous besoin de générer toutes les combinaisons à l'avance? Pourquoi ne pas générer une combinaison à partir de son index lorsque vous en avez besoin?
Pieter Witvoet

Réponses:

61

Vous pouvez utiliser les propriétés d'une structure et allouer la structure à l'avance. J'ai coupé certains niveaux dans l'exemple ci-dessous, mais je suis sûr que vous serez en mesure de comprendre les détails. Fonctionne environ 5 à 6 fois plus vite que l'original (mode de libération).

Le bloc:

struct ByteBlock
{
    public byte A;
    public byte B;
    public byte C;
    public byte D;
    public byte E;
}

La boucle:

var data = new ByteBlock[2*3*4*3*4];
var counter = 0;

var bytes = new ByteBlock();

for (byte a = 0; a < 2; a++)
{
    bytes.A = a;
    for (byte b = 0; b < 3; b++)
    {
        bytes.B = b;
        for (byte c = 0; c < 4; c++)
        {
            bytes.C = c;
            for (byte d = 0; d < 3; d++)
            {
                bytes.D = d;
                for (byte e = 0; e < 4; e++)
                {
                    bytes.E = e;
                    data[counter++] = bytes;
                }
            }
        }
    }
}

C'est plus rapide car il n'alloue pas de nouvelle liste à chaque fois que vous l'ajoutez à la liste. De plus, comme il crée cette liste, il a besoin d'une référence à toutes les autres valeurs (a, b, c, d, e). Vous pouvez supposer que chaque valeur n'est modifiée qu'une seule fois dans la boucle, afin que nous puissions l'optimiser pour ce faire (localité des données).

Lisez également les commentaires pour les effets secondaires.

Modification de la réponse pour utiliser un T[]au lieu d'un List<T>.

Caramiriel
la source
1
C'est une structure, donc vous devriez être d'accord =) ils sont tous uniques. Il est copié lors de l'appel de la List<T>.Addméthode.
Caramiriel
4
C'est encore plus rapide si vous avez alloué la capacité à la liste ()
Eric
5
Faites attention aux exceptions de stackoverflow lors de l'allocation d'un trop grand nombre d'objets sur la pile.
Andrei Tătar
7
@Andrew Je ne comprends pas votre point. Ce code n'est pas récursif et a une utilisation minimale de la pile.
CodesInChaos
3
@Andrew: Il n'y a plus de mémoire, pas stackoverflow. C'est parce que la List<T>.Add()méthode va au-delà du point de ce qu'elle peut stocker. Cela le fera redimensionner (double de taille), ce qui dépasse 2 Go de mémoire. Essayez de préallouer en utilisant le nouveau List <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), bien qu'il soit déjà `` aléatoire '' que vous ayez besoin d'un bloc complet de 2 Go de ces données en mémoire. Notez également que data.ToArray (); est cher car il garde les éléments en mémoire deux fois à ce stade. [reformulé]
Caramiriel
33

Ce que vous faites, c'est compter (avec une base variable, mais toujours en comptant).

Puisque vous utilisez C #, je suppose que vous ne voulez pas jouer avec une disposition de mémoire et des structures de données utiles qui vous permettent vraiment d' optimiser votre code.

Donc, ici, je poste quelque chose de différent, qui peut ne pas convenir à votre cas, mais cela vaut la peine de noter: si vous accédez réellement à la liste de manière clairsemée, voici une classe qui vous permet de calculer le i-ème élément en temps linéaire (plutôt que exponentielle comme les autres réponses)

class Counter
{
    public int[] Radices;

    public int[] this[int n]
    {
        get 
        { 
            int[] v = new int[Radices.Length];
            int i = Radices.Length - 1;

            while (n != 0 && i >= 0)
            {
                //Hope C# has an IL-opcode for div-and-reminder like x86 do
                v[i] = n % Radices[i];
                n /= Radices[i--];
            }
            return v;
        }
    }
}

Vous pouvez utiliser cette classe de cette façon

Counter c = new Counter();
c.Radices = new int[] { 2,3,4,3,4,3,3,4,2,4,4,3,4};

maintenant c[i]est le même que votre liste, nommez - le l, l[i].

Comme vous pouvez le voir, vous pouvez facilement éviter toutes ces boucles :) même lorsque vous pré-calculez toute la liste dans son ensemble, car vous pouvez simplement implémenter un compteur Carry-Ripple.

Les compteurs sont un sujet très étudié, je vous conseille vivement de rechercher de la littérature si vous vous sentez.

Adam
la source
4
J'aime votre réponse, mais dire que toutes les autres réponses sont exponentielles est faux.
Biscuits
1
Quelle est la vitesse à ce sujet par rapport à la réponse de Caramiriel?
John Odom
17
"C-kiddy- #", vraiment? Cela semble totalement inutile.
KChaloux
2
Et c'est le cas: Math.DivRem
Caramiriel
1
Je pense qu'au-delà d'un certain niveau, l'optimisation est une question d'utilisation. Par exemple, si chaque tableau ne doit être utilisé qu'une seule fois, vous pouvez éviter l'allocation de mémoire intensive, qui est le goulot d'étranglement critique à mon avis. De plus, si vous voulez calculer toute la valeur, vous devez exploiter le fait que vous faites des incréments simples (c'est-à-dire +1 incrément) en évitant une division. Ceci est plus conçu comme un vêtement "prêt à l'emploi" ou un prototype, je n'ai pas vraiment essayé de l'accélérer, je l'aime juste comme ça :)
14

Méthode 1

Une façon d'accélérer le processus consiste à spécifier la capacité si vous prévoyez de continuer à l'utiliser List<byte[]>, comme ceci.

var data = new List<byte[]>(2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4);

Méthode 2

De plus, vous pouvez utiliser System.Arraydirectement pour obtenir un accès plus rapide. Je recommande cette approche si votre question insiste pour que chaque élément soit physiquement peuplé en mémoire, dès le départ.

var data = new byte[2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4][];
int counter = 0;

for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
        for (byte c = 0; c < 4; c++)
            for (byte d = 0; d < 3; d++)
                for (byte e = 0; e < 4; e++)
                    for (byte f = 0; f < 3; f++)
                        for (byte g = 0; g < 3; g++)
                            for (byte h = 0; h < 4; h++)
                                for (byte i = 0; i < 2; i++)
                                    for (byte j = 0; j < 4; j++)
                                        for (byte k = 0; k < 4; k++)
                                            for (byte l = 0; l < 3; l++)
                                                for (byte m = 0; m < 4; m++)
                                                    data[counter++] = new[] { a, b, c, d, e, f, g, h, i, j, k, l, m };

Cela prend 596 ms pour terminer sur mon ordinateur, ce qui est environ 10,4% plus rapide que le code en question (qui prend 658 ms).

Méthode 3

Vous pouvez également utiliser la technique suivante pour une initialisation à faible coût qui convient à l'accès de manière clairsemée. Ceci est particulièrement favorable lorsque seuls certains éléments peuvent être nécessaires et que leur détermination initiale est considérée comme inutile. De plus, des techniques comme celles-ci peuvent devenir la seule option viable lorsque vous travaillez avec des éléments plus vastes lorsque la mémoire est insuffisante.

Dans cette mise en œuvre, chaque élément doit être déterminé paresseusement, à la volée, lors de l'accès. Naturellement, cela entraîne un coût de CPU supplémentaire qui est encouru lors de l'accès.

class HypotheticalBytes
{
    private readonly int _c1, _c2, _c3, _c4, _c5, _c6, _c7, _c8, _c9, _c10, _c11, _c12;
    private readonly int _t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9, _t10, _t11;

    public int Count
    {
        get { return _t0; }
    }

    public HypotheticalBytes(
        int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12)
    {
        _c1 = c1;
        _c2 = c2;
        _c3 = c3;
        _c4 = c4;
        _c5 = c5;
        _c6 = c6;
        _c7 = c7;
        _c8 = c8;
        _c9 = c9;
        _c10 = c10;
        _c11 = c11;
        _c12 = c12;
        _t11 = _c12 * c11;
        _t10 = _t11 * c10;
        _t9 = _t10 * c9;
        _t8 = _t9 * c8;
        _t7 = _t8 * c7;
        _t6 = _t7 * c6;
        _t5 = _t6 * c5;
        _t4 = _t5 * c4;
        _t3 = _t4 * c3;
        _t2 = _t3 * c2;
        _t1 = _t2 * c1;
        _t0 = _t1 * c0;
    }

    public byte[] this[int index]
    {
        get
        {
            return new[]
            {
                (byte)(index / _t1),
                (byte)((index / _t2) % _c1),
                (byte)((index / _t3) % _c2),
                (byte)((index / _t4) % _c3),
                (byte)((index / _t5) % _c4),
                (byte)((index / _t6) % _c5),
                (byte)((index / _t7) % _c6),
                (byte)((index / _t8) % _c7),
                (byte)((index / _t9) % _c8),
                (byte)((index / _t10) % _c9),
                (byte)((index / _t11) % _c10),
                (byte)((index / _c12) % _c11),
                (byte)(index % _c12)
            };
        }
    }
}

Cela prend 897 ms pour terminer sur mon ordinateur (également créer et ajouter à un Arraycomme dans la méthode 2 ), ce qui est environ 36,3% plus lent que le code en question (qui prend 658 ms).

Des biscuits
la source
1
Votre deuxième suggestion est également une économie significative en termes de consommation de mémoire. (Mais je
noterais
J'ai besoin que la liste entière soit créée en même temps - je ne peux pas faire référence à un index dans la liste.
benpage
@Taemyr Merci. Je vais mettre à jour pour le noter en conséquence. Si l'implémentation insiste vraiment pour que la liste entière soit remplie à l'avance, cette troisième option ne fonctionnera évidemment pas pour vous.
Biscuits
3
@benpage Pourquoi avez-vous besoin que la liste soit remplie?
Taemyr
14

Sur ma machine, cela génère les combinaisons en 222 ms vs 760 ms (les 13 boucles for):

private static byte[,] GenerateCombinations(byte[] maxNumberPerLevel)
{
    var levels = maxNumberPerLevel.Length;

    var periodsPerLevel = new int[levels];
    var totalItems = 1;
    for (var i = 0; i < levels; i++)
    {
        periodsPerLevel[i] = totalItems;
        totalItems *= maxNumberPerLevel[i];
    }

    var results = new byte[totalItems, levels];

    Parallel.For(0, levels, level =>
    {
        var periodPerLevel = periodsPerLevel[level];
        var maxPerLevel = maxNumberPerLevel[level];
        for (var i = 0; i < totalItems; i++)
            results[i, level] = (byte)(i / periodPerLevel % maxPerLevel);
    });

    return results;
}
Andrei Tătar
la source
C'est une excellente réponse! Malheureusement, il fonctionne plus lentement que les boucles imbriquées. Avez-vous une chance de pouvoir modifier en utilisant TPL?
benpage
encore un peu plus lent, malheureusement.
benpage
1
@benpage Il existe un moyen simple de le rendre au moins 2 fois plus rapide. Il vous suffit de changer le type de résultats en int [,]. Cela allouera toute la mémoire du tableau en un seul appel. Je ne sais pas comment cela répond à vos besoins (modification du type de retour).
Andrei Tătar
8
var numbers = new[] { 2, 3, 4, 3, 4, 3, 3, 4, 2, 4, 4, 3, 4 };
var result = (numbers.Select(i => Enumerable.Range(0, i))).CartesianProduct();

Utilisation de la méthode d'extension sur http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    // base case: 
    IEnumerable<IEnumerable<T>> result =
        new[] { Enumerable.Empty<T>() };
    foreach (var sequence in sequences)
    {
        // don't close over the loop variable (fixed in C# 5 BTW)
        var s = sequence;
        // recursive case: use SelectMany to build 
        // the new product out of the old one 
        result =
            from seq in result
            from item in s
            select seq.Concat(new[] { item });
    }
    return result;
}
Eric
la source
1
cela fonctionne beaucoup plus lentement :(
benpage
8

List a un tableau en interne où il stocke ses valeurs, avec une longueur fixe. Lorsque vous appelez List.Add, il vérifie s'il y a suffisamment d'espace. Quand il ne peut pas ajouter le nouvel élément, il créera un nouveau tableau de plus grande taille, copiera tous les éléments précédents, puis en ajoutera un nouveau. Cela prend plusieurs cycles.

Puisque vous connaissez déjà le nombre d'éléments, vous pouvez créer la liste de la bonne taille, qui devrait déjà être beaucoup plus rapide.

De plus, je ne sais pas comment vous accédez aux valeurs, mais vous pouvez créer cette chose et enregistrer l'image dans le code (le charger à partir du disque sera probablement plus lent que ce que vous faites maintenant. chose?

gjvdkamp
la source
J'ai en fait essayé de pré-allouer un tableau régulier et croyez-le ou non, c'est plus lent. Comme je l'ai dit ci-dessus, cela doit être créé à la volée, je ne peux pas le calculer une fois et le laisser.
benpage
vraiment? wow - vous exécutez avec les optimisations activées, non? (il suffit de demander)
Carsten
Ah c'est un autre problème, les tableaux réguliers [x, y] sont agréables à utiliser mais un tableau de tableaux sera plus rapide. stackoverflow.com/questions/597720/… en raison de la façon dont ils sont implémentés sous le capot dans IL
gjvdkamp
5

Voici une manière différente qui n'a besoin que de 2 boucles. L'idée est d'augmenter le premier élément et si ce nombre dépasse, d'augmenter le suivant.

Au lieu d'afficher les données, vous pouvez utiliser currentValues.Clone et ajouter cette version clonée à votre liste. Pour moi, cela a fonctionné plus rapidement que votre version.

byte[] maxValues = {2, 3, 4};
byte[] currentValues = {0, 0, 0};

do {
    Console.WriteLine("{0}, {1}, {2}", currentValues[0], currentValues[1], currentValues[2]);

    currentValues[0] += 1;

    for (int i = 0; i <= maxValues.Count - 2; i++) {
        if (currentValues[i] < maxValues[i]) {
            break;
        }

        currentValues[i] = 0;
        currentValues[i + 1] += 1;
    }

// Stop the whole thing if the last number is over
// } while (currentValues[currentValues.Length-1] < maxValues[maxValues.Length-1]);
} while (currentValues.Last() < maxValues.Last());
  • J'espère que ce code fonctionne, je l'ai converti à partir de vb
le_lotus
la source
3

Tous vos nombres sont des constantes de temps de compilation.

Qu'en est-il du déroulement de toutes les boucles dans une liste (en utilisant votre programme pour écrire du code):

data.Add(new [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
data.Add(new [] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
etc.

Cela devrait au moins supprimer la surcharge des boucles for (s'il y en a).

Je ne suis pas trop familier avec C #, mais il semble y avoir des moyens de sérialiser des objets. Et si vous venez de générer cette liste et de la sérialiser sous une forme ou une autre? Je ne suis pas sûr que la désérialisation soit plus rapide que la création de la liste et l'ajout des éléments.

nul
la source
La sérialisation est une approche vraiment géniale pour sortir des sentiers battus!
Joel B
Malheureusement, les maximums de la liste sont dynamiques, je ne peux pas taper cela de manière statique. Bonne idée cependant!
benpage
2

Avez-vous besoin que le résultat soit un tableau de tableaux? Avec la configuration actuelle, la longueur des tableaux internes est fixe et pourrait être remplacée par des structures. Cela permettrait à la chose entière d'être réservée comme un bloc continu de mémoire et fournit un accès plus facile aux éléments (je ne sais pas comment vous utiliserez cette chose plus tard).

L'approche ci-dessous est beaucoup plus rapide (41ms vs 1071ms pour l'original sur ma box):

struct element {
    public byte a;
    public byte b;
    public byte c;
    public byte d;
    public byte e;
    public byte f;
    public byte g;
    public byte h;
    public byte i;
    public byte j;
    public byte k;
    public byte l;
    public byte m;
}

element[] WithStruct() {
    var t = new element[3981312];
    int z = 0;
    for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
    for (byte c = 0; c < 4; c++)
    for (byte d = 0; d < 3; d++)
    for (byte e = 0; e < 4; e++)
    for (byte f = 0; f < 3; f++)
    for (byte g = 0; g < 3; g++)
    for (byte h = 0; h < 4; h++)
    for (byte i = 0; i < 2; i++)
    for (byte j = 0; j < 4; j++)
    for (byte k = 0; k < 4; k++)
    for (byte l = 0; l < 3; l++)
    for (byte m = 0; m < 4; m++)
    {
        t[z].a = a;
        t[z].b = b;
        t[z].c = c;
        t[z].d = d;
        t[z].e = e;
        t[z].f = f;
        t[z].g = g;
        t[z].h = h;
        t[z].i = i;
        t[z].j = j;
        t[z].k = k;
        t[z].l = l;
        t[z].m = m;
        z++;
    }
    return t;
}
gjvdkamp
la source
Bonne idée - en fait, c'est en fait ce que j'ai fait dans mon projet réel - je ne l'ai tout simplement pas mis dans la solution d'origine à cause de la simplicité. Je cherchais principalement une meilleure alternative aux boucles imbriquées.
benpage
1

Qu'en est-il de l'utiliser Parallel.For()pour l'exécuter? (Félicitations d'optimisation de structure à @Caramiriel ). J'ai légèrement modifié les valeurs (a est 5 au lieu de 2) donc je suis plus confiant dans les résultats.

    var data = new ConcurrentStack<List<Bytes>>();
    var sw = new Stopwatch();

    sw.Start();

    Parallel.For(0, 5, () => new List<Bytes>(3*4*3*4*3*3*4*2*4*4*3*4),
      (a, loop, localList) => {
        var bytes = new Bytes();
        bytes.A = (byte) a;
        for (byte b = 0; b < 3; b++) {
          bytes.B = b;
          for (byte c = 0; c < 4; c++) {
            bytes.C = c; 
            for (byte d = 0; d < 3; d++) {
              bytes.D = d; 
              for (byte e = 0; e < 4; e++) {
                bytes.E = e; 
                for (byte f = 0; f < 3; f++) {
                  bytes.F = f; 
                  for (byte g = 0; g < 3; g++) {
                    bytes.G = g; 
                    for (byte h = 0; h < 4; h++) {
                      bytes.H = h; 
                      for (byte i = 0; i < 2; i++) {
                        bytes.I = i; 
                        for (byte j = 0; j < 4; j++) {
                          bytes.J = j; 
                          for (byte k = 0; k < 4; k++) {
                            bytes.K = k; 
                            for (byte l = 0; l < 3; l++) {
                              bytes.L = l;
                              for (byte m = 0; m < 4; m++) {
                                bytes.M = m;
                                localList.Add(bytes);
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }


        return localList;
      }, x => {
        data.Push(x);
    });

    var joinedData = _join(data);

_join() est une méthode privée, définie comme:

private static IList<Bytes> _join(IEnumerable<IList<Bytes>> data) {
  var value = new List<Bytes>();
  foreach (var d in data) {
    value.AddRange(d);
  }
  return value;
}

Sur mon système, cette version fonctionne environ 6 fois plus vite (1,718 secondes contre 0,266 secondes).

jdphenix
la source
1
Ceci est à peu près garanti pour vous donner un faux partage et sera probablement plusieurs fois plus lent.
gjvdkamp
Pas mal - malheureusement, il tourne plus lentement que les boucles for. FWIW je l'ai essayé avec TOUS les Parallel.Fors et VS s'est écrasé!
benpage
@gjvdkamp J'ai mis à jour ma réponse avec une version parallèle qui, selon moi, élimine le problème de faux partage.
jdphenix
0

Certains de vos nombres tiennent entièrement sur un nombre entier de bits, vous pouvez donc les «emballer» avec le numéro de niveau supérieur:

for (byte lm = 0; lm < 12; lm++)
{
    ...
    t[z].l = (lm&12)>>2;
    t[z].m = lm&3;
    ...
}

Bien sûr, cela rend le code moins lisible, mais vous avez enregistré une boucle. Cela peut être fait chaque fois que l'un des nombres est une puissance de deux, ce qui correspond à sept fois dans votre cas.

Fabien Dupont
la source
J'aimerais en savoir plus sur cette réponse - pourriez-vous en dire plus?
benpage
Désolé de la réponse tardive. m va de 0 à 3, ce qui en binaire fait 00 à 11, l de 0 à 2, ce qui fait 00 à 10, donc si vous les imprimez séparément, cela ferait: 00 00 00 01 00 10 00 11 01 00 .. . et (1100) b puis on décale de deux bits pour avoir le nombre "réel". Au fait, je viens de réaliser qu'il suffit de faire lm >> 2 dans ce cas.
Fabien Dupont
0

Voici une autre solution. En dehors de VS, il fonctionne aussi vite que 437,5 ms, ce qui est 26% plus rapide que le code d'origine (593,7 sur mon ordinateur):

static List<byte[]> Combinations(byte[] maxs)
{
  int length = maxs.Length;
  int count = 1; // 3981312;
  Array.ForEach(maxs, m => count *= m);
  byte[][] data = new byte[count][];
  byte[] counters = new byte[length];

  for (int r = 0; r < count; r++)
  {
    byte[] row = new byte[length];
    for (int c = 0; c < length; c++)
      row[c] = counters[c];
    data[r] = row;

    for (int i = length - 1; i >= 0; i--)
    {
      counters[i]++;
      if (counters[i] == maxs[i])
        counters[i] = 0;
      else
        break;
    }
  }

  return data.ToList();
}
Henrik
la source