Simplifying ArrayHandle Internals and ArrayTransfer
While coding up several fancy array handles, I ran into a problem that is easy to miss but is nevertheless a bug. An ArrayTransfer for a fancy array handle typically delegates its behavior to other ArrayHandle objects that do the actual management of data between control an execution environments. However, the fancy ArrayHandle is also trying to manage data transfers for the ArrayTransfer and therefore sometimes suppresses “unnecessary” calls. In particular, if PrepareForInput is called, the ArrayTransfer passes the request to the delegate ArrayHandle and stores a const portal. But if the user then calls PrepareForInPlace, the fancy ArrayHandle assumes the data already exists in the execution environment and requests a non-const portal, which the ArrayTransfer does not have. Errors ensue.
In thinking about this a bit, I believe the correct solution is to simplify the internals of the ArrayHandle and push some of this responsibility to the associated classes while simplifying the interface for ArrayTransfer. This will involve removing the feature of storing a “user” portal not created by the storage and requiring the ArrayTransfer to manage what data is valid in the execution environment. The interface to ArrayTransfer will be changed to mirror better the ArrayHandle interface, which should simplify its implementation in the long run.
Removing UserPortal from ArrayHandle internals
ArrayHandle is generally designed to manage its own data. However, there is an important alternate case where we want to accept externally created data and make it accessible from the ArrayHandle. This was managed by having a special “user portal” field in the ArrayHandle that could hold a reference to an array portal for data actually created by the user.
This adds a bit of complication to the ArrayHandle internals since it always has to check the condition of having a user portal or data allocated by the storage (or neither). That in itself is not so bad since the single implementation in the ArrayHandle works for all data types.
The real problem is that this setup has inadvertently complicated the interface to the ArrayTransfer. When preparing data for input, the ArrayTransfer needs separate implementations for the user portal and from the Storage. User portals are also not used as much as originally expected. It was thought that fancy ArrayHandles would use user portals to pass in their data, but it turns out that managing this in a storage class is easier.
Consequently, the user portal and all associated logic within ArrayHandle is to be removed. This will initially remove the ability to pass in pointers to ArrayHandle. However, this only really works well with the basic storage, so the basic storage will be updated to accept an array in its constructor and manage this with a simple flag as to whether the array should be deleted when released.
Simplified ArrayTransfer interface
The current interface to ArrayTransfer is actually a little strange. Preparing an array for the execution environment is actually a two-step process. ArrayHandle first calls a LoadDataFor* or AllocateArrayForOutput and then later calls GetPortalExecution or GetPortalConstExecution to get the array portal. (The ArrayManagerExecution class has a similar interface.) This two-step process is in place so that the ArrayHandle can remember whether data is valid in the execution environment and skip the Load or Allocate methods when possible.
However, in addition to making for a strange API, it requires the ArrayTransfor to hold state. Furthermore, it introduces bugs when the ArrayTransfer wants to manage the data differently than the ArrayHandle (as mentioned in the intro).
Thus, the API is going to be changed to have the methods GetNumberOfValues, PrepareForInput, PrepareForInPlace, PrepareForOutput, Shrink, and ReleaseResourcesExecution. These will behave in the same way as the associated ArrayHandle methods except that they will all take a Storage object as a parameter (and will not take a Device object). The RetrieveOutputData method will also remain and behave in the same way that it does now.
In addition to being easier to remember, these changes will allow several ArrayTransfer implementations to become stateless. It will require the ArrayTransfer to more carefully manage when data actually needs to move from control to execution environment, but this will likely only need to be implemented in the default ArrayTransfer anyway. (Specialized ArrayTransfers will probably have delegate ArrayHandle objects to do this management for them.)
- When implementing this I had to make a couple changes to the idea. First, the ArrayTransfer is not stateless. It has to hold a reference to a Storage object to manage when getting a non-const portal after a const portal. Second, the ArrayPortal still manages whether the data is valid in the execution environment. It passes a flag to the PrepareForInput and PrepareForInPlace methods of the ArrayTransfer to signal when data needs to be transferred to the execution environment and when it should not. --Kmorel (talk) 18:07, 1 May 2015 (EDT)
Acknowledgements
Sandia National Laboratories is a multi-program laboratory managed and operated by Sandia Corporation, a wholly owned subsidiary of Lockheed Martin Corporation, for the U.S. Department of Energy's National Nuclear Security Administration under contract DE-AC04-94AL85000.
SAND2015-3564 O