Метод бенчмарка должен имитировать какую то нагрузку, приближенную к реальной. И это явно не один вызов. В реальном приложении, учитывая указанное в задании "в расписании задано много значений. Например очень много значений с шагом в одну миллисекунду", будет очень много запусков метода в течении короткого промежутка времени. Что я и попытался хоть как то приблизительно имитировать в методе бенчмарка.
Все методы (в том числе и конструкторы) структур, в том числе DateTime, выполняются в разы быстрее, чем экземплярные методы ссылочных объектов. Плюс их создание не лезет в кучу, которая охраняется объектами синхронизации и постоянно блокируется сборщиком мусора. Подробности по разнице в производительности можно глянуть, например в статье What Is Faster In C#: A Struct Or A Class?
"по объявленному опросу тоже интересно получается. Версии "я — хуевый программист" там нету". Это вариант подразумевается по ответу заказчика. Если вы не голосовали, то очевидно выбрали этот вариант. Загвоздка в том, что есть множество людей, с которыми я работаю (и начальники, и подчинённые, и параллельные из других организаций), которые так не считают. Вот и хотелось прояснить. Спасибо за ваше мнение.
"В большинстве случаев вы не будете упираться в сборщик мусора". Согласен на 100% и именно поэтому навсегда перебрался в платформы с автоматической сборкой мусора. Но. Заказчик пишет "в расписании задано много значений. Например очень много значений с шагом в одну миллисекунду.". Это уже серьёзное масштабирование и, как мне кажется, уже явный повод снижать нагрузку на GC.
"Непонятно, но вы не спросили". Я спросил. HR отказался меня соединять с тех.специалистом и сказал что то типа "вам выдали задание, делайте что можете".
"Тест ScheduleTests.Construction — плох, поскольку лезет в internals". Ну они для того и сделаны internal чтобы тест до них достал. Конечно, по уму надо было отдельную структуру расписания, где из методов был бы только парсер. Тесты этой структуры были бы красивыми. И отдельный класс с методами поиска ближайших событий. Но заказчик задал конкретную архитектуру, где это в едином классе. И без этого залезания в internals не получится протестировать парсинг.
"Посмотреть на реализацию подобного метода у популярных ребят". Ребята из команды Quartz:
Не работают с долями секунд, а мне была поставлена задача сделать быстрое решение при планировании задач, отличающихся миллисекундами. Я считаю, что тяжёлые классы типа SortedSet - это перебор для таких вычислений.
Используют методы в десять экранов. Не факт что это лучше, чем отступы в 10 табов.
Их код оптимизировался годами. От решения тестовой задачи требуется такое же качество? При том что я не знаком с реальными условиями эксплуатации кода.
"Но почему же byte, а не boolean?". Потому что надо экономить память (просьба заказчика), а boolean может занимать и 4 байта в зависимости от платформы. Всё таки разница 5000 байтов или 1300 - уже заметная.
"в C# есть структуры для этого — BitArray, например". Такие объёмы массивов как в моём решении (около 1300 байт), это как раз граничная область где переход на биты не даёт значительного уменьшения потребляемой памяти. Такая оптимизация планировалась как потенциальная, и именно поэтому так устроены массивы.
Спасибо за развернутый ответ. Мы не дети чтобы оскорбляться от грубых слов. Конкретно по делу:
"Отказ от регулярок полностью необоснован и высосан из пальца.". Я считаю, что неприменение регулярок внутри самого .Net - это уже достаточное обоснование. Посмотрите, например, разбор SMTP-ответа, там всё также как у меня в решении.
Осталось уточнить, а сколько раз запускается собственно метод. Чтобы получить количество циклов на один вызов метода. А «создание DateTime» не является нагрузкой. Это просто 128-битное число. Никакие объекты при этом не создаются. Обычно это компилируется в регистры процессора без использования памяти вообще.
Доли секунды — это единственный компонент, который можно сразу вычислить. А дальше надо учитывать тонкости календаря, поэтому используются итерации. Ведь при любом изменении (даже на секунду) могут поменять все компоненты даты вплоть до года. Конечно, можно написать по-другому. Думаю дальше надо сравнивать уже готовые реализации.
Да, при вызове new() выделяется память для всех членов класса. Но, эта память выделяется ДО вызова конструктора, а собственно в конструкторе никаких new() нет и ничего не выделяется. Что и написано в комментарии внутри конструктора.
Спасибо за мнение. Вы осуждаете читабельность и поддерживаемость кода. Но не забывайте, заказчик особо обратил внимание на эффективность по производительности и памяти. Пришлось идти на некоторый компромисс и кое где пожертвовать читабельностью. Я считаю, что читабельность пострадала не сильно. Но точно сказать это можно будет только при сравнении с другой реализацией.
Огромное спасибо за развёрнутый ответ. Про «отсебятину» совершенно согласен. Могу только добавить. Если требуется высокая степень оптимизации, то обычно в проекте уже есть свои «правильные» методы типа Int32.Parse (). И в реальном проекте я бы воспользовался ими.
Про «от вас ожидался код, который можно проверить за пару минут, по диагонали» добавлю, что заказчик давал срок — неделя и очень удивился когда я его сделал за день. Лично я считаю, что задание, которое надо делать неделю, не должно использоваться как тестовое. Поэтому специально ограничил время, учитывая что компонент небольшой.
Бенчмарк в подпроекте TestApp.Benchmark (он единственный запускаемый). Конечно, там только заготовка, потому что запускать нужно используя параметры реальных условий эксплуатации. Я набросал только пример бенчмарка.
Моё мнение. Регулярные выражения имеют только одно преимущество — компактный код. А в остальном:
Это другой язык, требующий особых знаний для понимания, не говоря уже про доработку.
Легко «выстрелить себе в пятку», получив разбор строки на многие секунды. Для эффективности, хотя бы сравнимой с простым разбором (как в моём проекте) требуется высокая квалификация по работе с регулярками.
Даже идеально подобранная регулярка будет медленнее простого последовательного разбора.
Верно подмечено. Но заказчик не специфицировал поведение на случай когда нет подходящего события. Я посчитал нормальным бросать какое нибудь исключение.
«Тут важна не "скорость", а правильная с точки зрения хранения данных структура» — спорное утверждение. По-моему, заказчик как раз намекал на скорость. Думаю, стоило уточнить у заказчика. Но заказчик отказался предоставлять связь, на связи был только HR.
Стандартный парсер от DateTime (я его применяю в тестах) не подходит, потому что тут каждый компонент можно задавать в виде перечисления или диапазона.
Верные замечания, но вы, как мне кажется, немного недооцениваете сложность задачи. Попробуйте сами на "штатных" методах работы с датами чтобы удостовериться.
Метод бенчмарка должен имитировать какую то нагрузку, приближенную к реальной. И это явно не один вызов. В реальном приложении, учитывая указанное в задании "в расписании задано много значений. Например очень много значений с шагом в одну миллисекунду", будет очень много запусков метода в течении короткого промежутка времени. Что я и попытался хоть как то приблизительно имитировать в методе бенчмарка.
Все методы (в том числе и конструкторы) структур, в том числе DateTime, выполняются в разы быстрее, чем экземплярные методы ссылочных объектов. Плюс их создание не лезет в кучу, которая охраняется объектами синхронизации и постоянно блокируется сборщиком мусора. Подробности по разнице в производительности можно глянуть, например в статье What Is Faster In C#: A Struct Or A Class?
"по объявленному опросу тоже интересно получается. Версии "я — хуевый программист" там нету". Это вариант подразумевается по ответу заказчика. Если вы не голосовали, то очевидно выбрали этот вариант. Загвоздка в том, что есть множество людей, с которыми я работаю (и начальники, и подчинённые, и параллельные из других организаций), которые так не считают. Вот и хотелось прояснить. Спасибо за ваше мнение.
"В большинстве случаев вы не будете упираться в сборщик мусора". Согласен на 100% и именно поэтому навсегда перебрался в платформы с автоматической сборкой мусора. Но. Заказчик пишет "в расписании задано много значений. Например очень много значений с шагом в одну миллисекунду.". Это уже серьёзное масштабирование и, как мне кажется, уже явный повод снижать нагрузку на GC.
"Непонятно, но вы не спросили". Я спросил. HR отказался меня соединять с тех.специалистом и сказал что то типа "вам выдали задание, делайте что можете".
"Тест ScheduleTests.Construction — плох, поскольку лезет в internals". Ну они для того и сделаны internal чтобы тест до них достал. Конечно, по уму надо было отдельную структуру расписания, где из методов был бы только парсер. Тесты этой структуры были бы красивыми. И отдельный класс с методами поиска ближайших событий. Но заказчик задал конкретную архитектуру, где это в едином классе. И без этого залезания в internals не получится протестировать парсинг.
"Посмотреть на реализацию подобного метода у популярных ребят". Ребята из команды Quartz:
Не работают с долями секунд, а мне была поставлена задача сделать быстрое решение при планировании задач, отличающихся миллисекундами. Я считаю, что тяжёлые классы типа SortedSet - это перебор для таких вычислений.
Используют методы в десять экранов. Не факт что это лучше, чем отступы в 10 табов.
Их код оптимизировался годами. От решения тестовой задачи требуется такое же качество? При том что я не знаком с реальными условиями эксплуатации кода.
"Но почему же byte, а не boolean?". Потому что надо экономить память (просьба заказчика), а boolean может занимать и 4 байта в зависимости от платформы. Всё таки разница 5000 байтов или 1300 - уже заметная.
"в C# есть структуры для этого — BitArray, например". Такие объёмы массивов как в моём решении (около 1300 байт), это как раз граничная область где переход на биты не даёт значительного уменьшения потребляемой памяти. Такая оптимизация планировалась как потенциальная, и именно поэтому так устроены массивы.
Спасибо за развернутый ответ. Мы не дети чтобы оскорбляться от грубых слов. Конкретно по делу:
"Отказ от регулярок полностью необоснован и высосан из пальца.". Я считаю, что неприменение регулярок внутри самого .Net - это уже достаточное обоснование. Посмотрите, например, разбор SMTP-ответа, там всё также как у меня в решении.
Осталось уточнить, а сколько раз запускается собственно метод. Чтобы получить количество циклов на один вызов метода. А «создание DateTime» не является нагрузкой. Это просто 128-битное число. Никакие объекты при этом не создаются. Обычно это компилируется в регистры процессора без использования памяти вообще.
Доли секунды — это единственный компонент, который можно сразу вычислить. А дальше надо учитывать тонкости календаря, поэтому используются итерации. Ведь при любом изменении (даже на секунду) могут поменять все компоненты даты вплоть до года. Конечно, можно написать по-другому. Думаю дальше надо сравнивать уже готовые реализации.
Мне бы хватило устной просьбы «не распространять». Но её не было. Моя совесть чиста.
Да, при вызове new() выделяется память для всех членов класса. Но, эта память выделяется ДО вызова конструктора, а собственно в конструкторе никаких new() нет и ничего не выделяется. Что и написано в комментарии внутри конструктора.
Спасибо за мнение. Вы осуждаете читабельность и поддерживаемость кода. Но не забывайте, заказчик особо обратил внимание на эффективность по производительности и памяти. Пришлось идти на некоторый компромисс и кое где пожертвовать читабельностью. Я считаю, что читабельность пострадала не сильно. Но точно сказать это можно будет только при сравнении с другой реализацией.
Огромное спасибо за развёрнутый ответ. Про «отсебятину» совершенно согласен. Могу только добавить. Если требуется высокая степень оптимизации, то обычно в проекте уже есть свои «правильные» методы типа Int32.Parse (). И в реальном проекте я бы воспользовался ими.
Про «от вас ожидался код, который можно проверить за пару минут, по диагонали» добавлю, что заказчик давал срок — неделя и очень удивился когда я его сделал за день. Лично я считаю, что задание, которое надо делать неделю, не должно использоваться как тестовое. Поэтому специально ограничил время, учитывая что компонент небольшой.
Бенчмарк в подпроекте TestApp.Benchmark (он единственный запускаемый). Конечно, там только заготовка, потому что запускать нужно используя параметры реальных условий эксплуатации. Я набросал только пример бенчмарка.
Моё мнение. Регулярные выражения имеют только одно преимущество — компактный код. А в остальном:
Это другой язык, требующий особых знаний для понимания, не говоря уже про доработку.
Легко «выстрелить себе в пятку», получив разбор строки на многие секунды. Для эффективности, хотя бы сравнимой с простым разбором (как в моём проекте) требуется высокая квалификация по работе с регулярками.
Даже идеально подобранная регулярка будет медленнее простого последовательного разбора.
Верно подмечено. Но заказчик не специфицировал поведение на случай когда нет подходящего события. Я посчитал нормальным бросать какое нибудь исключение.
«Тут важна не "скорость", а правильная с точки зрения хранения данных структура» — спорное утверждение. По-моему, заказчик как раз намекал на скорость. Думаю, стоило уточнить у заказчика. Но заказчик отказался предоставлять связь, на связи был только HR.
Стандартный парсер от DateTime (я его применяю в тестах) не подходит, потому что тут каждый компонент можно задавать в виде перечисления или диапазона.
Верные замечания, но вы, как мне кажется, немного недооцениваете сложность задачи. Попробуйте сами на "штатных" методах работы с датами чтобы удостовериться.
Да, вы подметили отличное направление для оптимизации исходного кода. Но я не думал, что это так важно. Буду совершенствоваться.