# BatchProcessor and BatchDesc

The ForEach documentation describes how to use `BatchProcessor` and `BatchDesc` with `ParallelForEach` and `SerialForEach`. They can also be used outside of the ForEach classes to implement double buffered DMA.

# BatchDesc

A batch descriptor (`BatchDesc` class) is used to describe up to `BatchDesc::sMaxArrays` arrays of data. Each `Array` of data has to be a homogeneous, contiguous, and sixteen byte aligned allocation of memory on the heap. (So it can be DMA'd, see the dma documentation). This descriptor itself must also be sixteen byte aligned and allocated on the heap.

# BatchProcessor

The batch processor is used to transfer slices of the arrays specified by a batch descriptor into a working localized working buffer. This makes it possible for SPU jobs to work on larger amounts of data than what would normally fit in the local store. It does require that the processing being done not rely on being able to randomly access array elements, since only a limited sub set of each array is available at any given time. You also cannot rely on the array being processed in any given order if you're using ParallelForEach to distribute the work (see the foreach documentation).

When running on an SPU the batch processor will double buffer DMA transfers. This should greatly reduce the time spend waiting for data to arrive from main memory.

# Example Code

## Example Setup Code

        #include "job_util/batch_desc.h"

        using namespace EA::Jobs;

        {
            // setup entry point
            #if defined EA_PLATFORM_XENON || defined EA_PLATFORM_WINDOWS
                EntryPoint batchProcessorJobEntryPoint = EntryPoint(&YourBatchProcessorJob);
            #elif defined EA_PLATFORM_PS3
                EntryPoint batchProcessorJobEntryPoint = LoadJob(SPU_JOB_PDC, "your_batch_processor.job");
            #endif

            const size_t numElements = 160;

            // allocate a 16 byte aligned array a numElements ints on the heap
            // total array size must be a multiple of 16 bytes.
            Array<int> s(numElements);

            // setup data to be processed.
            for (size_t j = 0; j < numElements; ++j)
                s[j] = static_cast<int>(j);

            BatchDesc bd(numElements);

            // Set the first array to modify the data inplace.
            bd.SetArray<int>(0, &s[0], &s[0]);

            // Create and scheduler a job to process the data.
            Job job;
            job.SetEntryPoint(batchProcessorJobEntryPoint);
            job.SetParam(0, reinterpret_cast<uint32_t>(&bd));

            mJobScheduler.Add(job);

            // Wait for it to finish
            job.WaitOn();

        }

Example Job Code:

        #include "job_util/batch_processor.h"
        #include "EABase/eabase.h"

        using namespace EA::Jobs;

        #if defined EA_PLATFORM_PS3_SPU
            int main
        #else
            void YourProcessorEachJob
        #endif
            (uint32_t descEa, uint32_t param1, uint32_t param2, uint32_t param3)
        {
            (void)param1;
            (void)param2;
            (void)param3;

            size_t const bufferSize = 128 * 1024;
            #if defined EA_PLATFORM_PS3_SPU
                EA_PREFIX_ALIGN(16) char buffer[bufferSize] EA_POSTFIX_ALIGN(16);
            #else
                char *buffer = 0;
            #endif

            // BatchProcessor ctor will DMA the descEa (if we're on SPU)
            // This is job index 0, since we're only using one job to process the data.
            // The buffer and bufferSize to use when sub-setting the arrays
            // If buffer is 0 and we're not on the SPU the batch processor will
            // avoid copying the data into a buffer and directly access it instead.
            BatchProcessor bp(descEa, 0, buffer, bufferSize);

            // keep fetching sub-sections of the arrays until all have been processed
            for (bp.First(); ! bp.Done(); bp.Next())
            {
                // process the sub-section
                int * data = bp.GetArray<int>(0);
                for (int i = 0; i < bp.GetNumElements(); ++i)
                    data[i] = ~data[i];
            }
        }

# How it works

Given that you have an independent operation you want to apply to many sets of arguments we can divide the arguments up into smaller groups and process them in parallel. There are limitations on the data imposed by the requirements of DMAing it to the SPU:

- Data must start on a 16 byte aligned address
- Data cannot be on the stack
- The total size of the data must be a multiple of 16 bytes
- Some multiple of the data element size that fits into the SPU local store most also be a multiple of 16 bytes

For example, if you have three arguments in separate arrays (see diagram) we can divide the data up into smaller arrays for processing:

The GetArray API is used to get at the data to be processed. This makes the use of a buffer transparent to the algorithm processing the data.

In the case where a buffer is used (mainly on the SPU) we double buffer the DMAs to reduce DMA overhead. This results in half of the buffer being used to store data the is currently being processed while the other half is being exchanged for the data to process in the next iteration of the loop.

Since the location of the data to process moves around within the buffer (based on which half is ready to process) you have to call into GetArray each time through the loop; i.e. after calling BatchProcessor::Next().