Bajo este título tan raro se encuentra escondida una de las características más interesantes de Windows 7 para desarrollar aplicaciones energéticamente eficientes.
Conforme la potencia de cálculo de los procesadores aumenta, también aumenta su consumo energético, y por supuesto el software juega un papel importante en este consumo. Así que lo que veremos en este post es algunas de las características más interesantes de Windows 7 para hacer aplicaciones energéticamente eficientes.
Los procesadores actuales incluyen funcionalidad que les permite establecerse en un estado de Power Ilde, en el que el procesador entra en un estado de bajo consumo de energía hasta que es interrumpido por alguna razón. La velocidad de los procesadores actuales hace que la medida de su tiempo comparada con la nuestra sea completamente abismal, ya que para nosotros un simple segundo es más que la eternidad para un procesador. Así que teniendo en cuenta este escenario y el consumo de CPU de la mayoría de los programas y del kernel en sí, el escenario más común a optimizar por la mayoría de las aplicaciones es el Idle. Es decir tenemos que hacer aplicaciones que respeten al máximo el estado de bajo consumo energético del procesador haciendo así que el procesador no esté constantemente saliendo y entrando de ese modo de bajo consumo energético, esto es así porque las constantes interrupciones hacen que se consuma más energía en esos cambios de estado que la energía que se ahora por estar el procesador en el estado de bajo consumo energético.
Sabiendo esto como podemos, los desarrolladores, hacer aplicaciones que no “molesten” tanto al procesador. Pongo molesten al procesador porque no se trata de no hacer funcionalidad por ahorrar energía, más quisiéramos, sino de intentar aprovechar al máximo las características de consumo de energía de Windows 7. Aquí tenemos algunas opciones.
· Evitar el uso de poolling, usar eventos, es decir en vez de están constantemente consultando una API para saber si un valor se ha establecido o se ha cambiado usar un evento de Windows para que se nos notifique ese cambio.
· No hacer poolling para conocer el estado energético del sistema, usar RegisterPowerSettingNotification
HPOWERNOTIFY WINAPI RegisterPowerSettingNotification( __in HANDLE hRecipient, __in LPCGUID PowerSettingGuid, __in DWORD Flags );
· En el caso de que no se pueda usar un evento nunca hacer polling más a menudo que un segundo.
Para el caso de que tengamos que hacer poolling constantemente Windows 7 incluye una API nueva para hacer temporizadores agrupados (coalescing timers). Veamos que son estos temporizadores agrupados.
En las dos gráficas podemos ver fechas de dos colores verdes y naranjas. Las fechas verdes que son constantes en el tiempo representan los eventos periódicos del temporizador del sistema, del kernel. Las flechas naranjas representan los eventos de temporizador personalizados, que se producen en el sistema, es decir temporizadores que los programadores programan. Lo que podemos ver entre estas dos graficas en que en la gráfica de abajo, que representa a Windows 7, podemos ver como los temporizadores naranjas se han alineado con los temporizadores verdes, es decir ahora los eventos se retrasan para agruparse cuando el sistema se levanta del estado de Ilde. Esto permite que entre evento y evento de color verder (kernel), el sistema se encuentre en completo idle y así que procesador pueda descansar durante ese periodo de tiempo, y solo cuando el procesador necesita ser “levantado” para ejecutar el evento del sistema (verde) aprovechamos y ejecutamos los eventos personalizados (naranjas) que tengamos pendientes.
Como podéis ver es una solución muy ingeniosa para optimizar nuestro escenario más habitual que como dijimos antes es el Idle del pc. Esto no significa que tengamos que cambiar todos nuestros temporizadores a temporizadores agrupados (coalescing timers), sino que si estamos haciendo alguna tarea en segundo plano, como monitorizar una web, o un dispositivo, podemos usar estos temporizadores para hacer nuestra aplicación más óptima con el consumo energético. En cualquier otro caso podemos seguir usando los temporizadores normales.
Este tipo de temporizadores es muy util para usarse en servicios de sistema que necesitan monitorizar algo en segundo plano.
Para crear un temporizador tenemos que llamar a la función CreateWaitableTimerEx
HANDLE WINAPI CreateWaitableTimerEx( __in_opt LPSECURITY_ATTRIBUTES lpTimerAttributes, __in_opt LPCTSTR lpTimerName, __in DWORD dwFlags, __in DWORD dwDesiredAccess );
Una vez llamado tenemos que llamar a la función SetWaitableTimerEx para inicializar el temporizador.
BOOL SetWaitableTimerEx( __in HANDLE hTimer, __in const LARGE_INTEGER *lpDueTime, __in LONG lPeriod, __in PTIMERAPCROUTINE pfnCompletionRoutine, __in LPVOID lpArgToCompletionRoutine, __in PREASON_CONTEXT WakeContext, __in ULONG TolerableDelay );
Quiero resaltar en esta función SetWaitableTimerEx, que el último parámetro ULONG TolerableDelay es el tiempo máximo en el que el temporizador puede ser retrasado para poder ser agrupado, así podemos indicar un tiempo por el cual el temporizador puede esperar sin problema, pasado este tiempo el sistema ejecutará el temporizador como un temporizador normal.
O podemos abrir un temporizador existente llamando a OpenWaitableTimer
HANDLE WINAPI OpenWaitableTimer( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in LPCTSTR lpTimerName );
Para los programadores administrador (.NET) incluyo código de una clase que implementa esta funcionalidad:
public sealed class CoalescingTimer : IDisposable { private IntPtr _timer; public CoalescingTimer(string name) { _timer = UnsafeNativeMethods.CreateWaitableTimerEx(IntPtr.Zero, name, 0, TIMER_MODIFY_STATE); } public event EventHandler Tick; public void Set(Int64 dueTime, int period, string reason, uint tolerableDelay) { UnsafeNativeMethods.POWER_REQUEST_CONTEXT reasonContext = new UnsafeNativeMethods.POWER_REQUEST_CONTEXT(); reasonContext.Version = UnsafeNativeMethods.POWER_REQUEST_CONTEXT_VERSION; reasonContext.Flags = UnsafeNativeMethods.POWER_REQUEST_CONTEXT_SIMPLE_STRING; reasonContext.SimpleReasonString = reason; _registeredDelegate = new CoalescingTimerProc(OnTimer); bool success = UnsafeNativeMethods.SetWaitableTimerEx(_timer, ref dueTime, period, _registeredDelegate, IntPtr.Zero, ref reasonContext, tolerableDelay); } private CoalescingTimerProc _registeredDelegate; private void OnTimer(IntPtr argument, uint timerLowValue, uint timerHighValue) { if (Tick != null) Tick(this, EventArgs.Empty); } const uint TIMER_MODIFY_STATE = 0x0002; public void Dispose() { UnsafeNativeMethods.CloseHandle(_timer); } } internal delegate void CoalescingTimerProc( IntPtr argument, uint timerLowValue, uint timerHighValue); internal sealed class UnsafeNativeMethods { [DllImport("kernel32.dll")] public static extern IntPtr CreateWaitableTimerEx( IntPtr lpSecurityAttributes, string timerName, uint flags, uint desiredAccess); [DllImport("kernel32.dll")] public static extern bool SetWaitableTimerEx( IntPtr hTimer, ref Int64 dueTime, int period, CoalescingTimerProc completionRoutine, IntPtr argument, ref UnsafeNativeMethods.POWER_REQUEST_CONTEXT reasonContext, uint tolerableDelay); [DllImport("kernel32.dll")] public static extern bool SetWaitableTimerEx( IntPtr hTimer, ref Int64 dueTime, int period, CoalescingTimerProc completionRoutine, IntPtr argument, ref UnsafeNativeMethods.POWER_REQUEST_CONTEXT_DETAILED reasonContext, uint tolerableDelay); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr handle); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct POWER_REQUEST_CONTEXT { public UInt32 Version; public UInt32 Flags; [MarshalAs(UnmanagedType.LPWStr)] public string SimpleReasonString; } }
Luis.