Default accelerator in C++ AMP

I have blogged previously about the accelerator and accelerator_view classes in C++ AMP and in this post I want to focus on some specific aspects of its usage.

The notion of default

You can write a lot of useful C++ AMP code without ever needing to learn or use the accelerator and accelerator_view types. That is because when the C++ AMP runtime is initialized (the first time you use some of our types), it picks a default accelerator (i.e. a default target device) where all subsequent operations (such as array allocations and parallel_for_each invocations) take place.

The algorithm for picking a default accelerator should be treated as an implementation detail, but here it is anyway: If you are performing GPU debugging then we pick the accelerator you choose in the project properties, by default the emulator accelerator for debugging (device_path="direct3d/ref" and description=“Software Adapter”); otherwise the environment variable CPPAMP_DEFAULT_ACCELERATOR is examined for a device path. Failing any of those conditions being true, the default is set to a non-emulated device if one exists. If there is more than one, the device with the largest dedicated_memory is chosen. If there is more than one with equal memory, then the one where has_display is false becomes the default one.

It is easy to check what accelerator was picked as the default by simply using the default constructor to initialize an accelerator (which creates the default accelerator)… if you catch my drift.

Setting the default programmatically

You can change the runtime’s default accelerator programmatically only once per process activation, i.e. after you set a default yourself, it cannot be changed again until the process exits (= runtime exits). You can only set a default before you perform any operation that results in the runtime using the default that it picked.

You set the default by calling accelerator::set_default() . Typically you would enumerate all accelerators on the system and then choose the one you want as default, e.g.:

 bool pick_accelerator()
{
  std::vector<accelerator> accs = accelerator::get_all();
  accelerator chosen_one;

  auto result = 
    std::find_if(accs.begin(), accs.end(), [] (const accelerator& acc)
    {
      return !acc.is_emulated && 
              acc.supports_double_precision && 
             !acc.has_display;
    });

  if (result != accs.end())
    chosen_one = *(result);

  std::wcout << chosen_one.description << std::endl;

  bool success = accelerator::set_default(chosen_one.device_path);
  return success;
}

accelerator_view

If during the process lifetime you want to change which accelerator you use, or you want different parts of your code to use different accelerators (e.g. to take advantage of multiple accelerators simultaneously), then you need to get an accelerator_view and pass that to the respective API, i.e. array constructor or parallel_for_each dispatch. This is very easy through the default_view property on the accelerator (and now you know how to get one of those), e.g.

 accelerator_view av = accelerator(accelerator::direct3d_warp).default_view;

For completeness I should say that you can also call the create_view method on accelerator to get an accelerator_view, as discussed in our blog post on accelerator_view queuing_mode. Since I am truly aiming for completeness, you can also get one of these objects through our DirectX Interop APIs.

 

Your feedback as always is welcome below or in our MSDN forum.