Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

На миг стремительней: измеряем время упаковки и распаковки важных типов данных

Anna | 17.06.2014 | нет комментариев
Многие неопытные разработчики не неизменно знают и понимают, что же происходит за кулисами их кода. Теперь речь пойдет об упаковке и распаковке важных типов данных (по-русски это звучит страшно, следственно «boxing and unboxing value types»).
Под катом маленький пример и измерение времени выполнения.

Что такое упаковка (boxing)?
Коротко. Есть важнейшие типы данных (value types) и ссылочные (reference types). Переменные важных типов данных хранят само значение (спасибо, кэп!), переменные ссылочных типов данных — ссылку на участок в памяти, где хранится это значение.

int valType = 15;

Это важный тип данных. Значение переменной valType будет храниться в стэке. Многие типовые типы данных — важные (int, byte, long, bool и т.д.).
Дальше если мы испробуем сделать вот так:

int valType = 15;
Object refType = valType;

И получим в итоге переменную ссылочного типа (refType). Здесь произойдет следующее: вначале в стэке появится значение переменной valType (важный тип), потом в памяти будет сделан контейнер для хранения значения этой переменной (в нашем случае контейнер для переменной типа int, то есть 4 байта под значение sync block index (еще 4 байта)), а вот теснее указатель на данный контейнер и будет храниться в нашей переменной ссылочного типа (refType). Данный процесс именуется упаковка (boxing).
Подробности дозволено посмотреть здесь, а отменнее прочитать в книжке Дж.Рихтера «CLR via C#» (Глава 5).

Самое неприятное в этих операциях то, что они происходят неявно.
Скажем, мы хотим вывести число в консоль. Так:

Console.WriteLine(20);

Либо вот так:

Console.WriteLine("{0}", 20);

В чем разница? Давайте посмотрим на итог компиляции в MSIL (сделать это дозволено утилитой ILdasm.exe):

// Это для вызова Console.WriteLine(20);
  IL_0000:  ldc.i4.s   20
  IL_0002:  call       void [mscorlib]System.Console::WriteLine(int32)

// А это для вызова Console.WriteLine("{0}", 20);
  IL_0007:  ldstr      "{0}"
  IL_000c:  ldc.i4.s   20
  IL_000e:  box        [mscorlib]System.Int32
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)

Во втором случае мы видим команду box, которая и исполняет упаковку.
Дабы осознать откуда она взялась, взглянем на сигнатуру способа Console.WriteLine и подметим, что их есть даже 18 штук.
В первом вызове применяется такая сигнатура:

void WriteLine(int value);

Здесь все легко — данный способ принимает важный тип данных int, передаем мы значение типа int, происходит передача параметра по значению. Упаковка здесь не необходима.
Во втором случае применяется иная сигнатура:

void WriteLine(string format, object arg0);

С передачей форматной строки ясно: требуется строка — мы передаем строку. А с доводом arg0 чуть труднее: способ умоляет от нас объект ссылочного типа данных object, а передаем мы в способ значение типа int. Вот здесь и необходима упаковка. В итоге чего в памяти создается контейнер для типа int, в него копируется значение 20, и указатель на данный контейнер попадает в довод arg0.

Сейчас испробуем посчитать, крепко ли замедляют данные операции наш код.
Для этого я написал маленький ломтик кода:

Простынка кода

static void Main()
{
    // переменная важного типа, неупакованная
    var val = 15;

    // переменная ссылочного типа, теснее упакованная
    Object obj = val;

    // число циклов - мильён =)
    const int cycles = 1000000;
    var str = "";

    // временные итоги будем заносить в список
    var results = new List<TimeSpan>();

    // повторяем навык 20 раз, Дабы получить больше подлинное среднее значение
    for (var j = 0; j < 20; j  )
    {
        // засекаем время
        var start = DateTime.Now;
        for (var i = 0; i < cycles; i  )
        {
            // создаем строку из 10 идентичных чисел
            // здесь передаются теснее упакованные значения
            str = String.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}", 
                                obj, obj, obj, obj, obj, obj, obj, obj, obj, obj);
        }
        // останавливаем таймер
        var end = DateTime.Now;
        // и получаем время выполнения цикла без операций упаковки (box)
        var objResult = end.Subtract(start);

        // вновь засекаем время
        start = DateTime.Now;
        for (var i = 0; i < cycles; i  )
        {
            // делаем верно такую же строку
            // но за данный вызов происходит 10 операций упаковки (box)
            str = String.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}", 
                                val, val, val, val, val, val, val, val, val, val);
        }
        // останавливаем таймер
        end = DateTime.Now;
        // и получаем время выполнения цикла, в котором было 10 миллионов операций упаковки
        var valResult = end.Subtract(start);

        // разницу во времени между двумя циклами старательно заносим в список       
        results.Add(valResult.Subtract(objResult));
    }

    // выводим список итогов
    foreach (var timeDif in results)
    {
        Console.WriteLine(timeDif);
    }

    // и считаем среднюю разницу
    Console.WriteLine();
    Console.Write("Milliseconds need for 10KK boxing operations: ");
    Console.WriteLine(results.Aggregate(TimeSpan.Zero, (sum, current) => sum.Add(current)).TotalMilliseconds / results.Count);
}

Итог выполнения я получил дальнейший (процессор Intel Core i5 750 2.67GHz, 4 ядра, выполнялось на одном):

00:00:00.0600060
00:00:00.0770077
00:00:00.0570057
00:00:00.0710071
00:00:00.0680068
00:00:00.0650065
00:00:00.0530053
00:00:00.0740074
00:00:00.0570057
00:00:00.0580058
00:00:00.0590059
00:00:00.0500050
00:00:00.0550055
00:00:00.0720072
00:00:00.0800080
00:00:00.0640064
00:00:00.0640064
00:00:00.0670067
00:00:00.0660066
00:00:00.0590059

Milliseconds need for 10KK boxing operations: 63,80638

Итого в среднем примерно 64мс на 10млн. операций упаковки.

Итог

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

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB