Jak wypełnić / utworzyć tablicę C # za pomocą jednej wartości?

205

Wiem, że instancyjne tablice typów wartości w języku C # są automatycznie zapełniane domyślną wartością typu (np. False dla bool, 0 dla int itp.).

Czy istnieje sposób na automatyczne wypełnienie tablicy wartością początkową, która nie jest domyślna? Zarówno podczas tworzenia, jak i później wbudowanej metody (np. Java Arrays.fill () )? Powiedzmy, że chciałem tablicę boolowską, która domyślnie była prawdziwa, a nie fałszywa. Czy istnieje wbudowany sposób, aby to zrobić, czy wystarczy iterować po tablicy za pomocą pętli for?

 // Example pseudo-code:
 bool[] abValues = new[1000000];
 Array.Populate(abValues, true);

 // Currently how I'm handling this:
 bool[] abValues = new[1000000];
 for (int i = 0; i < 1000000; i++)
 {
     abValues[i] = true;
 }

Konieczność iteracji po tablicy i „resetowania” każdej wartości do wartości true wydaje się nieefektywna. Czy jest coś takiego? Może przerzucając wszystkie wartości?

Po wpisaniu tego pytania i przemyśleniu go, domyślam się, że wartości domyślne są po prostu wynikiem tego, jak C # radzi sobie z alokacją pamięci tych obiektów za kulisami, więc wyobrażam sobie, że prawdopodobnie nie jest to możliwe. Ale nadal chciałbym wiedzieć na pewno!

patjbs
źródło
Zwykle zmieniam nazwę z is_found na is_still_hiding. Uwielbiam odpowiedzi, musiałem zrobić podobnie dla tablicy int w przypadku testowym. (dobre pytanie)
ctrl-alt-delor

Odpowiedzi:

146

Nie znam metody ramowej, ale możesz napisać szybkiego pomocnika, który zrobi to za Ciebie.

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}
JaredPar
źródło
3
Preferuj ++ i zamiast i ++, jeśli nie potrzebujesz kopii.
void.pointer
24
i ++ kopiuje i, inkrementuje i, i zwraca oryginalną wartość. ++ i właśnie zwraca wartość przyrostową. Dlatego ++ i jest szybszy, co może być znaczące w dużych pętlach, jak tutaj mówimy.
tenpn
57
@RobertDailey: Jest to optymalizacja kompilatora i nie jest już prawdziwa. Właśnie przetestowałem, aby zweryfikować swoje przekonanie: Jeśli zwracana wartość i ++ nie jest używana do niczego, to kompilator skompiluje ją automatycznie jako ++ i. Ponadto, nawet gdy używam wartości zwracanej, różnica wydajności jest tak mała, że ​​musiałem zrobić ekstremalny przypadek, aby ją zmierzyć. Nawet wtedy spowodowało to tylko kilka procent różnych czasów działania.
Edward Ned Harvey
8
Napisałem taką metodę rozszerzenia, ale zwróciłem oryginalną tablicę, aby umożliwić tworzenie łańcuchów metod, takich jak:int[] arr = new int[16].Populate(-1);
Gutblender
2
Zmiana voiddo T[], a następnie można zrobićvar a = new int[100].Polupate(1)
Orad
198
Enumerable.Repeat(true, 1000000).ToArray();
Rony
źródło
70
Chociaż to działa, nie jest to naprawdę dobre rozwiązanie, ponieważ jest bardzo wolne; w rzeczywistości jest około 4 razy wolniejszy niż iteracja z pętlą for.
patjbs
4
tak, to prawda, jeśli weźmiemy pod uwagę wydajność, pętla for jest szybsza
Rony
6
Aby zobaczyć prawdziwy test porównawczy, zobacz C # Initialize Array .
theknut
4
Enumerable.ToArraynie zna wielkości sekwencji, którą można wyliczyć, więc musi zgadnąć, jaki jest rozmiar tablicy. Oznacza to, że otrzymasz alokacje tablic za każdym razem ToArray, gdy bufor zostanie przekroczony, oraz jeszcze jedną alokację na końcu dla wykończenia. Istnieje również narzut związany z wymiennym obiektem.
Edward Brey,
5
Tylko uwaga, że ​​w przypadku typów referencji spowoduje to wypełnienie całej tablicy wszystkimi referencjami do tego samego pojedynczego obiektu. Jeśli nie jest to pożądane i faktycznie chcesz wygenerować różne obiekty dla każdego elementu tablicy, zobacz stackoverflow.com/a/44937053/23715 .
Alex Che
74

Utwórz nową tablicę z tysiącem truewartości:

var items = Enumerable.Repeat<bool>(true, 1000).ToArray();  // Or ToList(), etc.

Podobnie możesz generować sekwencje całkowite:

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999
bytebender
źródło
8
Nieźle, ale nadal jest wolniejszy niż pętla
forów
1
patjbs teoretycznie w przyszłości Enumerable.Repeat będzie działał szybciej, ponieważ będzie korzystał z implementacji równoległej.
Petar Pietrow
1
@PetarPetrov To się nigdy nie stanie z powodu przeładowania pamięci podręcznej. Jestem całkiem pewien, że ze względu na charakter pamięci podręcznej procesora wykonywanie pracy równoległej na jednej macierzy zawsze będzie wolniejsze, bez względu na wszystko, ponieważ komputer oczekuje pracy synchronicznej i odpowiednio ładuje dane.
TernaryTopiary
zamierzona pesymizacja! = brak przedwczesnej optymalizacji.
Denis Gladkiy
24

W przypadku dużych tablic lub tablic o zmiennej wielkości powinieneś prawdopodobnie użyć:

Enumerable.Repeat(true, 1000000).ToArray();

W przypadku małej tablicy można użyć składni inicjowania kolekcji w C # 3:

bool[] vals = new bool[]{ false, false, false, false, false, false, false };

Zaletą składni inicjalizacji kolekcji jest to, że nie trzeba używać tej samej wartości w każdym gnieździe i można użyć wyrażeń lub funkcji do zainicjowania grona. Ponadto uważam, że unikasz kosztów inicjowania gniazda tablicy na wartość domyślną. Na przykład:

bool[] vals = new bool[]{ false, true, false, !(a ||b) && c, SomeBoolMethod() };
LBushkin
źródło
I aby zainicjować tablicę float []:float[] AlzCalDefault = new float[] {(float) 0.5, 18, 500, 1, 0};
Jim Lahman,
Inicjalizacji tablicy FWIW można dokonać w dowolnej wersji C #, takiej jak:bool[] vals = { false, true, false, !(a || b) && c, SomeBoolMethod() };
Peter van der Heijden
24

Jeśli twoja tablica jest tak duża, powinieneś użyć BitArray. Używa 1 bitu na każdy bool zamiast bajtu (jak w tablicy boolów), a także możesz ustawić wszystkie bity na prawdziwe za pomocą operatorów bitów. Lub po prostu zainicjuj na true. Jeśli musisz to zrobić tylko raz, będzie to kosztować więcej.

System.Collections.BitArray falses = new System.Collections.BitArray(100000, false);
System.Collections.BitArray trues = new System.Collections.BitArray(100000, true);

// Now both contain only true values.
falses.And(trues);
MrFox
źródło
17

Możesz używać Array.Fillw .NET Core 2.0+ i .NET Standard 2.1+.

juFo
źródło
Doskonały! Należy jednak pamiętać, że jest to stosunkowo nowa metoda. Jest dostępny w .NET Core 2.0+ i .NET Standard 2.1, ale konkretnie nie w żadnej z wersji .NET Framework. (będzie w .NET 5.0, który łączy .NET Framework i .NET Core razem).
Abel
9

niestety nie sądzę, że istnieje bezpośredni sposób, ale myślę, że możesz napisać metodę rozszerzenia dla klasy tablicowej, aby to zrobić

class Program
{
    static void Main(string[] args)
    {
        int[] arr = new int[1000];
        arr.Init(10);
        Array.ForEach(arr, Console.WriteLine);
    }
}

public static class ArrayExtensions
{
    public static void Init<T>(this T[] array, T defaultVaue)
    {
        if (array == null)
            return;
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = defaultVaue;
        }
    }
}
bashmohandes
źródło
Im bardziej podoba mi się ten pomysł rozszerzenia? Czasami proste i proste rozwiązanie jest naprawdę najlepsze!
patjbs
8

Po trochę więcej googlingu i przeczytaniu znalazłem to:

bool[] bPrimes = new bool[1000000];
bPrimes = Array.ConvertAll<bool, bool>(bPrimes, b=> b=true);

Co jest z pewnością bliższe temu, czego szukam. Ale nie jestem pewien, czy jest to lepsze niż iteracja oryginalnej tablicy w pętli for i zmiana wartości. W rzeczywistości po szybkim teście wydaje się wolniejszy około 5 razy, więc nie jest to dobre rozwiązanie!

patjbs
źródło
4
jest to podobne do tego, co próbujesz zrobić, z wyjątkiem wywołania funkcji dla każdego elementu w tablicy. Może wyglądać znacznie ładniej składniowo, ale robi znacznie więcej pracy ...
Nader Shirazie
tak, wygląda na to, że po prostu dla pętli spełnia swoje zadanie równie dobrze, jak wszystko inne
patjbs
Tworzy nową tablicę (nie zmienia oryginalnej instancji).
Jeppe Stig Nielsen
7

Co z implementacją równoległą

public static void InitializeArray<T>(T[] array, T value)
{
    var cores = Environment.ProcessorCount;

    ArraySegment<T>[] segments = new ArraySegment<T>[cores];

    var step = array.Length / cores;
    for (int i = 0; i < cores; i++)
    {
        segments[i] = new ArraySegment<T>(array, i * step, step);
    }
    var remaining = array.Length % cores;
    if (remaining != 0)
    {
        var lastIndex = segments.Length - 1;
        segments[lastIndex] = new ArraySegment<T>(array, lastIndex * step, array.Length - (lastIndex * step));
    }

    var initializers = new Task[cores];
    for (int i = 0; i < cores; i++)
    {
        var index = i;
        var t = new Task(() =>
        {
            var s = segments[index];
            for (int j = 0; j < s.Count; j++)
            {
                array[j + s.Offset] = value;
            }
        });
        initializers[i] = t;
        t.Start();
    }

    Task.WaitAll(initializers);
}

Podczas inicjalizacji tablicy nie widać mocy tego kodu, ale myślę, że zdecydowanie powinieneś zapomnieć o „czystym” dla.

Petar Pietrow
źródło
Grozi to problemem fałszywego udostępniania, w którym różne wątki konkurują o linie pamięci podręcznej procesora, a tym samym zmniejszają wydajność w porównaniu z implementacją jednowątkową. To, czy tak się stanie, zależy od wielkości bloków pamięci na wątek i architektury procesora.
Eric J.
7

Poniższy kod łączy prostą iterację dla małych kopii i Array.Copy dla dużych kopii

    public static void Populate<T>( T[] array, int startIndex, int count, T value ) {
        if ( array == null ) {
            throw new ArgumentNullException( "array" );
        }
        if ( (uint)startIndex >= array.Length ) {
            throw new ArgumentOutOfRangeException( "startIndex", "" );
        }
        if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) {
            throw new ArgumentOutOfRangeException( "count", "" );
        }
        const int Gap = 16;
        int i = startIndex;

        if ( count <= Gap * 2 ) {
            while ( count > 0 ) {
                array[ i ] = value;
                count--;
                i++;
            }
            return;
        }
        int aval = Gap;
        count -= Gap;

        do {
            array[ i ] = value;
            i++;
            --aval;
        } while ( aval > 0 );

        aval = Gap;
        while ( true ) {
            Array.Copy( array, startIndex, array, i, aval );
            i += aval;
            count -= aval;
            aval *= 2;
            if ( count <= aval ) {
                Array.Copy( array, startIndex, array, i, count );
                break;
            }
        }
    }

Testy porównawcze dla różnych długości tablicy przy użyciu tablicy int [] to:

         2 Iterate:     1981 Populate:     2845
         4 Iterate:     2678 Populate:     3915
         8 Iterate:     4026 Populate:     6592
        16 Iterate:     6825 Populate:    10269
        32 Iterate:    16766 Populate:    18786
        64 Iterate:    27120 Populate:    35187
       128 Iterate:    49769 Populate:    53133
       256 Iterate:   100099 Populate:    71709
       512 Iterate:   184722 Populate:   107933
      1024 Iterate:   363727 Populate:   126389
      2048 Iterate:   710963 Populate:   220152
      4096 Iterate:  1419732 Populate:   291860
      8192 Iterate:  2854372 Populate:   685834
     16384 Iterate:  5703108 Populate:  1444185
     32768 Iterate: 11396999 Populate:  3210109

Pierwsze kolumny to rozmiar tablicy, a następnie czas kopiowania przy użyciu prostej iteracji (implementacja @JaredPared). Czas tej metody jest potem. Są to testy porównawcze przy użyciu tablicy o strukturze czterech liczb całkowitych

         2 Iterate:     2473 Populate:     4589
         4 Iterate:     3966 Populate:     6081
         8 Iterate:     7326 Populate:     9050
        16 Iterate:    14606 Populate:    16114
        32 Iterate:    29170 Populate:    31473
        64 Iterate:    57117 Populate:    52079
       128 Iterate:   112927 Populate:    75503
       256 Iterate:   226767 Populate:   133276
       512 Iterate:   447424 Populate:   165912
      1024 Iterate:   890158 Populate:   367087
      2048 Iterate:  1786918 Populate:   492909
      4096 Iterate:  3570919 Populate:  1623861
      8192 Iterate:  7136554 Populate:  2857678
     16384 Iterate: 14258354 Populate:  6437759
     32768 Iterate: 28351852 Populate: 12843259
Panos Theof
źródło
7

Lub ... możesz po prostu użyć odwróconej logiki. Niech falseoznacza truei na odwrót.

Próbka kodu

// bool[] isVisible = Enumerable.Repeat(true, 1000000).ToArray();
bool[] isHidden = new bool[1000000]; // Crazy-fast initialization!

// if (isVisible.All(v => v))
if (isHidden.All(v => !v))
{
    // Do stuff!
}
l33t
źródło
zabawne rozwiązanie, choć byłoby to o wiele trudniejsze, na przykład ints, ponieważ tracisz 0.
MrFox
1
jest to właściwie wykonalna opcja, jeśli „odwrócisz logikę” w nazwie zmiennej: zamiast bool[] isVisibletegobool[] isHidden
Markus Hütter,
1
Wydaje się, że ludzie reagują w ten sposób na zabawny hack. To popularna technika optymalizacji. Jeśli masz szczęście, kompilator zrobi to za Ciebie.
l33t
4

to również działa ... ale może być niepotrzebne

 bool[] abValues = new bool[1000];
 abValues = abValues.Select( n => n = true ).ToArray<bool>();
Stan R.
źródło
4

Wiele przedstawionych tutaj odpowiedzi sprowadza się do pętli, która inicjuje tablicę jeden element na raz, co nie wykorzystuje instrukcji procesora zaprojektowanych do działania na bloku pamięci jednocześnie.

.Net Standard 2.1 (w wersji zapoznawczej w tym tekście ) udostępnia Array.Fill () , który nadaje się do wysokowydajnej implementacji w bibliotece wykonawczej (choć na razie .NET Core nie wydaje się korzystać z tej możliwości) .

W przypadku wcześniejszych platform następująca metoda rozszerzenia przewyższa trywialną pętlę o znaczny margines, gdy rozmiar tablicy jest znaczący. Stworzyłem go, gdy moje rozwiązanie problemu z kodem online było o około 20% wyższe niż przydzielony budżet czasowy. Skróciło czas działania o około 70%. W tym przypadku wypełnienie tablicy zostało wykonane w innej pętli. BLOCK_SIZE był nastawiony raczej na przeczucie niż na eksperyment. Możliwe są pewne optymalizacje (np. Kopiowanie wszystkich bajtów już ustawionych na pożądaną wartość zamiast bloku o stałym rozmiarze).

internal const int BLOCK_SIZE = 256;
public static void Fill<T>(this T[] array, T value)
{
    if (array.Length < 2 * BLOCK_SIZE)
    {
        for (int i = 0; i < array.Length; i++) array[i] = value;
    }
    else
    {
        int fullBlocks = array.Length / BLOCK_SIZE;
        // Initialize first block
        for (int j = 0; j < BLOCK_SIZE; j++) array[j] = value;
        // Copy successive full blocks
        for (int blk = 1; blk < fullBlocks; blk++)
        {
            Array.Copy(array, 0, array, blk * BLOCK_SIZE, BLOCK_SIZE);
        }

        for (int rem = fullBlocks * BLOCK_SIZE; rem < array.Length; rem++)
        {
            array[rem] = value;
        }
    }
}
Eric J.
źródło
3

Jeśli planujesz ustawić tylko kilka wartości w tablicy, ale chcesz uzyskać (niestandardową) wartość domyślną przez większość czasu, możesz spróbować czegoś takiego:

public class SparseArray<T>
{
    private Dictionary<int, T> values = new Dictionary<int, T>();

    private T defaultValue;

    public SparseArray(T defaultValue)
    {
        this.defaultValue = defaultValue;
    }

    public T this [int index]
    {
      set { values[index] = value; }
      get { return values.ContainsKey(index) ? values[index] ? defaultValue; }
    }
}

Prawdopodobnie będziesz musiał zaimplementować inne interfejsy, aby były użyteczne, takie jak te w samej tablicy .

Douglas
źródło
3

Nie ma możliwości ustawienia wszystkich elementów w tablicy jako pojedynczej operacji, PONIEWAŻ, ta wartość jest wartością domyślną typów elementów.

Na przykład, jeśli jest to tablica liczb całkowitych, możesz ustawić je wszystkie na zero za pomocą jednej operacji, na przykład: Array.Clear(...)

James
źródło
2

Zdaję sobie sprawę, że spóźniłem się na przyjęcie, ale oto pomysł. Napisz opakowanie, które ma operatory konwersji do i od wartości opakowanej, aby można było jej użyć jako stand-in dla typu opakowanego. Zostało to zainspirowane głupią odpowiedzią @ l33t.

Po pierwsze (pochodzące z C ++) zdałem sobie sprawę, że w języku C # domyślny ctor nie jest wywoływany, gdy budowane są elementy tablicy. Zamiast tego - nawet w obecności domyślnego konstruktora zdefiniowanego przez użytkownika! - wszystkie elementy tablicy są inicjowane zerem. To mnie zaskoczyło.

Tak więc klasa opakowania, która po prostu zapewnia domyślny ctor z pożądaną wartością, działałaby dla tablic w C ++, ale nie w C #. Obejściem tego problemu jest umożliwienie odwzorowania typu opakowania 0 na pożądaną wartość początkową po konwersji. W ten sposób wszystkie zainicjowane wartości zerowe wydają się być inicjowane z ziarnem dla wszystkich praktycznych celów:

public struct MyBool
{
    private bool _invertedValue;

    public MyBool(bool b) 
    {   
        _invertedValue = !b;
    }

    public static implicit operator MyBool(bool b)
    {
        return new MyBool(b);
    }

    public static implicit operator bool(MyBool mb)
    {
        return !mb._invertedValue;
    }

}

static void Main(string[] args)
{
        MyBool mb = false; // should expose false.
        Console.Out.WriteLine("false init gives false: " 
                              + !mb);

        MyBool[] fakeBoolArray = new MyBool[100];

        Console.Out.WriteLine("Default array elems are true: " 
                              + fakeBoolArray.All(b => b) );

        fakeBoolArray[21] = false;
        Console.Out.WriteLine("Assigning false worked: " 
                              + !fakeBoolArray[21]);

        fakeBoolArray[21] = true;
        // Should define ToString() on a MyBool,
        // hence the !! to force bool
        Console.Out.WriteLine("Assigning true again worked: " 
                              + !!fakeBoolArray[21]);
}

Ten wzór dotyczy wszystkich typów wartości. Można na przykład odwzorować 0 do 4 dla ints, jeśli pożądana jest inicjalizacja z 4 itd.

Chciałbym zrobić szablon tego, co byłoby możliwe w C ++, podając wartość początkową jako parametr szablonu, ale rozumiem, że nie jest to możliwe w języku C #. A może coś mi brakuje? (Oczywiście w mapowaniu w C ++ wcale nie jest konieczne, ponieważ można podać domyślny ctor, który będzie wywoływany dla elementów tablicy).

FWIW, oto odpowiednik C ++: https://ideone.com/wG8yEh .

Peter - przywróć Monikę
źródło
2

Jeśli możesz odwrócić logikę, możesz użyć Array.Clear()metody, aby ustawić tablicę boolowską na false.

        int upperLimit = 21;
        double optimizeMe = Math.Sqrt(upperLimit);

        bool[] seiveContainer = new bool[upperLimit];
        Array.Clear(seiveContainer, 0, upperLimit);
superstewie
źródło
2

Jeśli korzystasz z .NET Core, .NET Standard> = 2.1 lub zależysz od pakietu System.Memory, możesz również użyć Span<T>.Fill()metody:

var valueToFill = 165;
var data = new int[100];

data.AsSpan().Fill(valueToFill);

// print array content
for (int i = 0; i < data.Length; i++)
{
    Console.WriteLine(data[i]);
}

https://dotnetfiddle.net/UsJ9bu

Apollo3zehn
źródło
2

Oto kolejna wersja dla nas Użytkownicy platformy porzuceni przez Microsoft. Jest 4 razy Array.Clearszybszy i szybszy niż rozwiązanie Panos Theof i równoległe rozwiązanie Erica J i Petara Pietrowa - do dwóch razy szybsze w przypadku dużych tablic.

Najpierw chcę przedstawić przodka funkcji, ponieważ ułatwia to zrozumienie kodu. Pod względem wydajności jest to prawie na równi z kodem Panos Theof, a dla niektórych rzeczy, które mogą już wystarczyć:

public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
    if (threshold <= 0)
        throw new ArgumentException("threshold");

    int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);

    while (current_size < keep_looping_up_to)
        array[current_size++] = value;

    for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
        Array.Copy(array, 0, array, current_size, current_size);

    Array.Copy(array, 0, array, current_size, count - current_size);
}

Jak widać, jest to oparte na wielokrotnym podwajaniu już zainicjowanej części. Jest to proste i wydajne, ale działa w porównaniu z nowoczesnymi architekturami pamięci. Stąd narodziła się wersja, która wykorzystuje podwajanie tylko do utworzenia przyjaznego dla bufora bloku początkowego, który jest następnie wysadzany iteracyjnie nad obszarem docelowym:

const int ARRAY_COPY_THRESHOLD = 32;  // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;

public static void Fill<T> (T[] array, int count, T value, int element_size)
{
    int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);

    while (current_size < keep_looping_up_to)
        array[current_size++] = value;

    int block_size = L1_CACHE_SIZE / element_size / 2;
    int keep_doubling_up_to = Math.Min(block_size, count >> 1);

    for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
        Array.Copy(array, 0, array, current_size, current_size);

    for (int enough = count - block_size; current_size < enough; current_size += block_size)
        Array.Copy(array, 0, array, current_size, block_size);

    Array.Copy(array, 0, array, current_size, count - current_size);
}

Uwaga: wcześniejszy kod potrzebny (count + 1) >> 1jako limit dla pętli dublującej, aby upewnić się, że w końcowej operacji kopiowania jest wystarczająca ilość paszy, aby pokryć wszystko, co pozostało. Nie byłoby tak w przypadku nieparzystych zliczeń, gdyby count >> 1zamiast nich zastosowano. W obecnej wersji nie ma to znaczenia, ponieważ pętla kopiowania liniowego wykryje luz.

Rozmiar komórki tablicowej musi zostać przekazany jako parametr, ponieważ - umysł zadziwia - nie można używać rodzajów ogólnych, sizeofchyba że zastosują ograniczenie ( unmanaged), które może, ale nie musi, stać się dostępne w przyszłości. Błędne szacunki nie są wielkim problemem, ale wydajność jest najlepsza, jeśli wartość jest dokładna, z następujących powodów:

  • Niedocenianie rozmiaru elementu może prowadzić do rozmiarów bloków większych niż połowa pamięci podręcznej L1, a tym samym zwiększa prawdopodobieństwo eksmisji źródłowych danych z L1 i konieczności ponownego pobrania z wolniejszych poziomów pamięci podręcznej.

  • Przeszacowanie rozmiaru elementu powoduje niepełne wykorzystanie pamięci podręcznej L1 procesora, co oznacza, że ​​pętla kopiowania bloku liniowego jest wykonywana częściej niż przy optymalnym wykorzystaniu. W ten sposób powstaje więcej narzutu związanego z ustaloną pętlą / wywołaniem niż jest to absolutnie konieczne.

Oto test porównawczy mojego kodu Array.Cleari pozostałe trzy wspomniane wcześniej rozwiązania. Czasy dotyczą wypełniania tablic liczb całkowitych ( Int32[]) o podanych rozmiarach. W celu zmniejszenia zmienności spowodowanej błędami pamięci podręcznej itp. Każdy test był wykonywany dwukrotnie, z powrotem do tyłu, a czasy były brane pod uwagę przy drugim wykonaniu.

array size   Array.Clear      Eric J.   Panos Theof  Petar Petrov   Darth Gizka
-------------------------------------------------------------------------------
     1000:       0,7 µs        0,2 µs        0,2 µs        6,8 µs       0,2 µs 
    10000:       8,0 µs        1,4 µs        1,2 µs        7,8 µs       0,9 µs 
   100000:      72,4 µs       12,4 µs        8,2 µs       33,6 µs       7,5 µs 
  1000000:     652,9 µs      135,8 µs      101,6 µs      197,7 µs      71,6 µs 
 10000000:    7182,6 µs     4174,9 µs     5193,3 µs     3691,5 µs    1658,1 µs 
100000000:   67142,3 µs    44853,3 µs    51372,5 µs    35195,5 µs   16585,1 µs 

Jeśli wydajność tego kodu nie będzie wystarczająca, obiecującą ścieżką będzie równoległa pętla kopiowania liniowego (wszystkie wątki będą korzystały z tego samego bloku źródłowego) lub nasz stary dobry przyjaciel P / Invoke.

Uwaga: czyszczenie i wypełnianie bloków jest zwykle wykonywane przez procedury wykonawcze, które rozgałęziają się do wysoce wyspecjalizowanego kodu przy użyciu instrukcji MMX / SSE i tak dalej, więc w każdym przyzwoitym środowisku można po prostu nazwać odpowiedni moralny ekwiwalent std::memseti być pewnym poziomu profesjonalnej wydajności. IOW, zgodnie z prawem funkcja biblioteki Array.Clearpowinna pozostawić wszystkie nasze ręcznie zwijane wersje w pył. Fakt, że jest na odwrót, pokazuje, jak dalece rzeczy są naprawdę zepsute. To samo dotyczy konieczności rzucania własnym Fill<>, ponieważ wciąż jest tylko w Core i Standard, ale nie w Framework. .NET istnieje już od prawie dwudziestu lat i nadal musimy P / Invoke w lewo i prawo, aby uzyskać najbardziej podstawowe rzeczy lub rzucić własne ...

DarthGizka
źródło
0

Oto kolejna ocena, z System.Collections.BitArrayktórą ma takiego konstruktora.

bool[] result = new BitArray(1000000, true).Cast<bool>().ToArray();

lub

bool[] result = new bool[1000000];
new BitArray(1000000, true).CopyTo(result, 0);
fubo
źródło
0

Stwórz prywatną klasę w środku, w której tworzysz tablicę, a do tego masz getter i setter. Jeśli nie potrzebujesz, aby każda pozycja w tablicy była czymś wyjątkowym, na przykład losowym, użyj int? jako tablicę, a następnie pobierz, jeśli pozycja jest równa null, wypełnij tę pozycję i zwróć nową losową wartość.

IsVisibleHandler
{

  private bool[] b = new bool[10000];

  public bool GetIsVisible(int x)
  {
  return !b[x]
  }

  public void SetIsVisibleTrueAt(int x)
  {
  b[x] = false //!true
  }
}

Albo użyj

public void SetIsVisibleAt(int x, bool isTrue)
{
b[x] = !isTrue;
}

Jako seter.

Peter J.
źródło
-2
Boolean[] data = new Boolean[25];

new Action<Boolean[]>((p) => { BitArray seed = new BitArray(p.Length, true); seed.CopyTo(p, 0); }).Invoke(data);
ldsmithperrin
źródło
Użyj lepszego formatowania i być może kilku słów wyjaśniających, aby inni mogli lepiej zrozumieć twoje rozwiązanie.
Gorgsenegger
1
Można to wykorzystać do zwiększenia wydajności inicjowania poprzez partycjonowanie tablicy docelowej i kopiowanie materiału siewnego na różne partycje. Miało to jedynie dać pomysł - to mój pierwszy i ostatni w historii post.
ldsmithperrin