Как создавать «зеленый» код

    Что такое энерго-эффективность в применении к мобильным платформам? Простыми словами это возможность сделать больше, затратив при этом меньше энергии.

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


    Если рассмотреть укрупненую модель любой мобильной платформы то она состоит и 3-х основных частей.

    Аккумулятор


    Является хранилищем энергии мобильного устройства. Производители аккумуляторов каждый год стараются увеличить емкость, уменьшить время полной зарядки.

    Железо


    Является основным прямым потребителем энергии. Тут прогресс тоже не стоит на месте. Производители «железа» создают все более энерго-эффективные чипы, выдающие большую производительность на Ватт потребленной энергии, добавляют различные режимы энергопотребления, позволяющие отключать неиспользуемое железо, переводить в режимы низкого энергопотребления, экономя тем самым батарею.

    Софт


    Является косвенным потребителем энергии. Напрямую софт ничего не потребляет, он вынуждает железо потреблять энергию. Здесь тоже есть свои методики, позволяющие продлить жизнь батареи. О проблеме энерго-эффективности софта я и хотел бы поговорить в данной статье.

    Как именно софт влияет на потребление энергии? Если в двух словах — он не дает железу «спать».

    Рассмотрим одного из крупных потребителей энергии в системе — процессор.

    Процессор может управлять своим энергопотреблением с помощью, так называемых, C-State. Для тех, кто не знаком с этими режимами, привожу короткую справку:

    С0 — рабочее состояние процессора, подразделяется на различные P-States.
    C1 — состояние, когда процессор ничего не делает, но готов приступить к работе, правда с небольшой задержкой. Многие процессоры имеют различные вариации этого состояния.
    С2 — почти тоже самое, что и С1, но в этом состоянии процессор потребляет меньше энергии, и имеет большую задержку для перехода в рабочее состояние.
    С3 — состояние «сна», переходя в это состояние процессор очищает кэш второго уровня. Характеризуется меньшим энергопотреблением, и более долгим временем перехода в рабочее состояние.

    … и так далее в зависимости от процессора.

    Для того чтобы было более наглядно приведу иллюстрацию:



    Самый энерго-эффективный вариант — процессор всегда спит. Значит самая эффективная, в плане энергозатрат программа, это та программа, которая не запущена и его не «будит». Она не производит никаких действий, и вообще ничего не потребляет. Но такой софт никому не нужен, программа должна делать что-то полезное. Компромисное решение — программа, которая не делает ничего тогда когда не должна ничего делать («будит» только по нужде), и если делает что-то, то делает это максимально быстро.

    Особенно это касается программ, которые выполняют какие-либо действия в фоновом режиме. Эти программы должны спать всегда и просыпаться только при наступлении какого-либо события.

    События рулят или Event-driven подход


    Приведу пример «неправильного» кода (к сожалению, такой подход к написанию кода используется гораздо чаще, чем вы думаете). Данный пример кода служит для получения и данных из сокета, например, в каком-нибудь серверном приложении.

    while(true)
    {
    	// Получаем данные
    	result = recv(serverSocket, buffer, bufferLen, 0);
    
    	// Обработка полученных данных	
    	if(result != 0)
    	{
    		HandleData(buffer);
    	}
    
    	// Спим секунду и повторяем
    	Sleep(1000);
    }
    

    Что же здесь «неправильного»? Есть данные или нет данных, код будет «будить» процессор каждые 1000 мс. Поведение кода напоминает осла из Шрека: «Уже приехали? А теперь приехали? А сейчас приехали?».

    «Правильный» код, для данной задачи, не будет ни кого спрашивать, он уснет у будет ждать когда разбудят его. Для этого, во многих операционных системах, существуют объекты синхронизации, такие как события. С учетом сказанного код должен выглядеть так (код не полный, опущена обработка ошибок и кодов возврата, моя задача просто проиллюстрировать принцип):

    WSANETWORKEVENTS NetworkEvents;
    WSAEVENT	 wsaSocketEvent;
    wsaSocketEvent = WSACreateEvent();
    
    WSAEventSelect(serverSocket, wsaSocketEvent, FD_READ|FD_CLOSE);
    
    while(true)
    {
    	// Ждем наступления одного из событий - получения данных или закрытия сокета
    	WaitForSingleObject(wsaSocketEvent, INFINITE);
    
    	// Что произошло?
    	WSAEnumNetworkEvents(m_hServerSocket, wsaSocketEvent, &NetworkEvents);
    	
    	// Пришли новые данные
    	if(NetworkEvents.lNetworkEvents & FD_READ)
    	{
    		// Читаем, обрабатываем
    		WSARecvFrom(serverSocket, &buffer, ...);
    	}
    }
    


    В чем прелесть примера выше? Он будет спать тогда, когда ему нечего делать.

    Таймеры, будильники нашего кода


    Иногда без таймеров не обойтись, примеров масса — проигрывание аудио, видео, анимация.

    Немного о таймерах. Интервал системного таймера Windows, по умолчанию, равен 15,6 мс. Что это означает для программ? Допустим вы хотите, чтобы выше приложение выполняло какое-то действие каждые 40 мс. Проходит первый интервал в 15,6 мс, слишком мало, проходит второй 31,1, опять рано, третий 46,8 — попали, таймер сработает. В большинстве случаев лишние 6,8 мс не имеют значения.

    Так же прямое влияние на Sleep, если вы вызовете Sleep(1), при установленном интервале в 15,6 мс, то спать код будет не 1 мс, а все 15,6 мс.

    Но если дело касается проигрывания видео — тогда это поведение не приемлемо. В этих случаях разработчик может изменить дискретность системного таймера вызвав функцию из Windows Multimedia API — timeBeginPeriod. Данная функция позволяет изменить период таймера вплоть до 1мс. Для кода это хорошо, но сильно сокращает жизнь батареи (вплоть до 25%).
    Как найти компромисс? Все просто изменяйте период системного таймера только тогда, когда это действительно необходимо. Например, если вы разрабатываете приложение, использующее анимацию и вам нужна меньшая дискретность таймера меняйте таймер тогда, когда анимация отображается и происходит, и возвращайте если, например, окно свернуто или анимация остановлена.

    С точки зрения пользователя иногда, чтобы понять как продлить жизнь от батареи будет интересна утилита Powercfg. С ее помощью можно узнать какое-то приложение изменило период системого таймера, значение периода системного таймера, информацию о проблемах драйверов, не позволяющих переводить «железо» в режим низкого энерго потребления и т.д.

    Объединение таймеров


    В Windows 7 появилась замечательная возможность объединять таймеры. Что это такое и как это работает представлено на рисунке ниже:



    Т.е. Windows «подстраивает» таймеры приложений таким образом, чтобы они совпадали со срабатываниями таймера самой операционной системы.

    Для того, чтобы использовать эту возможность необходимо вызвать

    BOOL WINAPI
    SetWaitableTimerEx(
        __in     HANDLE hTimer,
        __in     const LARGE_INTEGER *lpDueTime,
        __in     LONG lPeriod,
        __in_opt PTIMERAPCROUTINE pfnCompletionRoutine,
        __in_opt LPVOID lpArgToCompletionRoutine,
        __in_opt PREASON_CONTEXT WakeContext,
        __in     ULONG TolerableDelay
        );
    


    Полное описание функции вы можете найти в MSDN. В рамках данной статьи нас интересуют только параметр TolerableDelay, который определяет максимальное допустимое отклюнение от заданного интервала.

    Более подробно о таймерах в Windows можно прочитать в статье Timers, Timer Resolution, and Development of Efficient Code

    Сделай это быстро


    Еще один способ сделать программу более энерго-эффективной это научить ее делать нужные вещи быстро, на сколько это возможно. Добиться этого можно, например, оптимизировав код, путем использования SSE, AVX и других аппаратных возможностей платформы. В качестве примера хочу привести использование Quick Sync в Sandy Bridge для кодирования и декодирования видео. На сайте Tom's Hardware можно посмотреть результаты.

    Допустим мы оптимизировали нашу программу, но насколько она теперь более энерго-эффективна, как это оценить? Очень просто — с помощью специальных программ и инструментов.

    Инструменты для анализа энерго-эффективности


    1. Intel Power Checker. Пожалуй самый простой и быстрый способ оценить энерго-эффективность своей программы.



    Обзор и описание программы можно найти в блоге ISN

    2. Intel Battery Life analyzer



    Более сложный, но вместе с тем более информативный инструмент, служит для отслеживания различных активностей железа и софта, которые влияют на время работы от батареи.

    3. Joulemeter от Microsoft



    Тоже достаточно интересный инструмент, определяющий энергопотребление различных компонентов платформы. Может работать в связке с ваттметром WattsUp.

    Где узнать больше



    Intel Power Efficiency Community статьи, практические рекомендацие и советы по созданию энерго-эффективного программного обеспечения.

    Battery Life and Energy Efficiency сборник статей и рецептов от Microsoft

    Timers, Timer Resolution, and Development of Efficient Code ссылка уже приведена выше, для тех, кто начинает читать статью с конца.

    Если есть вопросы — задавайте в комментариях. Также свои вопросы по разработке «зеленого» софта можете задать мне на вебинаре, который состоится завтра, 15 декабря в 11 утра.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 32

      +7
      Вопрос. Вы пишете, что правильно не использовать циклы для того, чтобы проверять состояние чего-либо. Вместо этого нужно засыпать и ждать события.

      А как устроена прослушивание события внутри? Разве там не такой-же цикл? Мне кажется, что единственный способ остановить алгоритм, при этом не завершив его — это зацикливание.
        +1
        monitor/mwait еще позволяет заснуть до наступления события. В юзермоде недоступно пока, к сожалению.
          +5
          Так мне и интересно, как устроен код, который проксирует событие в пользовательский код.
          WaitForSingleObject(wsaSocketEvent, INFINITE); — вот эта функция как устроена?
            +18
            Вероятно, эта функция общается с драйвером, а драйвер обрабатывает аппаратные прерывания. В этом случае никакой цикл с поллингом не нужен
              +8
              Во, спасибо! А то минусы-минусы.
              +8
              > А кто эти прерывания генерировать будет? Тоже программы ведь.
              Неа, их генерируют устройства, потому они и назваются аппаратными.

              Процессор умеет останавливать свою работу, для этого существует специальная команда. Для х86 это HLT. Вывести его из остановленного состояния как раз может аппаратное прерывание
                0
                Это был ответ на этот комментарий
                  0
                  Я имел в виду то, что устройство без программы — простая железка, а вот та программа, которая на них выполняется, как раз и заставляет их генерировать прерывания, передавая их другим компонентам (сетевая карта слушает порт, и если она «услышит» сигнал, она разбудит ЦП, который обработает поступивший пакет, и «забудит» обратно).
                  +1
                  Вполне может быть, что она приостанавливает вызывающий поток до поступления события.
                    +1
                    Отдает управление обратно ОС, выставляется флаг мол ждет события. Ну а дальше переключается TSS на следующую задачу. Через некоторое время опять приходит черед этому процессу, проверяется есть это событие или нет, если да то продолжает выполняться, если нет то процессорное время отдается следующему. Если все ждут то работает while(1) {hlt} останавливающий проц до срабатывания следующего прерывания
                  +1
                  Проблема не в цикле,
                  WaitForSingleObject(wsaSocketEvent, INFINITE);
                  Вернет управление когда случится событие,
                  а recv(serverSocket, buffer, bufferLen, 0);
                  примет 0 байт если ничего не пришло
                    0
                    Проблема в том, что с использованием Sleep код будет производить периодическую, ненужную активность.
                    +2
                    Присоединяюсь. Есть ли hardware-поддержка Event-Driven программ, или же события таки эмулируются опросом «Уже приехали? А теперь приехали?» на самом-самом-самом низком уровне?
                      +3
                      Прерывания?
                        0
                        Да, если это аппаратные прерывания (и если я правильно представляю, как оно может работать без такой же постоянно слушающей программы, пусть и низкоуровневой)…

                        Так, стоп. А кто эти прерывания генерировать будет? Тоже программы ведь. Если не программы, значит, обработку нужно выносить на внешнее устройство, чтобы процессор вообще не был занят (ни пользовательскими программами, ни ОС, ни низкоуровневыми вещами) и, соотвественно, мог останавливать свою работу. А внешнее устройство также требует источник питания. Может, я чего-то не понимаю, но полностью уйти от постоянно работающих программ не получится (банально — генерация сигнала от видеокарты на монитор (видеокарта — тоже компьютер со своим процессором, памятью etc, и кто скажет, что это не так, пусть первый кинет в меня камень)), но можно снизить количество мучающих процессор задач путем сведения всех «опросников состояния» к более-менее единой системе (таймер ОС, аппаратные прерывания). Автор, ты это хотел сказать?
                          +1
                          Ответил тут
                            +4
                            Допустим у нас сетевой сервис, например, веб-сервер. Запросов нет, обрабатывать нечего, процессор уходит в «спячку» при вызове WaitForSingleObject(). Приходит пакет на сетевую карту, она генерирует аппаратное прерывание, процессор просыпается, получает от нее данные, возвращает управление в WaitForSingleObject(), в основном цикле программы запрос обрабатывается, опять вызывается WaitForSingleObject() и процессор опять засыпает до следующего пакета.

                            Сетевая карта при таком раскладе, конечно, должна работать, но потребляет она гораздо меньше процессора, который бы её постоянно опрашивал. То же и с видеокартой, причем есть подозрения :), что при выводе статической картинки потребление энергии минимально и вмешательства основного процессора не требуется.
                        0
                        Все это уходит очень глубоко в подсистему windows kernel, где задействуется в том числе и планировщик потоков, который не выделяет процессорного времени потоку, «висящему» на объекте синхронизации.

                        Была какая-то хорошая книжка по архитектуре Windows 2000, там это описывалось.
                        0
                        Интересно, но хотелось бы немного информации о unix like для сравнения с windows api…
                          +2
                          И там и там одинаково хорошо.
                          Это у автора надо спросить, зачем создавать видимость, что recv плохо, а WSAEventSelect — хорошо.
                            0
                            Я написал выше, что не recv плохо, а плохо создавать ненужную активность. В примере выше WSAEventSelect по сути не играет никакой роли, акцент был сделан на WaitForSingleObject :)
                              0
                              Значит название раздела «События рулят или Event-driven подход» выбрано не совсем удачно.
                                +3
                                Ну почему не сделать акцент на тот же select()? Стандартная функция из BSD sockets. Поддерживается в том числе и в WinSockets. Все примеры были бы унифицированны.
                                  0
                                  Понял свою ошибку, для чистоты сравнения обоих подходов стоило использовать просто recv после WaitForSingleObject
                                    0
                                    Ну я вообще намекал, что вместо WaitForSingleObject можно select() использовать :)
                                    Или вообще вызвать recv() в блокирующем режиме.
                            +2
                            С точки зрения энергоэфективности одинаково хорошо как event-driven в одном потоке, так и блокирующие вызовы в отдельных потоках.

                            Переход от recv (которая может работать в блокирующем режиме) к WSAEventSelect (event driven) вносит лишнюю путаницу. Event-driven с экономией на количестве потоков часто имеет смысл, но к теме этой статьи отношения не имеет.
                              +1
                              Как же вы могли в инструментах анализа энергоэффективности забыть powertop от intel?

                              www.lesswatts.org/projects/powertop/
                                0
                                Да, есть такой инструмент. Если честно то сам ни разу не пользовался, и боюсь писать о том, чего не знаю. :)
                                  0
                                  Так он же под линукс, а тут про винду вроде речь.
                                  –5
                                  Дошел до этого момента и чуть не стошнило
                                  BOOL WINAPI<br/> SetWaitableTimerEx(<br/> __in HANDLE hTimer,<br/> __in const LARGE_INTEGER *lpDueTime,<br/> __in LONG lPeriod,<br/> __in_opt PTIMERAPCROUTINE pfnCompletionRoutine,<br/> __in_opt LPVOID lpArgToCompletionRoutine,<br/> __in_opt PREASON_CONTEXT WakeContext,<br/> __in ULONG TolerableDelay<br/> );<br/>
                                  Функция с 7 параметрами, куча тайпдефов, не удивлюсь что там еще и маленькая кучка структур (с кучей своей пое..), каждый параметр нужно смотреть в документации что он значит и из чего состоит. Какой ппц, вы уж меня извините за грубость.
                                    0
                                    Простите, вы разработчик или где?
                                    p.s: и функция эта вызывается один раз только при установке таймера ;) код работы с уже имеющимся в программе таймером менять не придётся.
                                    +1
                                    Было познавательно, спасибо. Думаю логическим продолжением будет подобная статья о архитектуре APM, как там работает вся эта кухня, и за счет чего их получилось сделать менее энергоемкими.

                                    Only users with full accounts can post comments. Log in, please.