İç içe döngülere daha hızlı bir alternatif mi?


85

Sayı kombinasyonlarından oluşan bir liste oluşturmam gerekiyor. Rakamlar oldukça küçük olduğundan byteyerine kullanabilirim int. Ancak, her olası kombinasyonu elde etmek için birçok iç içe döngü gerektirir. Peşinde olduğum şeyi yapmanın daha verimli bir yolu olup olmadığını merak ediyorum. Şimdiye kadarki kod:

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});
}

A gibi bir şey kullanmayı düşünüyordum BitArrayama onu nasıl dahil edebileceğimden emin değilim.

Her tavsiye çok makbule geçecektir. Alternatif olarak, belki de istediğimi yapmanın en hızlı yolu budur?

DÜZENLE Birkaç hızlı nokta (ve bunları orijinal gönderiye koymadım özür dilerim):

  • Sayıları ve bunların sırası (2, 3, 4, 3, 4, 3, 3 vb.) Çok önemlidir, bu nedenle LINQ kullanarak Permütasyon Oluşturmak gibi bir çözüm kullanmak yardımcı olmaz çünkü her bir 'sütundaki' maksimum farklı
  • Matematikçi değilim, bu yüzden 'permütasyonlar' ve 'kombinasyonlar' gibi teknik terimleri doğru kullanmıyorsam özür dilerim :)
  • Ben do Sadece bir tanesini kapmak olamaz veya başka bir dizin dayalı - Bir defada bu kombinasyonlardan her doldurmak gerekir
  • Kullanmak byte, kullanmaktan daha hızlıdır int, garanti ederim . İnts yerine 67 milyondan fazla bayt dizisine sahip olmak bellek kullanımında çok daha iyidir
  • Buradaki nihai hedefim, iç içe döngülere daha hızlı bir alternatif aramaktır.
  • Paralel programlamayı kullanmayı düşündüm, ancak başarmaya çalıştığım şeyin yinelemeli doğası nedeniyle, bunu başarılı bir şekilde yapmanın bir yolunu bulamadım (ile bile ConcurrentBag) - ancak yanlış olduğunu kanıtladığım için mutluyum :)

SONUÇ

Caramiriel, döngüleri bir süre kısaltan iyi bir mikro optimizasyon sağladı, bu yüzden bu yanıtı doğru olarak işaretledim. Eric ayrıca Listeyi önceden tahsis etmenin daha hızlı olduğunu belirtti. Ancak bu aşamada, iç içe geçmiş döngüler aslında bunu yapmanın en hızlı yolu gibi görünüyor (iç karartıcı, biliyorum!).

Karşılaştırmaya çalıştığım şeyi tam olarak denemek istiyorsanız StopWatch, her döngüde 4'e kadar sayan 13 döngü ile gidin - bu, listede yaklaşık 67 milyon + satır demektir. Makinemde (i5-3320M 2.6GHz) optimize edilmiş sürümü yapmak yaklaşık 2.2 saniye sürer.


1
Linq'i

1
Gördüğüm şeye dayanarak bunların permütasyonlar değil, birkaç çok küçük (2-4 eleman) kümenin kombinasyonları bu doğru mu yoksa gerçekten bir kümenin tüm / bazı permütasyonlarını mı istiyorsunuz ?
Carsten

Bing.com/search?q=c%23+permutation+enumerable'ı zaten aradığınızı ve bazı nedenlerden dolayı ( gönderide belirtilmemiştir) stackoverflow.com/questions/4319049/… gibi mevcut yanıtlara karşı karar verdiğinizi varsayıyorum ... Bu soruyu daha iyi hale getirmek için baktığınız ve aleyhinize karar verdiğiniz seçenekler.
Alexei Levenkov

3
Bu performansla ilgiliyse: Listeyi (yapıcı) önceden tahsis edebilir ve bazı döngüleri açabilirsiniz, ancak bu sayıları önceden hesaplamak ve depolamak dışında sanırım bununla ilgili. Vücudun içinde düşük miktarda işlem olduğundan, döngüler (tepegöz) muhtemelen hepsinden daha maliyetli olanıdır.
Caramiriel

5
@benpage: Neden tüm kombinasyonları önceden oluşturmanız gerekiyor? İhtiyaç duyduğunuzda neden dizininden bir kombinasyon oluşturmuyorsunuz?
Pieter Witvoet

Yanıtlar:


61

Bir hatırlatma olarak: kendi çözümünüzü geliştirirken muhtemelen bu tür bir koda ihtiyacınız yoktur. Bu sadece çok özel durumlarda kullanılabilir ve kullanılmalıdır. Okunabilirlik genellikle hızdan daha önemlidir.

Bir yapının özelliklerini kullanabilir ve yapıyı önceden tahsis edebilirsiniz. Aşağıdaki örnekteki bazı seviyeleri kestim, ama eminim ayrıntıları anlayabilirsiniz. Orijinalden yaklaşık 5-6 kat daha hızlı çalışır (serbest bırakma modu).

Blok:

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

Döngü:

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;
                }
            }
        }
    }
}

Daha hızlıdır çünkü listeye her eklediğinizde yeni bir liste ayırmaz. Ayrıca bu listeyi oluşturduğu için, diğer her değere (a, b, c, d, e) bir referansa ihtiyacı var. Her bir değerin döngü içinde yalnızca bir kez değiştirildiğini varsayabilirsiniz, böylece bunu yapmak için onu optimize edebiliriz (veri konumu).

Ayrıca yan etkilerle ilgili yorumları da okuyun.

Yanıtı T[], a yerine bir kullanacak şekilde düzenledi List<T>.


1
Bu bir yapı, yani iyi olmalısın =) hepsi benzersiz. List<T>.AddYöntemi çağırırken kopyalanır .
Caramiriel

4
Kapasiteyi Listeye () ayırırsanız daha da hızlı
Eric

5
Dikkat stackoverflow yığın çok fazla nesne tahsis ederken istisnalar.
Andrei Tătar

7
@Andrew ne demek istediğini anlamadım. Bu kod yinelemeli değildir ve minimum yığın kullanımına sahiptir.
CodesInChaos

3
@Andrew: Bu bellek yetersiz, yığın aşımı değil. Bunun nedeni, List<T>.Add()yöntemin saklayabileceği noktasının ötesine geçmesidir. Bu, 2GB hafızayı aşan yeniden boyutlandırılmasını (boyut olarak iki katına çıkar) sağlayacaktır. Yeni List <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)) kullanarak önceden tahsis etmeyi deneyin, ancak zaten 'rasgele' bu verinin tam 2GB bloğuna bellek içinde ihtiyacınız olacaktır. Ayrıca data.ToArray (); pahalıdır, çünkü bu noktada öğeleri iki kez bellekte tutar. [yeniden ifade edildi]
Caramiriel

33

Yaptığınız şey saymaktır (değişken tabanda ama yine de sayıyor).

C # kullandığınız için, kodunuzu gerçekten optimize etmenize izin veren kullanışlı bellek düzeni ve veri yapılarıyla oynamak istemediğinizi varsayıyorum .

Bu yüzden burada farklı bir şey gönderiyorum, bu sizin durumunuza uymayabilir, ancak kayda değer: Listeye gerçekten seyrek bir şekilde erişirseniz, burada i-inci elementi lineer zamanda hesaplamanıza izin veren bir sınıf (bunun yerine diğer cevaplar gibi üstel olmaktan çok)

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;
        }
    }
}

Bu sınıfı bu şekilde kullanabilirsiniz

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

Şimdi c[i]söyle, listenizde aynıdır l, l[i].

Gördüğünüz gibi, bir Carry-Ripple sayacı uygulayabileceğiniz için, tüm listeyi bir bütün olarak önceden hesaplasanız bile, tüm bu döngülerden kolayca kaçınabilirsiniz :).

Sayaçlar çok çalışılmış bir konudur, eğer hissediyorsanız biraz literatür aramanızı şiddetle tavsiye ederim.


4
Cevabınızı beğendim, ancak diğer tüm cevapların üstel olduğunu söylemek doğru değil.
Biscuits

1
Caramiriel'in cevabına kıyasla bunun hızı nedir?
John Odom

17
"C-kiddy- #", gerçekten mi? Bu tamamen gereksiz görünüyor.
KChaloux

2
Ve yapar: Math.DivRem
Caramiriel

1
Sanırım bir seviyenin ötesinde, Optimizasyon bir kullanım meselesi. Örneğin, her dizi yalnızca bir kez kullanılacaksa, benim görüşüme göre kritik darboğaz olan yoğun bellek ayırma işleminden kurtulabilirsiniz. Ayrıca, tüm değeri hesaplamak istiyorsanız, bir bölmeden kaçınarak tek artışlar (yani +1 artış) yaptığınız gerçeğinden yararlanmalısınız. Bu daha çok "kutudan çıkmış" bir cevap veya bir prototip olarak tasarlanmıştır, gerçekten hızlandırmaya çalışmadım, sadece bu şekilde seviyorum :)

14

Yöntem 1

Daha hızlı hale getirmenin bir yolu, bunun List<byte[]>gibi kullanmaya devam etmeyi planlıyorsanız kapasiteyi belirlemektir .

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

Yöntem 2

Ayrıca, System.Arraydaha hızlı erişim elde etmek için doğrudan kullanabilirsiniz . Sorunuz her öğenin fiziksel olarak bellekte önceden doldurulmasında ısrar ediyorsa, bu yaklaşımı öneririm.

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 };

Bu işlemin bilgisayarımda tamamlanması 596 ms sürüyor , bu da söz konusu koddan yaklaşık % 10.4 daha hızlı (658 ms sürer).

Yöntem 3

Alternatif olarak, seyrek erişime uygun düşük maliyetli başlatma için aşağıdaki tekniği kullanabilirsiniz. Bu, özellikle sadece bazı unsurlara ihtiyaç duyulduğunda ve bunların hepsinin önceden belirlenmesi gereksiz görüldüğünde elverişlidir. Dahası, bu tür teknikler, bellek yetersiz kaldığında daha geniş öğelerle çalışırken uygulanabilir tek seçenek olabilir.

Bu uygulamada, her unsur erişim üzerine tembel olarak belirlenmeye bırakılır. Doğal olarak, bu, erişim sırasında ortaya çıkan ek bir CPU maliyetiyle gelir.

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)
            };
        }
    }
}

Bu işlemin bilgisayarımda tamamlanması 897 ms sürüyor (ayrıca Yöntem 2'dekiArray gibi bir oluşturma ve ekleme ), bu da söz konusu koddan yaklaşık % 36,3 daha yavaş (658 ms sürer).


1
İkinci öneriniz de hafıza tüketimi açısından önemli bir tasarruftur. (Ama listenin değişmemesi gerektiğini varsaydığını belirtmek
isterim

Tek seferde oluşturulan tüm listeye ihtiyacım var - Listedeki bir dizine başvuramıyorum.
benpage

@Taemyr Teşekkürler. Buna göre güncelleme yapacağım. Uygulama gerçekten listenin tamamının önceden doldurulmuş olması konusunda ısrar ediyorsa, bu 3. seçenek açıkça sizin için çalışmayacaktır.
Biscuits

3
@benpage Neden listenin doldurulmasına ihtiyacınız var?
Taemyr

14

Benim makinemde bu, kombinasyonları 222 ms ile 760 ms arasında oluşturur (döngüler için 13):

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;
}

Bu harika bir cevap! Maalesef iç içe döngülerden daha yavaş çalışır. TPL kullanarak düzenleme yapma şansınız var mı?
benpage

maalesef hala biraz daha yavaş.
benpage

1
@benpage Bunu en az 2 kat daha hızlı yapmanın kolay bir yolu var. Sonuç türünü int [,] olarak değiştirmeniz yeterlidir. Bu, tüm dizi belleğini tek bir çağrıda tahsis edecektir. Bunun ihtiyaçlarınıza nasıl uyduğundan emin değilim (iade türünü değiştirmek).
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();

Http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/ adresindeki uzantı yöntemini kullanma

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;
}

1
bu çok daha yavaş çalışır :(
benpage

8

List, değerleri sabit uzunlukta sakladığı dahili bir diziye sahiptir. List.Add'ı aradığınızda yeterli alan olup olmadığını kontrol eder. Yeni elemanı ekleyemediğinde, daha büyük boyutlu yeni bir dizi oluşturacak, önceki tüm elemanları kopyalayacak ve ardından yenisini ekleyecektir. Bu epeyce döngü alır.

Öğelerin sayısını zaten bildiğiniz için, doğru boyutta bir liste oluşturabilirsiniz, bu zaten çok daha hızlı olmalı.

Ayrıca, değerlere nasıl eriştiğinizden emin değilsiniz, ancak bu şeyi bir tane oluşturabilir ve görüntüyü kodda kaydedebilirsiniz (diskten yüklemek muhtemelen şu anda yaptığınızdan daha yavaş olacaktır. Bunu kaç kez okuyup / yazarsınız? şey?


Aslında normal bir diziyi önceden ayırmayı denedim ve ister inanın ister inanmayın, daha yavaştır. Yukarıda söylediğim gibi, bunun anında yaratılması gerekiyor, bir kez hesaplayıp bırakamam.
benpage

Gerçekten mi? vay - optimizasyonlar etkin şekilde çalışıyorsunuz değil mi? (sadece soruyorum)
Carsten

Ah bu başka bir konu, normal dizilerin [x, y] kullanımı güzel, ancak bir dizi dizisi daha hızlı olacaktır. stackoverflow.com/questions/597720/…
IL'de

5

İşte sadece 2 döngüye ihtiyaç duyan farklı bir yol. Buradaki fikir, ilk öğeyi artırmak ve bu sayı artarsa ​​bir sonrakini artırmaktır.

Verileri görüntülemek yerine currentValues.Clone'u kullanabilir ve klonlanan bu sürümü listenize ekleyebilirsiniz. Benim için bu senin versiyonundan daha hızlı koştu.

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());
  • Umarım bu kod çalışır, onu vb'den dönüştürdüm

3

Tüm sayılarınız derleme zamanı sabitidir.

Tüm döngüleri bir listeye açmaya ne dersiniz (programınızı kod yazmak için kullanarak):

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.

Bu, en azından for döngülerinin ek yükünü ortadan kaldırmalıdır (eğer varsa).

C # ile pek aşina değilim, ancak nesneleri serileştirmenin bazı yolları var gibi görünüyor. Ya bu Listeyi yeni oluşturduysanız ve bir biçimde serileştirdiyseniz? Yine de seriyi kaldırma işleminin daha hızlı olup olmadığından emin değilim.


Serileştirme, kutu yaklaşımının dışında gerçekten harika bir düşünce!
Joel B

Maalesef listedeki maksimum değerler dinamiktir, bunu statik olarak yazamıyorum. Yine de iyi fikir!
benpage

2

Sonucun bir dizi dizisi olması gerekiyor mu? Mevcut kurulumla iç dizilerin uzunluğu sabittir ve yapılarla değiştirilebilir. Bu, her şeyin tek bir sürekli bellek bloğu olarak ayrılmasına izin verir ve öğelere daha kolay erişim sağlar (bu şeyi daha sonra nasıl kullandığınızdan emin değilsiniz).

Aşağıdaki yaklaşım çok daha hızlıdır (kutumdaki orijinal için 41 ms'ye karşı 1071 ms):

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;
}

İyi fikir - aslında, gerçek dünya projemde yaptığım şey bu - basitlik nedeniyle orijinal çözüme koymadım. Esas olarak iç içe döngülere daha iyi bir alternatif arıyordum.
benpage

1

Parallel.For()Çalıştırmak için kullanmaya ne dersiniz ? (Struct optimizasyonu @Caramiriel'e övgü ). Değerleri biraz değiştirdim (a, 2 yerine 5), böylece sonuçlardan daha eminim.

    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() şu şekilde tanımlanan özel bir yöntemdir:

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

Sistemimde bu sürüm yaklaşık 6 kat daha hızlı çalışıyor (1,718 saniye karşısında 0,266 saniye).


1
Bu, size yanlış paylaşım vereceği garantilidir ve muhtemelen birçok kez daha yavaş olacaktır.
gjvdkamp

Fena değil - maalesef çalışıyor for döngülerinden daha yavaş . FWIW TÜM Parallel.Fors ile denedim ve VS çöktü!
benpage

@gjvdkamp Cevabımı, yanlış paylaşım sorununu ortadan kaldırdığına inandığım paralel bir sürümle güncelledim.
jdphenix

0

Numaralarınızdan bazıları tamamen bir tam sayı bit sayısına sığar, böylece onları üst seviye numarayla "paketleyebilirsiniz":

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

Elbette bu, kodu daha az okunabilir hale getirir, ancak bir döngü kaydetmiş olursunuz. Bu, sayılardan birinin ikinin üssü olduğu her seferinde yapılabilir, bu sizin durumunuzda yedi kezdir.


Bu cevap hakkında daha fazla bilgi edinmek istiyorum - bu konuyu genişletebilir misiniz?
benpage

Geç cevap için üzgünüm. m, 0'dan 3'e gider, ikili sayılarda 00'dan 11'e, l'den 2'ye, bu da 00'dan 10'a yapar, yani onları ayrı ayrı yazdırırsanız, bu şöyle olur: 00 00 00 01 00 10 00 11 01 00 .. .10 11 Bunları 0000'den 1011'e kadar tek bir 4 bit sayısında bir araya getirebilir ve lm & 3 maskesini kullanarak uygun bitleri seçebilirsiniz ve lm ile (11) arasında b lm & 12, lm ile aynı şeyi yapar. ve (1100) b sonra "gerçek" sayıyı elde etmek için iki bit kaydırırız. Bu arada, bu durumda lm >> 2 yapmanın yeterli olduğunu anladım.
Fabien Dupont

0

İşte başka bir çözüm. VS'nin dışında 437,5 ms kadar hızlı çalışır ve bu da orijinal koddan% 26 daha hızlıdır (bilgisayarımda 593.7):

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();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.