php microtime to seconds
Определяем время работы скрипта PHP
Доброго времени суток, коллеги! 🙂
Сегодня я решил затронуть такую тему, как время выполнения скрипта PHP и его определение.
Не скажу, что данная необходимость возникает каждый день, но время от времени на практике возникают ситуации, когда нужно получить время работы скрипта PHP.
Особенно часто за таким занятием можно застукать разработчиков, занимающихся рефакторингом кода и оптимизацией существующих алгоритмов, когда время выполнения PHP скрипта является ключевым фактором при выборе конечного варианта.
Ну, и ещё иногда данные цифры требуют менеджеры и заказчики, желающие выяснить, сколько времени потребуется на то или иное действие в коде.
Поэтому я и решил написать данную коротенькую заметку, в которой решил изложить порядок действий в аналогичной ситуации.
Сперва мы рассмотрим сам алгоритм определения времени исполнения PHP скрипта, а затем я приведу код, с помощью которого он будет реализовываться.
Время выполнения PHP скрипта — алгоритм определения
Порядок наших действий будет предельно прост:
Полученное в итоге значение как раз и будет временем выполнения PHP скрипта, которое можно будет принимать во внимание при дальнейшей оптимизации и прочих действиях.
Время работы PHP скрипта — реализация алгоритма
Для вывода текущего времени в PHP коде я решил воспользоваться стандартной PHP функцией microtime(), которая возвращает текущую метку времени в Unix формате с микросекундами.
Зачем такая точность?
Затем, чтобы уловить малейшие изменения времени выполнения скриптов, т.к. иногда доли секунды могут быть фатальны и привести к большим потерям при использовании медленного кода в больших масштабах.
Ну, и плюс, учёт микросекунд при вычислениях влияет на точность калькуляций в положительную сторону.
Для демонстрации своих теоретических повествований я написал простенький скриптик, который вычисляет время прогона пустого цикла с 30 000 000 итераций (решил взять побольше для наглядности):
Как сказано в официальной документации PHP, по умолчанию microtime() возвращает строку в формате «msec sec», где sec — количество секунд с начала эпохи Unix (1 января 1970 0:00:00 GMT), а msec — это количество микросекунд, прошедших после sec.
Функция PHP microtime() имеет всего один параметр get_as_float, при указании которому значения true можно получить текущее время PHP в секундах, прошедших с начала эпохи Unix с точностью до микросекунд.
Поскольку мне нужно было именно текущее время, а не количество секунд с начала эпохи Unix, то я воспользовался данным параметром, что можно видеть в моём коде.
В итоге, с помощью функции echo(), на экран вывелось следующее сообщение: Скрипт был выполнен за 1.3156361579895 секунд.
Чтобы определить, что данная методика работает верно, я решил задать фиксированное время выполнения скрипта с помощью функции sleep(), которая делает задержку выполнения скрипта на указанное количество секунд.
В итоге, следующая конструкция вернула сообщение Скрипт был выполнен за 2.0000510215759 секунд:
Превышение указанной задержки на микроскопические доли секунд можно списать на время вызова кодовых конструкций и обработку результатов их выполнения серверным железом, поэтому на них вполне можно закрыть глаза.
Если они будут всё-таки раздражать вас или вашего заказчика, то можете воспользоваться хаком в виде банального округления до сотых или тысячных долей с помощью PHP функции round(), задействовав её следующим образом:
Результатом выполнения данного куска кода для вышеприведённого примера станет значение в кругленькие 2 секунды, что устроит самых искушённых перфекционистов 🙂
На этом сегодняшняя информация о том, как можно определить время выполнения скрипта PHP, подходит к концу.
Пишите свои отзывы в комментариях и задавайте интересующие вас вопросы в пабликах проекта в социальных сетях.
Всем удачи и до новых встреч! 🙂
P.S.: если вам нужен сайт либо необходимо внести правки на существующий, но для этого нет времени и желания, могу предложить свои услуги.
Более 5 лет опыта профессиональной разработки сайтов. Работа с PHP, OpenCart, WordPress, Laravel, Yii, MySQL, PostgreSQL, JavaScript, React, Angular и другими технологиями web-разработки.
Опыт разработки проектов различного уровня: лендинги, корпоративные сайты, Интернет-магазины, CRM, порталы. В том числе поддержка и разработка HighLoad проектов. Присылайте ваши заявки на email cccpblogcom@gmail.com.
И с друзьями не забудьте поделиться 😉
Время высокой точности: как работать с долями секунды в MySQL и PHP
Однажды я поймал себя на мысли, что при работе со временем в базах данных почти всегда использую время с точностью до секунды просто потому, что я к этому привык и что именно такой вариант описан в документации и огромном количестве примеров. Однако сейчас такой точности достаточно далеко не для всех задач. Современные системы сложны — они могут состоять из множества частей, иметь миллионы пользователей, взаимодействующих с ними, — и во многих случаях удобнее использовать бОльшую точность, поддержка которой уже давно существует.
В этой статье я расскажу про способы использования времени с дробными частями секунды в MySQL и PHP. Она задумывалась как туториал, поэтому материал рассчитан на широкий круг читателей и местами повторяет документацию. Основную ценность должно представлять то, что я собрал в одном тексте всё, что нужно знать для работы с таким временем в MySQL, PHP и фреймворке Yii, а также добавил описания неочевидных проблем, с которыми можно столкнуться.
Я буду использовать термин «время высокой точности». В документации MySQL вы увидите термин “fractional seconds”, но его дословный перевод звучит странно, а другого устоявшегося перевода я не нашёл.
Когда стоит использовать время высокой точности?
Для затравки покажу скриншот списка входящих писем моего почтового ящика, который хорошо иллюстрирует идею:
Письма представляют собой реакцию одного и того же человека на одно событие. Человек случайно нажал не на ту кнопку, быстро понял это и исправился. В результате мы получили два письма, отправленных примерно в одно и то же время, которые важно правильно отсортировать. Если время отправки совпадает, есть шанс, что письма будут показаны в неправильном порядке и получатель будет сконфужен, так как потом получит не тот результат, на который будет рассчитывать.
Я сталкивался со следующими ситуациями, в которых время высокой точности было бы актуально:
Нужно иметь в виду, что нельзя верить полученным значениям на 100% и реальная точность получаемых значений может быть меньше шести знаков после запятой. Это происходит из-за того, что мы можем получить неточное значение времени (особенно при работе в распределённой системе, состоящей из многих серверов), время может неожиданно измениться (например, при синхронизации через NTP или при переводе часов) и т. д. Я не стану здесь останавливаться на всех этих проблемах, но приведу пару статей, где про них можно почитать подробнее:
Работа со временем высокой точности в MySQL
В этом примере у метки времени вставленной записи нулевая дробная часть. Это произошло потому, что входящее значение было указано с точностью до секунды. Для решения проблемы нужно, чтобы точность входящего значения была такой же, как у значения в базе данных. Совет кажется очевидным, но он актуален, поскольку подобная проблема может всплыть в реальных приложениях: мы сталкивались с ситуацией, когда значение на входе имело три знака после запятой, а в базе данных хранилось шесть.
Самый простой способ предупредить возникновение этой проблемы — использовать входящие значения с максимальной точностью (до микросекунды). В этом случае при записи данных в таблицу время округлится до требуемой точности. Это абсолютно нормальная ситуация, которая не будет вызывать никаких warning-ов (предупреждений):
При использовании автоматической инициализации и автоматического обновления колонок типа TIMESTAMP с помощью конструкции вида DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP важно, чтобы значения имели ту же точность, что и сама колонка:
Функции MySQL для работы со временем поддерживают работу и с дробной частью единиц измерения. Перечислять их все я не буду (предлагаю посмотреть в документации), но приведу несколько примеров:
В данном примере точность значений в запросе выше, чем точность значений в базе, и проблема возникает «на границе сверху». В обратной ситуации (если значение на входе будет иметь точность ниже, чем значение в базе) проблемы не будет — MySQL приведёт значение к нужной точности и в INSERT-е, и в SELECT-е:
Согласованность точности значений всегда стоит держать в голове при работе со временем высокой точности. Если подобные граничные проблемы для вас критичны, то нужно следить за тем, чтобы код и база данных работали с одинаковым количеством знаков после запятой.
Объём места, занимаемого дробной частью единицы времени, зависит от количества знаков в колонке. Кажется естественным выбирать привычные значения: три или шесть знаков после запятой. Но в случае с тремя знаками не всё так просто. Фактически MySQL использует один байт для хранения двух знаков дробной части:
Fractional Seconds Precision | Storage Required |
---|---|
0 | 0 bytes |
1, 2 | 1 byte |
3, 4 | 2 bytes |
5, 6 | 3 bytes |
Получается, что если вы выбираете три знака после запятой, то не в полной мере используете занятое место и при тех же накладных расходах могли бы взять четыре знака. Вообще я рекомендую всегда использовать чётное количество знаков и при необходимости «обрезать» ненужные при выводе. Идеальный же вариант — не жадничать и брать шесть знаков после запятой. В худшем случае (при типе DATETIME) эта колонка займёт 8 байт, то есть столько же, сколько целое число в колонке типа BIGINT.
Работа со временем высокой точности в PHP
Мало иметь время высокой точности в базе данных — нужно уметь работать с ним в коде ваших программ. В этом разделе я расскажу про три основных момента:
Получение и форматирование времени
При работе со временем есть несколько основных операций, которые нужно уметь делать:
В этой части я расскажу, какие возможности для выполнения этих операций есть в PHP.
Вот сигнатуры основных таких функций из официальной документации:
Символ в строке format | Описание | Пример возвращаемого значения |
---|---|---|
u | Микросекунды (добавлено в PHP 5.2.2). Учтите, что date() всегда будет возвращать 000000, т.к. она принимает целочисленный параметр, тогда как DateTime::format() поддерживает микросекунды, если DateTime создан с ними. | Например: 654321 |
v | Миллисекунды (добавлено в PHP 7.0.0). Замечание такое же как и для u. | Например: 654 |
Если вам нужно работать только с таймерами, то хорошим вариантом будет библиотека HRTime, которую я не стану рассматривать подробнее из-за ограниченности её применения. Скажу только, что она позволяет работать со временем с точностью до наносекунды и гарантирует монотонность таймеров, что избавляет от части проблем, с которыми можно столкнуться при работе с другими библиотеками.
Для полноценной работы с дробными частями секунды нужно использовать модуль DateTime. С определёнными оговорками он позволяет выполнять все перечисленные ранее операции:
Буква u в строке форматирования означает микросекунды, но она корректно работает и в случае с дробными частями меньшей точности. Более того, это единственный способ задать дробные части секунды в строке формата. Пример:
В целом я советую проявлять осторожность при работе с интервалами и тщательно покрывать такой код тестами.
Работа со временем высокой точности в PDO
PDO и mysqli — это два основных интерфейса для выполнения запросов к базам данных MySQL из PHP-кода. В контексте разговора про время они похожи друг на друга, поэтому я расскажу только про один из них — PDO.
При работе с базами данных в PDO время фигурирует в двух местах:
Хорошим тоном при передаче параметров в запрос является использование плейсхолдеров. В плейсхолдеры можно передавать значения из очень небольшого набора типов: булевы значения, строки и целые числа. Подходящего типа для даты и времени нет, поэтому необходимо вручную преобразовать значение из объекта класса DateTime/DateTimeImmutable в строку.
Использовать такой код не очень удобно, поскольку каждый раз нужно делать форматирование переданного значения. Поэтому в кодовой базе Badoo мы реализовали поддержку типизированных плейсхолдеров в нашей обёртке для работы с базой данных. В случае с датами это очень удобно, так как позволяет передавать значение в разных форматах (объект, реализующий DateTimeInterface, отформатированная строка или число с меткой времени), а уже внутри делаются все необходимые преобразования и проверки корректности переданных значений. В качестве бонуса при передаче некорректного значения мы узнаём об ошибке сразу, а не после получения ошибки от MySQL при выполнении запроса.
Получение данных из результатов запроса выглядит довольно просто. При выполнении этой операции PDO отдаёт данные в виде строк, и в коде нужно дополнительно обработать результаты, если мы хотим работать с объектами времени (и тут нам потребуется функционал получения момента времени из отформатированной строки, о котором я рассказывал в предыдущем разделе).
К сожалению, тут есть проблема, о которой нужно знать. В PHP до версии 7.3 есть баг, из-за которого PDO при выключенном атрибуте PDO::ATTR_EMULATE_PREPARES «обрезает» дробную часть секунды при её получении из базы. Подробности и пример можно посмотреть в описании бага на php.net. В PHP 7.3 эту ошибку исправили и предупредили о том, что это изменение ломает обратную совместимость.
Работа со временем высокой точности в Yii 2
Большинство современных фреймворков предоставляют функционал миграций, который позволяет хранить в коде историю изменений схемы базы данных и инкрементально изменять её. Если вы используете миграции и хотите использовать время высокой точности, то ваш фреймворк должен его поддерживать. К счастью, это работает из коробки во всех основных фреймворках.
В данном разделе я покажу, как эта поддержка реализована в Yii (в примерах я использовал версию 2.0.26). Про Laravel, Symfony и другие я не стану писать, чтобы не делать статью бесконечной, но буду рад, если вы добавите деталей в комментариях или новых статьях на эту тему.
Пример миграции, в которой создаётся новая таблица с колонкой времени высокой точности:
А это пример миграции, в которой меняется точность в уже существующей колонке:
Заключение
Надеюсь, мне удалось показать, что время высокой точности — полезная штука, которую можно использовать уже сегодня. Если после прочтения статьи вы станете более осознанно подходить к работе с точностью времени в своих проектах, то я буду считать, что добился своей цели. Спасибо, что дочитали до конца!
mktime
(PHP 4, PHP 5, PHP 7, PHP 8)
mktime — Get Unix timestamp for a date
Description
Returns the Unix timestamp corresponding to the arguments given. This timestamp is a long integer containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time specified.
Arguments may be left out in order from right to left; any arguments thus omitted will be set to the current value according to the local date and time.
Calling mktime() without arguments is deprecated. time() can be used to get the current timestamp.
Parameters
The number of the year, may be a two or four digit value, with values between 0-69 mapping to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as most common today, the valid range for year is somewhere between 1901 and 2038.
Return Values
Errors/Exceptions
Every call to a date/time function will generate a E_WARNING if the time zone is not valid. See also date_default_timezone_set()
Changelog
Examples
Example #1 mktime() basic example
// Set the default timezone to use.
date_default_timezone_set ( ‘UTC’ );
Example #2 mktime() example
mktime() is useful for doing date arithmetic and validation, as it will automatically calculate the correct value for out-of-range input. For example, each of the following lines produces the string «Jan-01-1998».
Example #3 Last day of a month
See Also
User Contributed Notes 27 notes
Do remember that, counter-intuitively enough, the arguments for month and day are inversed (or middle-endian). A common mistake for Europeans seems to be to feed the date arguments in the expected order (big endian or little endian).
Be careful passing zeros into mktime, in most cases a zero will count as the previous unit of time. The documentation explains this yet most of the comments here still use zeroes.
For example, if you pass the year 2013 into mktime, with zeroes for everything else, the outcome is probably not what you are looking for.
Please note, mktime requires an integer value, if you use date(«H»), date(«i»), date(«s») as a value, which is actually have a leading zero, you may get «A non well formed numeric value encountered» notice. so you need some tricks like this
mktime( date(«G»), intval(date(«i»)), intval(date(«s»), date(«n»), date(«j»), date(«Y») )
Since there are no minute & second without leading zero in the date function, we can use the intval() function or you can cast value type like this to force the value type.
I was using the following to get a list of month names.
Pay attention that not all days have the same number of seconds (86400s) if you are using date_default_timezone_set(..) and the used timezone has Daylight Saving Time (DST) e.g. «Europe/Berlin». Under PHP 5.5.16 I get the following results:
You may workaround this by using date_default_timezone_set(‘UTC’) where all days have the same number of seconds.
The following function moves all the parameters in order of most significant (biggest) to least significant (smallest) order.
Year is bigger than month. Month is bigger than day. Day bigger than hours.
Much less confusing than mktime order.
Add (and subtract) unixtime:
Function to generate array of dates between two dates (date range array)
echo » ;
?>
[EDIT BY danbrown AT php DOT net: Contains a bugfix submitted by (carlosbuz2 AT gmail DOT com) on 04-MAR-2011, with the following note: The first date in array is incorrect.]
The maximum possible date accepted by mktime() and gmmktime() is dependent on the current location time zone.
You cannot simply subtract or add month VARs using mktime to obtain previous or next months as suggested in previous user comments (at least not with a DD > 28 anyway).
If the date is 03-31-2007, the following yeilds March as a previous month. Not what you wanted.
If you are just looking to do month and year arithmetic using mktime, you can use general days like 1 or 28 to do stuff like this:
What’s odd is that mktime doesn’t seem to support every possible year number. It’s common sense that 2 digit (shortened) year numbers are interpreted in the range 1970..2069
However, when padded with zeroes, no such transformation should happen (at least that is the behaviour of other date functions). Unfortunately it does (until year 100 *inclusive*):
>*/
if(strlen($value[2])==4)/13/12/2012
//int mktime([hour[minute[second[month[day[year
return mktime(0, 0, 0,$value[1],$value[0],$value[2]);
>else < //2012/12/13
//int mktime([hour[minute[second[month[day[year
return mktime(0, 0, 0,$value[1],$value[2],$value[0]);
>
>
caculate days between two date
There are several warnings here about using mktime() to determine a date difference because of daylight savings time. However, nobody seems to have mentioned the other obvious problem, which is leap years.
Leap years mean that any effort to use mktime() and time() to determine the age (positive or negative) of some timestamp in years will be flawed. There are some years that are 366 days long, therefore you cannot say that there is a set number of seconds per year.
Timestamps are good for determining *real* time, which is not the same thing as *human calendar* time. The Gregorian calendar is only an approximation of real time, which is tweaked with daylight savings time and leap years to make it conform more to humans’ expectations of how time should or ought to work. Timestamps are not tweaked and therefore are the only authoritative way of recording in computers a proper order of succession of events, but they cannot be integrated with a Gregorian system unless you take both leap years and DST into account. Otherwise, you may get the wrong number of years when you are approaching a value of exactly X years.
As for PHP, you could still use timestamps as a way of determining age if you took into account not only DST but also whether or not each year is a leap year and adjusted your calculations accordingly. However, this could become messy and inefficient.
This solution works because it stays within the Gregorian system and doesn’t venture into the world of timestamps.
There is also the issue of leap seconds, but this will only arise if you literally need to get the *exact* age in seconds. In that case, of course, you would also need to verify that your timestamps are exactly correct and are not delayed by script processing time, plus you would need to determine whether your system conforms to UTC, etc. I expect this will hardly be an issue for anybody using PHP, however if you are interested there is an article on this issue on Wikipedia:
How can I time PHP’s microtime itself?
I tend to check the speed of my PHP stuff using the usual approach.
How can I time how much time is used by the two calls of PHP’s microtime function itself?
4 Answers 4
The answer is: “You can’t – meaning: your timing result will not 100% exact!”.
Fact is, logic already implies that it is not really possible time «how much time is used by the two calls of PHP’s microtime function itself» and get a result that’s 100% exact, as you’ll always end up including (at least) one variable assignment in your timing.
So, this is the closest you’ll get:
To explain what I mean: We can avoid the variable assignment at the end of the timing sequence by replacing
No matter how we try to approach the problem, we’ll always end up doing the following:
You need point “2.” to remember the starting time. You can’t skip that variable assignment in any way or work around the problem. Whatever you do, you’ll always be including a variable assignment in your timing result. And to make it worse: you’ll never know how much time was wasted during that variable assignment, making your result incorrect (better: not 100% correct).
To help you understand it… in case of the question and the code I provided at the start of this answer, the problem can be visualized as:
Explaining it: microtime will know the exact time before it returns it’s value – so we won’t get an exact value returned (but pretty close). Then the returned value needs to be assigned to a variable so we can remember it – which takes more time which is not related to what we actually want to time.
Wrapping it up: you can get close, but your return value will never be 100% correct. If you’ve understood what I explained above, you’ll now know that, whatever you’re timing in PHP, will always include the time wasted on that one “remember-start-time” variable initialization.
Even if you would create a script containing nothing but and use an external program… you would notice you’re doing nothing else but shifting the problem to the external program. The external program will have the same problem the visualization shows, because the external program will have to remember the starting-time by initializing a variable too, which means the external program will not be able to correctly time it either.
In the end, my question ended up to be more of a brain-teaser to think about the logic behind timing programs, loops, and functions using PHP as an example. As my answer explains it doesn’t matter what coding language you use, you’ll always hit the same issue.