Efficient Use of Common In/Out Parameter Conventions

When using the Common In/Out Parameter Conventions for a given function, a performance gain might be realized by pre-allocating a buffer for the returned "pb" data, without querying that function for the exact size "cb" needed, dependent upon the specified data being input. Whenever a pre-allocated buffer is at least as big as the "cb" required by that function, a throughput savings is realized since the function is only called once, producing the returned data placed in the "pb" specified buffer.

The following example code demonstrates how a pre-allocated buffer can be used to provide a performance gain:

// Suppose that we have the same SomeFunction from the example in 
// the Common In/Out Parameter Conventions, that it is called 
// very repetitively by an application, and that we know that a
// buffer size of 50 bytes will handle the output for all but a
// few cases for the input data.

// Setup SomeFunction variables
PCCRL_CONTEXT pCrlContext; // Initialized elsewhere.
DWORD dwPropId;            // Initialized elsewhere.

// We can write code to pre-allocate a 50 byte buffer, and
// use that buffer in a call to SomeFunction which should produce the 
// desired output data most of the time. However, we need to be 
// prepared for a case where more than 50 bytes was required for the 
// output data. For that case, we use the cb returned by the failed 
// call to SomeFunction to allocate a bigger buffer. A provisional 
// second call to SomeFunction using the larger buffer is then made.

DWORD cb = 50;
PBYTE pb; // pointer to byte buffer

if (NULL == (pb = (PBYTE)malloc(cb))) // attempt to allocate cb bytes
   goto Log_Memory_Allocation_Error;
// Memory successfully allocated, continue.
if (!SomeFunction(pCrlContext, dwPropId, pb, &cb))
   // A failure occurred. Determine if the buffer was too small.
   if (GetLastError() != ERROR_MORE_DATA)
      goto Log_SomeFunction_Failure;
   // Getting here means that the buffer wasn't big enough and that cb
   // has been updated with the size of the buffer required.
   if (NULL == (pb = (PBYTE)malloc(cb))) //attempt to allocate cb bytes
      goto Log_Memory_Allocation_Error;
   // Memory successfully allocated, continue.
   if (!SomeFunction(pCrlContext, dwPropId, pb, &cb))
      goto Log_SomeFunction_Failure;

// Getting here indicates a successful call to SomeFunction.
// pb points to the output data, cb contains the size of that data.

; // Process the data. Note that cb is updated with the actual size of 
  // the data returned, and it is this size that should be used when 
  // processing the data.

free(pb); // deallocate the buffer when done processing the data

return; // if appropriate


Log_SomeFunction_Failure :
   ; // Handle as desired
   return; // if appropriate
Log_Memory_Allocation_Error:
   ; // Handle as desired
 

The above example code can be rewritten into loop form, where the call to SomeFunction appears only once which reduces the amount of code generated when compiled. This is shown below:

BYTE cTryAgain = 2; // Used to make sure there is only one retry
                    // if the pre-allocated buffer was not big enough.

while (cTryAgain-- ){ // We break out for success.
   if (NULL == (pb = (PBYTE)malloc(cb))) 
      goto Log_Memory_Allocation_Error;
   if (SomeFunction(pCrlContext, dwPropId, pb, &cb))
      break; // Success !
   free(pb); // If break not taken, function failure occurred.
             // Free the buffer on failure.
   // Check to see if the buffer was not big enough.
   if (GetLastError() != ERROR_MORE_DATA) 
      // Some other failure occurred. Exit loop and log it.
      goto Log_SomeFunction_Failure;

   // In the case of ERROR_MORE_DATA, we stay in the loop, and 
   // cb has been updated with the size of the buffer required.

}  // ends loop

// If loop exited because of success, process the output data.

; // Process the data. Note that cb is updated with the actual size of 
  // the data returned, and it is this size that should be used when 
  // processing the data.


free(pb); // Free the data buffer after the data has been processed.

   return; // if appropriate


Log_SomeFunction_Failure :
   ; // Handle as desired
   return; // if appropriate
Log_Memory_Allocation_Error:
   ; // Handle as desired
 

Another method of obtaining performance gains is to use alloca to allocate memory from the stack, instead of using malloc to allocate memory from the heap. Since the buffer is on the stack, free does not need to be called when finished using the buffer. When using alloca, it should be noted that the data buffer must be used locally to the calling function since upon function return, the buffer is automatically deallocated by the stack pop. Also, be aware that each call to alloca from the same function uses more stack. Excessive use may cause a stack overflow in very repetitive cases.