Это, кстати, можно вынести в итоги. В частности, я бы заметил, что для тестового задания не стоит себя вгонять в нереальные рамки. Либо разбивать «сидение» на части. В целом, писать сначала как получится, а потом возвращаться и смотреть свежим взглядом. Это всё, к слову, противоречит вот этому выводу:
так-себе-код и нормальный код по времени пишутся одинаково долго
Нормальный код всё-таки требует большего внимания к «мелочам», и — увы — займёт больше времени, но на дистанции это оправдается
Понравился и подход, и описание, не понравился только код. Немного побуду занудой.
Код читаем: его реально можно прочитать. Нужен парсинг — пошли посмотрели 50 строчек тут, нужно понять как маппится — 20 строчек там, нужно понять, как ищем расписания — ничего проще, вот ещё немного кода.
Я пошёл смотреть код ещё до прочтения статьи, и открыл вот это. Неподготовленный человек это не поймёт. Куча интерфейсов, подменяющих неясно зачем bool и структур, которые называются в стиле «менятель года», но в случае с FalseType (?) они меняют вообще миллисекунды (??), при этом создавая новый DateTime (???).
Я к чему. После прочтения статьи мне стало ясно и для чего такие структуры, и для чего goto. К принятым решениям вопросов нет, но чтобы такой код стал читаем — его нужно аккуратно и понятно документировать. Конечно, этот код — просто иллюстрация к статье, бла бла бла, но это должно быть, всё-таки, стандартным подходом.
P.S. IBool бы я вообще заменил на Direction, а TrueType и FalseType на Forward и Back или типа того, уж очень вводит в заблуждение.
Возможно, я просто не очень понял, что вы хотели донести этим.
То, что я не вижу смысла в создании DateTime в таких количествах. Год, месяц и т.д. можно вычислить последовательно — и уже после этого построить из них DateTime.
Именно поэтому вы решили узнать у меня, знаю ли я, чем отличаются ref и out? (вопрос риторический)
Потому что создание структуры само по себе нагрузкой не является. Существенной нагрузкой является логика в конструкторе.
То есть, вы не согласны с моей фразой, что создание структуры через конструктор является нагрузкой… и тут же пишите, что «нагрузкой является логика в конструкторе». И я вам дополнительно указывал, что я говорю не просто про какой-нибудь конструктор, речь про конкретно используемые.
В первом случае у нас просто вызывается конструктор (call .DateTime::.ctor).
Во втором случае у нас происходит аллоцирование (newobj DateTime::.ctor).
В обоих случаях происходит аллоцирование. В первом случае аллокация происходит для переменной (ldloca.s), и в уже аллоцированную память записываются значения. Разница в ключевых словах в том, что второй случай используется как параметр метода AddYears. Вот тут это всё хорошо описано Скитом.
В случае структур конструктор не создаёт объект, а просто инициализирует поля.
Нет, создаёт. Собственно, если бы «просто инициализировались поля», вы бы не смогли передать объект структуры через ref. Ещё, можете попробовать сделать v is object
Следующие примеры идентичны по логике, а варианты 2 и 3 — идентичны по MSIL (разница только в сигнатурах методов).
Они создаются с вполне понятной целью: преобразовать (year, month, day, hour, minute, second) в ticks, прибавить к ticks некоторое значение, а затем сделать обратное преобразование в (year, month, day, hour, minute, second).
Хорошо, что вы всё поняли. Расскажите тогда, зачем 200 раз преобразовывать туда-сюда?
Ну а сам по себе DateTime — это структура, обёртка над long, и потому накладные расходы, связанные с использованием обёртки, ничтожно малы.
Я последний раз напишу: сколько бы ничтожно малой операция не была, если она лишняя — её стоит удалить.
Метод бенчмарка должен имитировать какую то нагрузку, приближенную к реальной.
Нет, не должен. Вы не понимаете, что такое бенчмарк. А это даже не тест производительности.
Все методы (в том числе и конструкторы) структур, в том числе DateTime, выполняются в разы быстрее, чем экземплярные методы ссылочных объектов.
Да причём тут экземпляры методов ссылочных объектов? Я вам говорю, что вы создаёте ненужные DateTime. Зачем вы мне рассказываете о том, что там быстрее, о куче, о сборщике мусора, синхронизации и прочем. Это же элементарная вещь. Простая, как дрын. Не нужно создавать лишнее.
public class MyTest
{
[Benchmark]
public void Long ()
{
new DateTime(637637692751830282);
}
[Benchmark]
public void Contructor()
{
new DateTime(2021, 08, 05, 10, 10, 10, 10);
}
}
Вы делаете ложное предположение о причинах и на нём строите выводы.
Использование System.Text.RegularExpressions в других System.* библиотеках даст лишнюю зависимость, и она должна быть хорошо обоснована. Для одного места этого никто не будет делать. Даром что это код низкого уровня.
Кстати, на примере этого же класса вы можете начать пользоваться goto. Это, ведь, достаточное обоснование.
Да что ж такое-то, право слово. Ну где я писал, что создание структур — это «что-то тяжёлое»?
Например, конструктор `DateTime(long ticks)` вообще невесомый и ничем не отличается от просто присваивания одного лонга другому.
Я всё-таки говорю про конкретную реализацию, там используются другие конструкторы. На один цикл создаётся и тут же выбрасывается сотни объектов DateTime. И мы говорим о перфоманс-ориентированном коде для собеседования.
Осталось уточнить, а сколько раз запускается собственно метод. Чтобы получить количество циклов на один вызов метода.
Ваш метод бенчмарка запускается, очевидно, один раз. В нём он запускает методы NextEvent и PrevEvent по 216963 раз.
Так вот оно что, ваш «бенчмарк» тестирует не метод, который вы написали, а сотни тысяч вызовов этих методов. Зачем??
А «создание DateTime» не является нагрузкой. Это просто 128-битное число.
DateTime — это структура, и её создание через конструктор само по себе является нагрузкой в плане производительности, а число — это тоже объект. Про память же я ничего не писал. Да, это структура хранит одно число, хоть и 64-битное.
Насчёт производительности, раз уж заказчик обратил особое внимание и из-за которой, как вы говорите, пожертвовали читабельностью. Я запустил ваш же бенчмарк, и он показывает, что для всех вариантов, кроме первого, выполнение идёт несколько секунд.
Посмотрел на одном из них ("*.*.1 0:0:0"), там в методе NearestEvent (сам его код мой мозг отказался осмысливать) цикл while (true) выполняется 3777575 раз. Прибавим сюда ещё NearestPrevEvent, в котором цикл запускается 33973986 раз. Каждый раз создавая новый объект DateTime. Это грустно.
Я уже выше написал, но повторю вам как автору. У вас там нет бенчмарка. В лучшем случае, это тест производительности. Бенчмарк по определению сравнивает несколько версий одного и того же для поиска более оптимального. Если будет время, я даже, может, дополню его версией с регулярками
Нормальный код всё-таки требует большего внимания к «мелочам», и — увы — займёт больше времени, но на дистанции это оправдается
Я пошёл смотреть код ещё до прочтения статьи, и открыл вот это. Неподготовленный человек это не поймёт. Куча интерфейсов, подменяющих неясно зачем bool и структур, которые называются в стиле «менятель года», но в случае с FalseType (?) они меняют вообще миллисекунды (??), при этом создавая новый DateTime (???).
Я к чему. После прочтения статьи мне стало ясно и для чего такие структуры, и для чего goto. К принятым решениям вопросов нет, но чтобы такой код стал читаем — его нужно аккуратно и понятно документировать. Конечно, этот код — просто иллюстрация к статье, бла бла бла, но это должно быть, всё-таки, стандартным подходом.
P.S. IBool бы я вообще заменил на Direction, а TrueType и FalseType на Forward и Back или типа того, уж очень вводит в заблуждение.
То есть, вы не согласны с моей фразой, что создание структуры через конструктор является нагрузкой… и тут же пишите, что «нагрузкой является логика в конструкторе». И я вам дополнительно указывал, что я говорю не просто про какой-нибудь конструктор, речь про конкретно используемые.
v is objectПотому что значение по умолчанию для struct типов создаётся при помощи конструктора без параметров.
То есть, они идентичны потому, что конструктор вызывается и там, и там, а не наоборот.
Я последний раз напишу: сколько бы ничтожно малой операция не была, если она лишняя — её стоит удалить.
Сначала сравниваются методы WriteThenRead и ReadThenWrite, затем Channel_ReadThenWrite и BufferBlock_ReadThenWrite.
Просто для повествования они рассмотрели их в первом случае по отдельности.
Да причём тут экземпляры методов ссылочных объектов? Я вам говорю, что вы создаёте ненужные DateTime. Зачем вы мне рассказываете о том, что там быстрее, о куче, о сборщике мусора, синхронизации и прочем. Это же элементарная вещь. Простая, как дрын. Не нужно создавать лишнее.
| Method | Mean | Error | StdDev || Long | 1.906 ns | 0.0140 ns | 0.0131 ns || Contructor | 11.807 ns | 0.2642 ns | 0.2472 ns |Разница в 6 раз. Я не знаю, о каких «обёртках над long» вы вообще пишете.
Использование System.Text.RegularExpressions в других System.* библиотеках даст лишнюю зависимость, и она должна быть хорошо обоснована. Для одного места этого никто не будет делать. Даром что это код низкого уровня.
Кстати, на примере этого же класса вы можете начать пользоваться goto. Это, ведь, достаточное обоснование.
Так вот оно что, ваш «бенчмарк» тестирует не метод, который вы написали, а сотни тысяч вызовов этих методов. Зачем??DateTime — это структура, и её создание через конструктор само по себе является нагрузкой в плане производительности, а число — это тоже объект. Про память же я ничего не писал. Да, это структура хранит одно число, хоть и 64-битное.
Посмотрел на одном из них ("*.*.1 0:0:0"), там в методе NearestEvent (сам его код мой мозг отказался осмысливать) цикл
while (true)выполняется 3777575 раз. Прибавим сюда ещё NearestPrevEvent, в котором цикл запускается 33973986 раз. Каждый раз создавая новый объект DateTime. Это грустно.