Chapter 13: C Inline Assembler and Windows Time Functions

image from book  Download CD Content

A significant part of applications that work in the Windows family of operating systems use time functions and timers. Such functions are necessary for real-time operations, work with device drivers, and multimedia applications. Note that Windows operating systems are not real-time ones. This means that it is very difficult to execute operations that depend on precise time intervals or relate to them. Those programmers who have to write applications that work with very short time intervals encounter many problems. With regard to operations with relatively long intervals (a few seconds or a few minutes), there are usually no problems. Things are much more complicated for applications operating with milliseconds or tens of milliseconds . Delays, which appear during processing audio and video data, and inaccurate sampling when working with physical devices lead to losses and distortions of data, thus making the applications inoperable.

All problems related to time dependencies can be divided into two categories: the difficulty of executing a particular algorithm within a particular time and the impossibility of measuring a time interval with the required precision while the algorithm is quite operable.

In the first case, the best option is to use assembly language. Implementing such a task in pure C++ is not always possible. A well-designed algorithm written in the assembler will always be faster than its analog coded in C++. Therefore, using the assembler often makes it possible to fit both crucial pieces of code and a whole application to reasonable time limits.

Setting a time interval precisely is a more complicated task. As developers at Microsoft say, it is almost impossible to obtain a time interval shorter than 50 microseconds in the Windows operating system. This is because of the structure of the operating system and time dependencies among its subsystems.

Nevertheless, there are solutions that allow a programmer to obtain rather short intervals. Assembly code is also useful in this case.

To work precisely with short time intervals, the GetTickCount WIN API function is often used. It returns the number of milliseconds elapsed since the Windows startup. Generally, one could say that this function computes the time between its subsequent calls. Using GetTickCount is very useful in the following situations:

  • When you need to compute the interval between two events. These could be the beginning and end of execution of a particular code fragment or, for example, the beginning and end of receiving data in a data processing system.

  • When you need to specify a certain time interval for execution of a program.

The effectiveness of this function significantly increases when the assembler is used for implementing such tasks . We will illustrate this with examples.

The first example demonstrates the use of the GetTickCount function to determine the time of execution of a for loop that computes the sine of monotonically increasing floating-point numbers . The for loop is implemented in C++. The source code of the application is shown in Listing 13.1.

Listing 13.1: Computing the time of execution of a for loop with C++ .NET statements
image from book
 // GetTickCount_plus_ASM.cpp : Defines the entry point for the console  // application  #include "stdafx.h"  #include <windows.h>  #include <math.h>  int _tmain(int argc, _TCHAR* argv[])  {   float i1 = 0.0;   float ires = 0.0;   const float one = 1.0;  printf("Comparison ASM and C++ speed of execution (pure C++) \n");  DWORD dwStart = GetTickCount();  for (int cnt = 0; cnt < 1000000; cnt++)    ires = sin(++i1);  DWORD dwInterval = GetTickCount()   dwStart;  printf("\nSine = %.3f \n", ires);  printf("Operation is completed through %d ms!\n", (int)dwInterval);  getchar();  return 0;  } 
image from book
 

To compute the time interval in milliseconds, a simple expression is used that evaluates the difference between the readings of the counter after the for loop completes and at the start of the application:

 DWORD dwInterval = GetTickCount()   dwStart 

The window of the application is shown in Fig. 13.1.

image from book
Fig. 13.1: Window of an application that displays the time of execution of a for loop

Note the time of execution of the loop; we will need it later to compare it with the result of a modified version of this application.

Rewrite the for loop in the assembler. The source code of the modified application appears as shown in Listing 13.2.

Listing 13.2: Replacing the for loop with an assembly block
image from book
 // Time of calculating with ASM.cpp : Defines the entry point for the  // console application  #include "stdafx.h"  #include <windows.h>  #include <math.h>  int _tmain(int argc, _TCHAR* argv[])  {   float il = 0.0;   float ires = 0.0;  printf("Comparison ASM and C++ speed of execution (ASM block) \n");     DWORD dwStart = GetTickCount();  _asm {        mov   ECX, 1000000        finit    next:        fld1        fadd  DWORD PTR i1        fstp  DWORD PTR i1        fld   DWORD PTR i1        fsin        fstp  DWORD PTR ires        dec   ECX        jnz   next        fwait       };  DWORD dwInterval = GetTickCount()   dwStart;  printf("\nSine = %.3f \n", ires);  printf("Operation is completed through %d ms!\n", (int)dwInterval);  getchar();  return 0;  } 
image from book
 
image from book
Fig. 13.2: Window of an application that demonstrates the performance of a loop implemented with assembly commands

The window of the application is shown in Fig. 13.2.

Compare the time of execution with that from the previous example. The assembly block is executed much faster. Both results were obtained on a computer with Pentium IV 2.4 GHz. On computers with other processors and other system parameters, the results will look different, but the relationship will be the same: The assembler version of the for loop is executed faster!

The GetTickCount function can be used for repeatedly executing a program with certain time intervals. Usually, such programs are generators of signals of a particular shape or electronic circuit simulators in modeling applications.

The next example demonstrates computing ten values of sine of a floating-point number with time intervals of 5 msec. The source code of the application is shown in Listing 13.3.

Listing 13.3: Computing ten values of the sine with time intervals of 5 msec
image from book
 // SINE_X_5MS_ASM.cpp : Defines the entry point for the console  // application  #include "stdafx.h"  #include <windows.h>  #include <math.h>  int _tmain(int argc, _TCHAR* argv[])  {   float f1[10] = {1.5, 4.1, 0.7, 2, 45.12, 21.7, 9.65, 11.3, 0.7, 77};   float fsin[10];   DWORD dwStart;  printf("Calculation SIN(x) each 5 milliseconds with ASM\n\n");  for (int cnt = 0; cnt < 10; cnt++)  {   dwStart = GetTickCount();   while ((GetTickCount()   dwStart)<=5);   _asm{       mov    ECX, 10       lea    ESI, DWORD PTR f1       lea    EDI, DWORD PTR fsin       finit   next:       fld1       fadd   DWORD PTR [ESI]       fstp   DWORD PTR [ESI]       fld    DWORD PTR [ESI]       fsin       fstp   DWORD PTR [EDI]       dec    ECX       jz ex       add    ESI, 4       add    EDI, 4       jmp    next  ex:       fwait      };  }  printf("\nfl:    ") ;  for (int cnt = 0; cnt < 10; cnt++)    printf("%.2f ", f1[cnt]);  printf("\n\nSIN (f1) : ") ;  for (int cnt = 0; cnt < 10; cnt++)    printf ("%.2f ", fsin [cnt]);  getchar ();  return 0;  } 
image from book
 

Note this very important point. When computing time intervals, the precision of computation also depends on the time of execution of the statements that compute (this is true not only for the GetTickCount function). For relatively long intervals (compared to the time of execution of commands), the time of execution of statements does not influence precision significantly because it is a hundred times less than those intervals.

The window of the application is shown in Fig. 13.3.

image from book
Fig. 13.3: Window of an application that computes the sine of a number every 5 milliseconds

To work with very short time intervals (hundreds of milliseconds or shorter), use special techniques and WIN API functions such as QueryPerformanceCounter and QueryPerformanceFrequency . These are beyond the scope of this book.

Operations that should be performed with certain fixed time intervals can be implemented with a timer. The timer is a scheduled event created by the operating system with specified intervals. The precision of the system timer is a little less than that computed with the GetTickCount function, but it is satisfactory for many applications. The timer can be used in either of these two ways:

  • Create the WM_TIMER message and write a handler for this event.

  • Write a callback function for handling the timer event.

You can create or change the system timer with the SetTimer function. Having used the timer, delete it with the KillTimer function. The parameters of these functions are thoroughly covered in special literature, so we will not concentrate on them. Rather, we will proceed with an example that handles the timer event with a WM_TIMER handler. In this example, the Windows system timer is used for performing a mathematical operation: more precisely, for extracting the square root from a floating-point number.

To generate the application frame, use the C++ .NET 2003 Application Wizard and generate a 32-bit procedure-oriented application. Modify the template to use the system timer function (the changes are in bold in the listing below).

The square root is computed in the assembly block. The source code of the application is shown in Listing 13.4.

Listing 13.4: Using Windows system timers
image from book
 // GL13_TEST_TIMER.cpp : Defines the entry point for the application  #include "stdafx.h"  #include <stdio.h>  #include "GLl3_TEST_TIMER.h"  #define MAX_LOADSTRING 100  char buf[32];   float f1 = 0;   float fres = 0;   int nTimer;   int written = 0;  // Global Variables:  HINSTANCE hInst;                     // Current instance  TCHAR szTitle[MAX_LOADSTRING];       // The title bar text  TCHAR szWindowClass[MAX_LOADSTRING]; // The main window class name  // Forward declarations of functions included in this code module:  ATOM                 MyRegisterClass (HINSTANCE hInstance);  BOOL                 InitInstance (HINSTANCE, int);  LRESULT              CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);  LRESULT              CALLBACK About (HWND, UINT, WPARAM, LPARAM);  int APIENTRY _tWinMain(HINSTANCE hInstance,                       HINSTANCE hPrevInstance,                       LPTSTR    lpCmdLine,                       int       nCmdShow)  {  // TODO: Place code here.  MSG msg;  HACCEL hAccelTable;  // Initialize global strings  LoadString (hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);  LoadString (hInstance, IDC_GL13_TEST_TIMER, szWindowClass, MAX_LOADSTRING);  MyRegisterClass (hInstance) ;  // Perform application initialization:  if (! InitInstance (hInstance, nCmdShow))    {     return FALSE;    }  hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GL13_TEST_TIMER);  // Main message loop:  while (GetMessage(&msg, NULL, 0, 0))   {    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))     {      TranslateMessage(&msg);      DispatchMessage(&msg);     }  }    return (int) msg.wParam;  }  // FUNCTION: MyRegisterClass()  // PURPOSE: Registers the window class.  // COMMENTS:  // This function and its usage are only necessary if you want this code  // to be compatible with Win32 systems prior to the 'RegisterClassEx'  // function that was added to Windows 95. It is important to call this  // function so that the application will get 'well formed' small icons  // associated with it.  ATOM MyRegisterClass(HINSTANCE hInstance)   {    WNDCLASSEX wcex;    wcex.cbSize = sizeof(WNDCLASSEX);    wcex.style         = CS_HREDRAW  CS_VREDRAW;    wcex.lpfnWndProc   = (WNDPROC)WndProc;    wcex.cbClsExtra    = 0;    wcex.cbWndExtra    = 0;    wcex.hInstance     = hInstance;    wcex.hIcon         = LoadIcon(hInstance, (LPCTSTR)IDI_GL13_TEST_TIMER);    wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);    wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW   2);    wcex.lpszMenuName  = (LPCTSTR)IDC_GL13_TEST_TIMER;    wcex.lpszClassName = szWindowClass;    wcex.hIconSm       = LoadIcon(wcex. hInstance, (LPCTSTR) IDI_SMALL) ;    return RegisterClassEx (&wcex);    }  // FUNCTION: InitInstance (HANDLE, int)  // PURPOSE: Saves instance handle and creates main window  // COMMENTS:  // In this function, we save the instance handle in a global variable and  // create and display the main program window.  BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)   {     HWND hWnd;     hInst = hInstance; // Store instance handle in our global variable     hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,                                                 CW_USEDEFAULT, 0,                                                 CW_USEDEFAULT, 0,                                                 NULL, NULL, hInstance,                                                 NULL);     if (!hWnd)     {        return FALSE;     }     ShowWindow(hWnd, nCmdShow);     UpdateWindow(hWnd);     return TRUE;  }  // FUNCTION: WndProc (HWND, unsigned, WORD, LONG)  // PURPOSE: Processing messages for the main window  // WM_COMMAND - Process the application menu.  // WM_PAINT - Paint the main window.  // WM_DESTROY - Post a quit message and return.  LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  {    int wmId, wmEvent;    PAINTSTRUCT ps;    HDC hdc;  RECT rc;   #define TIMER1 1  switch (message)     {      case WM_COMMAND:         wmId    = LOWORD (wParam);         wmEvent = HIWORD (wParam);  // Parse the menu selections:         switch (wmId)          {           case IDM_ABOUT:             DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);             break;           case IDM_EXIT:             DestroyWindow(hWnd);             break;           default:             return DefWindowProc(hWnd, message, wParam, lParam);           }         break;     case WM_PAINT:      hdc = BeginPaint(hWnd, &ps);  GetClientRect(hWnd, &rc);  // TODO: Add any drawing code here    TextOut(hdc,  (  rc.right-rc.left)/4, (rc.bottom-rc.top)/2, buf, written);  EndPaint(hWnd, &ps);    break;  case WM_CKEATE:   nTimer = SetTimer (hWnd, TIMER1, 5000, NULL) ;  break;  case WM_TIMER:  switch(wParam)  case TIMER1:   {   _asm{   finit   fld1   fadd  DWORD PTR f1   fstp  DWORD PTR f1   fld   DWORD PTR f1   fsqrt   fstp  DWORd PTR fres   fwait   };   written = sprintf(buf, "Value=%.3f, SQRT=%.3f", f1, fres)   hdc = GetDC(hWnd) ;   GetClientRect(hWnd, &rc);   InvalidateRect(hWnd, &rc, FALSE);   ReleaseDC(hWnd, hdc);  break;     }    break;  case WM_DESTROY:  KillTimer(hWnd, nTimer);  PostQuitMessage (0);    break;  default:    return DefWindowProc (hWnd, message, wParam, lParam) ;   }  return 0;  // Message handler for about box.  LRESULT CALLBACK About (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  {    switch (message)     {      case WM_INITDIALOG:         return TRUE;      case WM_COMMAND:         if (LOWORD(wParam) == IDOK   LOWORD (wParam) = IDCANCEL)          {           EndDialog(hDlg, LOWORD(wParam));            return FALSE;          }          break;     }  } 
image from book
 

We will explain this listing with the declarations of variables. The following variables are declared at the beginning of the code:

  • char buf [32] ” a character buffer for storage of a floating-point number converted to a string which will be subsequently displayed with the TextOut function

  • float f1 ” a positive floating-point number, from which the square root is extracted

  • float fres ” the result of square root extraction

  • int nTimer ” the handle of a new timer

  • int written ” the number of characters written to the buffer during conversion of the number to a string

To create a system timer, the SetTimer function is used:

 nTimer = SetTimer(hWnd, TIMER1, 5000, NULL) 

This statement creates a system timer with the TIMER1 identifier that triggers an event every 5 seconds. The nTimer handle is used by the KillTimer function to destroy the timer after the application terminates. The NULL parameter indicates the absence of a callback function for handling the event (in this case, the WM_TIMER handler is used).

The window of the application is shown in Fig. 13.4.

image from book
Fig. 13.4: Window of an application that demonstrates computing a square root in a WM_TIMER handler

Note that the code of the WM_TIMER should be as short as possible. The smaller the interval between triggering events, the stricter this limitation will be. In the preceding example, this interval is equal to 5 seconds, which is sufficient both for fast mathematical computation and for processing the data before displaying them. However, if the interval between triggering the timer is short (for example, 10 milliseconds), the hardware performance and the quality of the code affect the situation.

With short intervals, it is not advisable to write a handler that displays or saves the processed data in a file. These operations take too much time that is comparable to the interval of triggering timer events. It is likely that the next event occurs while the data is still being processed . In such a case, the application will behave unpredictably, and the data might be lost.

Be sure to take into account another factor. The system triggers the WM_TIMER message only when there are no unprocessed messages in the queue. You could describe this by saying that all other messages (except WM_PAINT ) have priorities higher than that of the timer messages.

You can optimize the last application if you try to move a few or all data displaying operations from the WM_TIMER handler to, for example, the WM_PAINT handler. After analysis of the source code, it turns out that the

 written = sprintf(buf, "Value = %.3f, SQRT = %.3f", f1, fres) 

statement can be moved from the WM_TIMER handler to the WM_PAINT handler:

 . . . case WM_PAINT:   hdc = BeginPaint(hWnd, &ps);   GetClientRect(hWnd, &rc);  // TODO: Add any drawing code here   written = sprintf(buf, "Value = %.3f, SQRT = %.3f", f1, fres);   TextOut(hdc, (rc.right-rc.left)/4, (rc.bottom-rc.top)/2, buf, written);   EndPaint(hWnd, &ps);   break;  . . . 

The next example is similar to the previous, but it has one important feature: It uses a callback function for handling the timer event. The source code of the application is shown in Listing 13.5.

Listing 13.5: Using a callback function for handling a timer event
image from book
 // MyTimer_with_ASM.cpp : Defines the entry point for the application  #include "stdafx.h"  #include "MyTimer_with_ASM.h"  #include <stdio.h>  #define MAX_LOADSTRING 100  char buf[64];   float f1 = 0;   float fres = 0;   int nTimer;   int written = 0;  // Global Variables:  HINSTANCE hInst;                     // Current instance  TCHAR   szTitle[MAX_LOADSTRING];     // The title bar text  TCHAR szWindowClass[MAX_LOADSTRING]; // The main window class name  // Forward declarations of functions included in this code module:  ATOM                 MyRegisterClass(HINSTANCE hInstance);  BOOL                 InitInstance(HINSTANCE, int);  LRESULT CALLBACK     WndProc(HWND, UINT, WPARAM, LPARAM);  LRESULT CALLBACK     About(HWND, UINT, WPARAM, LPARAM);  int APIENTRY _tWinMain(HINSTANCE hInstance,                       HINSTANCE hPrevInstance,                       LPTSTR    lpCmdLine,                       int       nCmdShow)  {  // TODO: Place code here.  MSG msg;  HACCEL hAccelTable;  // Initialize global strings  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);  LoadString(hInstance, IDC_MYTIMER_WITH_ASM, szWindowClass, MAX_LOADSTRING) ,  MyRegisterClass(hInstance);  // Perform application initialization:  if (!InitInstance (hInstance, nCmdShow))   {    return FALSE;   }  hAccelTable = LoadAccelerators (hInstance, (LPCTSTR) IDC_MYTIMER_WITH_ASM) ;  // Main message loop:  while (GetMessage (&msg, NULL, 0, 0))   {    if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg))     {      TranslateMessage (&msg) ;      DispatchMessage (&msg) ;     }  }  return (int) msg.wParam;  }  //   FUNCTION: MyRegisterClass ()  //   PURPOSE: Registers the window class.  //   COMMENTS:  //   This function and its usage are only necessary if you want this code  // to be compatible with Win32 systems prior to the 


Visual C++ Optimization with Assembly Code
Visual C++ Optimization with Assembly Code
ISBN: 193176932X
EAN: 2147483647
Year: 2003
Pages: 50
Authors: Yury Magda

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net