Optimizing Test Resources with Read/Write Bursts

This tutorial shows how to improve the performance of the Test Resources  (files on the host machine accessed from your embedded unit tests) using the read/write bursts.

We will start with a basic demo project showing the test resources and will update it to use bursts for better performance. Before you begin, follow our Embedded Test Resources Tutorial to create a basic project that uses test resources. Alternatively, you can clone the sample project from our Github repository.

  1. Open the project created per the Embedded Test Resources Tutorial and locate the GenerateTestData() function that generates the inputs.dat and outputs.dat files. See how the function produces a chunk of data an then sends it to the host by calling TRMWriteFile():you can verify this by stepping into TRMWriteFile() and seeing that it is simply a wrapper around the RunBlockingFastSemihostingCall() function:
  2. Now we will modify the GenerateTestData() function to use the burst API instead of calling TRMWriteFile(). Add the following calls before the main loop:
        auto hInputsBurst = TRMBeginWriteBurst(hInputs);
        auto hOutputsBurst = TRMBeginWriteBurst(hOutputs);

    End the bursts after the main loop and before closing the files:

        auto hInputsBurst = TRMBeginWriteBurst(hInputs);
        auto hOutputsBurst = TRMBeginWriteBurst(hOutputs);

    Reduce the size of buf1 and buf2 to 32 change the loop count from 256 to 1024 * 1024 / sizeof (buf1):

        float buf1[32], buf2[32];
        for (int block = 0; block < 1024 * 1024 / sizeof(buf1); block++)
        {
            //...
        }

    Finally, replace the calls to TRMWriteFile() with TRMWriteFileCached() and change the first argument of each call to the burst handle:

  3. Now instead of waiting for the host to receive the data, TRMWriteFileCached() will quickly copy it to the semihosting buffer and will return control to GenerateTestData(). VisualGDB will then read the previous chunk of data while the embedded code is computing the next one:
  4. You can control the size of the semihosting buffer via VisualGDB Project Properties -> Embedded Frameworks -> Semihosting -> Buffer Size:
  5. Try deleting the inputs.dat and outputs.dat files from the TestResources directory and run a regular debug session again (with F5). Observe how they will get re-created faster, as long as you are using a large semihosting buffer.
  6. Now we will show how to use read bursts to speed up the reading of data by the test program. Locate the SineTest method that reads the data from the test files.
  7. Read bursts require declaring a temporary buffer for each burst where VisualGDB will prefetch the data from the file.  Add the following code before the main loop:
        char workArea1[4096], workArea2[4096];
        auto hInputsBurst = TRMBeginReadBurst(hInputs, workArea1, sizeof(workArea1));
        auto hOutputsBurst = TRMBeginReadBurst(hOutputs, workArea2, sizeof(workArea2));

    End the bursts after the main loop:

        TRMEndReadBurst(hInputsBurst);
        TRMEndReadBurst(hOutputsBurst);

    As soon as you call TRMBeginReadBurst(), VisualGDB will start filling the work area with the contents of the read file and will continue until the buffer is full. To read the data from the buffer, automatically waiting for it to be filled, replace the calls to TRMReadFile() with TRMReadFileCached():

            ssize_t done = TRMReadFileCached(hInputsBurst, buf1, sizeof(buf1), 0);

    The final version of the code should look like shown below:

  8. Run the test and make sure it succeeds:
  9. The read bursts can be further optimized by using the TRMBeginReadFileCached()/TRMEndReadFileCached() instead of TRMReadFileCached(). Instead of copying the data from the work area into buf1 or buf2, those calls will return a pointer inside the work area that contains the data fetched by VisualGDB. You can access it directly from your test logic and call TRMEndReadFileCached() to release it, so that VisualGDB could rewrite it with the next portion:
        for (;;)
        {
            size_t done1, done2;
            float *pBuf1 = (float *)TRMBeginReadFileCached(hInputsBurst, &done1, 1);
            float *pBuf2 = (float *)TRMBeginReadFileCached(hOutputsBurst, &done2, 1);
            if (!pBuf1 || !pBuf2)
            {
                CHECK(!pBuf1 && !pBuf2);
                break;
            }
            size_t todo = std::min(done1, done2);
            CHECK(todo > 0);
            for (int i = 0; i < (todo / sizeof(*pBuf1)); i++)
            {
                float error = fabsf(pBuf2[i] - sinf(pBuf1[i]));
                CHECK(error < 0.001F);
            }
            TRMEndReadFileCached(hInputsBurst, pBuf1, todo);
            TRMEndReadFileCached(hOutputsBurst, pBuf2, todo);
            total += todo;
            g_Progress = total;
        }
  10. Try running the test now, it will complete slightly faster than the previous version:You can find a detailed overview of the Test Resource Manager API in the official documentation page.