В предыдущем видео мы с вами с помощью измерений смогли найти узкое место в программе, из-за которого она работала медленно. На практике, для этого применяют специальные инструменты, которые называются профайлеры. И вот у меня на экране перечислены некоторые из них. Сами по себе это достаточно большие, сложные инструменты, которые требуют определенного времени на освоение, и при этом, они платформенно-специфичные вы можете видеть, что у меня на экране нет ни одного профайлера, который был бы представлен и на Windows, и на Linux, и на MacOS. И возникает, собственно, вопрос: а как же нам вот в нашем курсе, чем же нам пользоваться, чтобы искать узкие места в наших программах? А вам естественно придется их искать, мы уж вам точно дадим соответствующие задачи. И давайте мы здесь поступим также, как поступили в желтом поясе с фреймворком для написании юнитестов. Мы сделаем свой, простой маленький профайлер и будем им пользоваться. Преимущество здесь от разработки своего инструмента такие же, как и были в юнитест фреймворке, он будет простой и вы будете понимать, как он устроен внутри, будете им пользоваться и возможно при желании добавлять для себя какую-то функциональность. Для написания программы у нас будет, для написания профайлера у нас будет подопытный кролик в виде вот такой вот достаточно простой программы. Она открывает файл input.txt, считывает из него количество элементов и потом считывает такое количество элементов и добавляет их в множество elements. Дальше наша программа считывает количество запросов, и считывает это количество чисел из входного файла и для каждого числа проверяет — есть это число в нашем множестве, или нету. Если есть, то увеличивает на единицу переменную total_found, и в конце выводит значение этой переменной на экран. Эта программа нам будет нужна, для того, чтоб мы демонстрировали на ней как пользоваться нашим профайлером. Итак, давайте поймем, что мы хотим. Мы хотим разработать некий инструмент, который позволит нам легко замерять время работы произвольного блока программы. Как мы на данный момент умеем мерить время работы куска кода? Ну мы это уже делали в предыдущих видео, мы умеем запоминать начало работы, момент в начале работы нашего кода. Дальше мы запоминаем момент его окончания, также с помощью steady_clock:: now. Дальше мы считаем, сколько разность между этими двумя моментами, и с помощью не самой красивой конструкции duration_cast [БЕЗ_СЛОВ] выводим на экран, выводим на экран длительность работы кода. Вот давайте мы скомпилируем то, что мы здесь написали. Оно скомпилировалось и запустим. Вот наш код отработан, тут надо сказать, что вот мы здесь читаем из файла input.txt, и этот файл содержит достаточно (вот он у меня в консоли), он содержит по 250 тысяч элементов для нашего множества и 250 тысяч запросов к нашему множеству. Ну такие большие числа выбраны, чтобы мы могли видеть какое-то различимое количество милисекунд, которое работает наша программа. Вот здесь мы видим, что ввод множества из файла занимает 126 милисекунд. Итак, мы понимаем, что вот такой вот способ замера времени работы блока кода — неудобный. Ну потому что, если мы захотим какой-то другой блок померить, то нам придется что, вот это все копипастить? Это неудобно, а мы хотим уметь легко — раз, вставил какую-то одну строчку, и у тебя уже мерится сколько работает кусок кода, который идет после нее. Что бы нам хотелось? Нам бы хотелось вот то, что я сказал: вставил одну строчку, и все мерится. Как мы это можем сделать? Смотрите, какое средство в языке C++ умеет выполнять действие когда мы его объявляем, и умеет выполнять действие, когда закончился соответствующий блок кода? Это классы. У них есть конструкторы, которые то-то делают, когда мы объявили переменную этого класса, и деструкторы, которые выполняются, когда наш объект выходит из области видимости. И вот давайте мы напишем специальный класс, назовем его LogDuration (залогировать длительность). Как он будет выглядеть? У него будет приватное поле типа steady_clock::time_point. Это момент начала измерений. Собственно, в конструкторе мы в это поле запишем steady_clock::now. Все, в момент создания мы запомнили момент времени. Теперь деструктор. [БЕЗ_СЛОВ] Деструктор. Что мы хотим делать в деструкторе? Мы хотим залогировать LogDuration. Мы берем и выводим, ну вернее, мы делаем вот эти наши действия, которые у нас здесь уже запрограммированы, и их надо просто перенести в деструктор. Мы запоминаем момент окончания работы, вычисляем разность даже, чтобы у нас все было красиво. А нет, тут мы сэкономим. Дальше. Выводим длительность, указываем, что это милисекунды, переводим строку, и готово. Как мы этим пользуемся? Ну, очень просто. Мы, например, хотим померить, сколько у нас выполняется ввод. Мы берем, пишем LogDuration input. Но мы же хотим померить, сколько вот этот блок выполняется, поэтому мы можем взять его и обернуть в фигурные скобки. Давайте убедимся, что это компилируется. Компилируется. Запустим. И у нас наша программа отработала, мы видим, что в консоль вывело 133 милисекунды. Какой есть недостаток у нашего класса LogDuration, который мы только что написали? Ну если мы, допустим, захотим еще померить сколько у нас еще работает подсчет, ну обработка запросов, допустим, также мы берем, заводим переменную queries, оборачиваем кусок кода в фигурные скобки, компилируем, запускаем и у нас вывелось 74 милисекунды. Глядя на это, непонятно, что такое 120 милисекунд, что такое 74 милисекунды. Когда будете анализировать вывод, будете путаться. Непонятно, что это такое. Поэтому давайте мы добавим в наш класс LogDuration какую-то строку, какое-то сообщение, которое будет выводиться вместе с длительностью. Ну и пусть это сообщение по умолчанию будет пустым, ну так нам будет удобнее. Тогда мы заводим поле в нашем классе, в которое мы запоминаем сообщение. Ну здесь я для удобства сразу добавлю вот к сообщению двоеточие и пробел. И как тогда будет выглядеть вывод? Мы выводим message, выводим длительность. Компилируется? Компилируется. Ну естественно, у нас есть значение по умолчанию. Кстати, так как это конструктор от одного параметра, мы должны сделать его explicit (явным). И теперь мы можем добавить сообщение. Компилируется. Запускаем. И видим, что теперь наш класс LogDuration выводит достаточно понятные сообщения. Он выводит, сколько времени у нас ушло на ввод, и сколько времени у нас ушло на обработку запросов. Что еще мы можем померить? Ну например, мы можем померить, сколько у нас вообще выполнялась наша программа. Объявляем переменную total вначале, функцией main запускаем, компилируем, запускаем и видим, что ввод выполнялся 102 милисекунды, обработка запросов 71 милисекунду, а всего программа работала 175 милисекунд. Отлично, у нас уже получился хороший инструмент, которым можно пользоваться.