Почему массивы UInt16, кажется, добавляют быстрее, чем международные массивы?

Кажется, что C# быстрее при добавлении двух массивов UInt16[] чем он при добавлении двух массивов int[]. Это не имеет никакого смысла мне, так как я предположил бы, что массивы будут выровнены словом, и таким образом int[] потребовал бы меньшего количества работы от ЦП, нет?

Я выполнил тестовый код ниже и получил следующие результаты:

Int    for 1000 took 9896625613 tick (4227 msec)
UInt16 for 1000 took 6297688551 tick (2689 msec)

Тестовый код делает следующее:

  1. Создает два названные массива a и b, однажды.
  2. Заполняет их случайными данными, однажды.
  3. Запускает секундомер.
  4. Добавляет a и b, пообъектно. Это сделано 1000 раз.
  5. Останавливает секундомер.
  6. Отчеты, сколько времени это взяло.

Это сделано для int[] a, b и для UInt16 a,b. И каждый раз я выполняю код, тесты для UInt16 массивы занимают на 30%-50% меньше времени, чем int массивы. Можно ли объяснить это мне?

Вот код, если Вы хотите попробовать если за себя:

public static UInt16[] GenerateRandomDataUInt16(int length)
{
    UInt16[] noise = new UInt16[length];
    Random random = new Random((int)DateTime.Now.Ticks);
    for (int i = 0; i < length; ++i)
    {
        noise[i] = (UInt16)random.Next();
    }

    return noise;
}

public static int[] GenerateRandomDataInt(int length)
{
    int[] noise = new int[length];
    Random random = new Random((int)DateTime.Now.Ticks);
    for (int i = 0; i < length; ++i)
    {
        noise[i] = (int)random.Next();
    }

    return noise;
}

public static int[] AddInt(int[] a, int[] b)
{
    int len = a.Length;
    int[] result = new int[len];
    for (int i = 0; i < len; ++i)
    {
        result[i] = (int)(a[i] + b[i]);
    }
    return result;
}

public static UInt16[] AddUInt16(UInt16[] a, UInt16[] b)
{
    int len = a.Length;
    UInt16[] result = new UInt16[len];
    for (int i = 0; i < len; ++i)
    {
        result[i] = (ushort)(a[i] + b[i]);
    }
    return result;
}


public static void Main()
{
    int count = 1000;
    int len = 128 * 6000;

    int[] aInt = GenerateRandomDataInt(len);
    int[] bInt = GenerateRandomDataInt(len);

    Stopwatch s = new Stopwatch();
    s.Start();
    for (int i=0; i<count; ++i) 
    {
        int[] resultInt = AddInt(aInt, bInt);
    }
    s.Stop();
    Console.WriteLine("Int    for " + count 
                + " took " + s.ElapsedTicks + " tick (" 
                + s.ElapsedMilliseconds + " msec)");

    UInt16[] aUInt16 = GenerateRandomDataUInt16(len);
    UInt16[] bUInt16 = GenerateRandomDataUInt16(len);

    s = new Stopwatch();
    s.Start();
    for (int i=0; i<count; ++i) 
    {
        UInt16[] resultUInt16 = AddUInt16(aUInt16, bUInt16);
    }
    s.Stop();
    Console.WriteLine("UInt16 for " + count 
                + " took " + s.ElapsedTicks + " tick (" 
                + s.ElapsedMilliseconds + " msec)");


}
8
задан Grzegorz Gierlik 13 April 2010 в 07:58
поделиться

5 ответов

Вы видите дырявую абстракцию. UInt16 занимает половину памяти, чем int (16 против 32 бит).

Это означает, что область памяти, занятая массивом int16, занимает половину площади, чем int32. Таким образом, большая часть этой области может уместиться в кеш-памяти процессора и, таким образом, доступна очень быстро.

Вы можете попробовать этот код на процессоре, у которого больше кэш-памяти, и разница, вероятно, будет меньше.

Также попробуйте с гораздо большими массивами.

6
ответ дан 5 December 2019 в 15:21
поделиться

Массивы выровнены по словам, но нет причин, по которым записи в массиве должны быть выровнены по словам.

2
ответ дан 5 December 2019 в 15:21
поделиться

Просто SWAG: меньшее использование памяти массивами UInt16 улучшило характеристики памяти (GC, кеш, кто знает что еще). Поскольку кажется, что выделений не так много, я предполагаю, что кеш является основным фактором.

Кроме того, вы должны позаботиться о том, чтобы тестирование производительности могло оказаться непростым делом - похоже, ваше время, вероятно, включает некоторую JIT-компиляцию, которая может искажать результаты. Вы можете попробовать изменить порядок, в котором вы тестируете массив int с массивом UInt16 , и посмотреть, совпадают ли тайминги или нет.

У Джона Скита была (или была) простая структура тестирования производительности, которую он закодировал еще тогда, когда пытался учесть эти эффекты. Я не знаю, доступен ли он (или вообще применим); может он прокомментирует.

1
ответ дан 5 December 2019 в 15:21
поделиться

Я не эксперт в .NET, но я бы проверил две вещи:

  1. Передача большего массива (N элементов типа int) занимает больше времени, чем массив из N ushort элементов. Это можно проверить, используя различные размеры массивов и стиль кодирования - см. мой комментарий к вопросу). Числа из ваших тестов соответствуют этой теории :).
  2. Сложение двух ushort переменных может быть реализовано как сложение двух int с результатом типа int -- без проверки переполнения. И я предполагаю, что обработка в коде любого вида исключения (включая исключение переполнения) является трудоемкой задачей. Это можно проверить в документации .NET.
1
ответ дан 5 December 2019 в 15:21
поделиться

Пара факторов

1) Вы также синхронизация генерации результирующего массива ... поэтому было бы интересно посмотреть, сколько времени потребовалось только на добавление, а не на создание результирующего массива, который передается обратно

2) Было бы интересно посмотреть, какой IL генерируется . Поскольку ваш код ОЧЕНЬ прост (итерация и добавление), компилятор может оптимизировать его, возможно, вставляя несколько uint16 в больший регистр и выполняя несколько добавлений для каждой инструкции

1
ответ дан 5 December 2019 в 15:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: