Подтвердить что ты не робот

Микропроцессорность DateTime.DayOfWeek

Прежде всего:

  • Я задаю этот вопрос просто для удовольствия и желаю учиться. Я должен признать, что я люблю возиться с микро-оптимизацией (хотя они никогда не приводили к значительному увеличению скорости в любом из моих событий).

  • Метод DateTime.DayOfWeek не является узким местом в любом моем приложении.

  • И вряд ли это будет проблемой в любом другом. Если кто-то думает, что этот метод влияет на производительность его приложения, он должен подумать о Когда оптимизировать, а затем, он должен выполнить профилирование.

Декомпиляция класса DateTime с помощью ILSpy, мы узнаем, как реализуется DateTime.DayOfWeek:

[__DynamicallyInvokable]
        public DayOfWeek DayOfWeek
        {
            [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
            get
            {
                return (DayOfWeek)((this.InternalTicks / 864000000000L + 1L) % 7L);
            }
        }


public long Ticks
{
    [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    get
    {
        return this.InternalTicks;
    }
}

Этот метод выполняет следующие действия:

  • Тики, соответствующие текущему дню, делятся на существующее количество тиков в день.

  • Добавим 1 к предыдущему результату, чтобы остаток деления 7 находился между числами 0 и 6.

Это единственный способ рассчитать день недели?

Можно ли переопределить это для ускорения работы?

4b9b3361

Ответ 1

Сделайте некоторые настройки.

DayOfWeek теперь можно выразить как:

public DayOfWeek DayOfWeek
{                   
    get
    {
        return (DayOfWeek)(((Ticks>>14) / 52734375 + 1L) % 7L);
    }
}

И мы работаем по модулю 7, 52734375 % 7 it 1. Итак, приведенный выше код равен:

public static DayOfWeek dayOfWeekTurbo(this DateTime date)
{
    return (DayOfWeek)(((date.Ticks >> 14) + 1) % 7);
}

Интуитивно, он работает. Но пусть это докажет с помощью кода

public static void proof()
{
    DateTime date = DateTime.MinValue;
    DateTime max_date = DateTime.MaxValue.AddDays(-1);
    while (date < max_date)
    {
        if (date.DayOfWeek != date.dayOfWeekTurbo())
        {
            Console.WriteLine("{0}\t{1}", date.DayOfWeek, date.dayOfWeekTurbo());
            Console.ReadLine();
        }
        date = date.AddDays(1);
    }
}

Вы можете запустить его, если хотите, но я заверяю вас, что он работает нормально.

Хорошо, осталось только немного бенчмаркинга.

Это вспомогательный метод, чтобы сделать код более понятным:

public static IEnumerable<DateTime> getAllDates()
{
    DateTime d = DateTime.MinValue;
    DateTime max = DateTime.MaxValue.AddDays(-1);
    while (d < max)
    {
        yield return d;
        d = d.AddDays(1);
    }
}

Я думаю, это не нуждается в объяснении.

public static void benchDayOfWeek()
{

    DateTime[] dates = getAllDates().ToArray();
    // for preventing the compiler doing things that we don't want to
    DayOfWeek[] foo = new DayOfWeek[dates.Length];
    for (int max_loop = 0; max_loop < 10000; max_loop+=100)
    {


        Stopwatch st1, st2;
        st1 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].DayOfWeek;
        st1.Stop();

        st2 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].dayOfWeekTurbo();
        st2.Stop();

        Console.WriteLine("{0},{1}", st1.ElapsedTicks, st2.ElapsedTicks);

    }
    Console.ReadLine();
    Console.WriteLine(foo[0]);

}

Вывод:

96,28
172923452,50884515
352004290,111919170
521851120,168153321
683972846,215554958
846791857,264187194
1042803747,328459950
Monday

Если мы создадим диаграмму с данными, она выглядит так:

Chart

╔══════════════════════╦════════════════════╦═════════════════════╦═════════════╗
║ Number of iterations ║ Standard DayOfWeek ║ Optimized DayOfWeek ║   Speedup   ║
╠══════════════════════╬════════════════════╬═════════════════════╬═════════════╣
║                    0 ║                 96 ║                  28 ║ 3.428571429 ║
║                  100 ║          172923452 ║            50884515 ║ 3.398351188 ║
║                  200 ║          352004290 ║           111919170 ║ 3.145165301 ║
║                  300 ║          521851120 ║           168153321 ║ 3.103424404 ║
║                  400 ║          683972846 ║           215554958 ║ 3.1730787   ║
║                  500 ║          846791857 ║           264187194 ║ 3.205272156 ║
║                  600 ║         1042803747 ║           328459950 ║ 3.174827698 ║
╚══════════════════════╩════════════════════╩═════════════════════╩═════════════╝

3 раза быстрее.

Примечание: код был скомпилирован в Visual Studio 2013, режиме выпуска и запускался со всем закрытым, но с приложением. (В том числе VS, конечно).

Я провел тесты в toshiba Satellite C660-2JK, Процессор Intel® Core ™ i3-2350M и 64-разрядная версия Windows® 7 Home Premium.

EDIT:

Как Jon Skeet заметил, что этот метод может потерпеть неудачу, если он не находится на границе даты.

Благодаря Jon Skeet прокомментируйте этот ответ,

dayOfWeekTurbo может выйти из строя, если он не находится на границе даты. Например, рассмотрите new DateTime(2014, 3, 11, 21, 39, 30) - ваш метод считает это пятница, когда на самом деле это вторник. "Мы работаем по модулю 7" - это неправильный путь, в основном... путем удаления этого дополнительного разделение, изменения дня недели в течение дня.

Я решил отредактировать его.

Если мы изменим метод proof(),

public static void proof()
{
    DateTime date = DateTime.MinValue;
    DateTime max_date = DateTime.MaxValue.AddSeconds(-1);
    while (date < max_date)
    {
        if (date.DayOfWeek != date.dayOfWeekTurbo2())
        {
            Console.WriteLine("{0}\t{1}", date.DayOfWeek, date.dayOfWeekTurbo2());
            Console.ReadLine();
        }
        date = date.AddSeconds(1);
    }
}

Не удается!

Джон Скит был прав. Позвольте следовать совету Джона Скита и применить разделение.
public static DayOfWeek dayOfWeekTurbo2(this DateTime date)
{
    return (DayOfWeek)((((date.Ticks >> 14) / 52734375L )+ 1) % 7);
}

Также мы меняем метод getAllDates().

public static IEnumerable<DateTime> getAllDates()
{
    DateTime d = DateTime.MinValue;
    DateTime max = DateTime.MaxValue.AddHours(-1);
    while (d < max)
    {
        yield return d;
        d = d.AddHours(1);
    }
}

И benchDayOfWeek()

public static void benchDayOfWeek()
{

    DateTime[] dates = getAllDates().ToArray();
    DayOfWeek[] foo = new DayOfWeek[dates.Length];
    for (int max_loop = 0; max_loop < 10000; max_loop ++)
    {


        Stopwatch st1, st2;
        st1 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].DayOfWeek;
        st1.Stop();

        st2 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].dayOfWeekTurbo2();
        st2.Stop();

        Console.WriteLine("{0},{1}", st1.ElapsedTicks, st2.ElapsedTicks);

    }
    Console.ReadLine();
    Console.WriteLine(foo[0]);

}

Он все равно будет быстрее? ответ да

Вывод:

90,26
43772675,17902739
84299562,37339935
119418847,47236771
166955278,72444714
207441663,89852249
223981096,106062643
275440586,125110111
327353547,145689642
363908633,163442675
407152133,181642026
445141584,197571786
495590201,217373350
520907684,236609850
511052601,217571474
610024381,260208969
637676317,275558318

Chart

╔══════════════════════╦════════════════════╦════════════════════════╦═════════════╗
║ Number of iterations ║ Standard DayOfWeek ║ Optimized DayOfWeek(2) ║  Speedup    ║
╠══════════════════════╬════════════════════╬════════════════════════╬═════════════╣
║                    1 ║           43772675 ║               17902739 ║ 2.445026708 ║
║                    2 ║           84299562 ║               37339935 ║ 2.257624766 ║
║                    3 ║          119418847 ║               47236771 ║ 2.528090817 ║
║                    4 ║          166955278 ║               72444714 ║ 2.304588821 ║
║                    5 ║          207441663 ║               89852249 ║ 2.308697504 ║
║                    6 ║          223981096 ║              106062643 ║ 2.111781205 ║
║                    7 ║          275440586 ║              125110111 ║ 2.201585338 ║
║                    8 ║          327353547 ║              145689642 ║ 2.246923958 ║
║                    9 ║          363908633 ║              163442675 ║ 2.226521519 ║
║                   10 ║          407152133 ║              181642026 ║ 2.241508433 ║
║                   11 ║          445141584 ║              197571786 ║ 2.25306251  ║
║                   12 ║          495590201 ║              217373350 ║ 2.279903222 ║
║                   13 ║          520907684 ║              236609850 ║ 2.201546909 ║
║                   14 ║          511052601 ║              217571474 ║ 2.348895246 ║
║                   15 ║          610024381 ║              260208969 ║ 2.344363391 ║
║                   16 ║          637676317 ║              275558318 ║ 2.314124725 ║
╚══════════════════════╩════════════════════╩════════════════════════╩═════════════╝

2x быстрее.