Utilisation des objets accelerator et accelerator_view
Vous pouvez utiliser les classes accelerator et accelerator_view pour spécifier le périphérique ou l'émulateur sur lequel exécuter votre code C++ AMP. Un système peut avoir plusieurs périphériques ou émulateurs qui varient selon la quantité de mémoire, la prise en charge de la mémoire partagée, la prise en charge du débogage, ou la prise en charge en double précision. C++ AMP (C++ Accelerated Massive Parallelism) fournit des API que vous pouvez utiliser pour examiner les accélérateurs disponibles, en définir un comme valeur par défaut, spécifier plusieurs accelerator_views pour plusieurs appels au parallel_for_each, et effectuer des tâches spéciales de débogage.
Utilisation de l'accélérateur par défaut
Le runtime C++ AMP choisit un accélérateur par défaut, à moins que vous écriviez le code pour en choisir un spécifique. Le runtime sélectionne l'accélérateur par défaut comme suit :
Si l'application s'exécute en mode débogage, un accélérateur prend en charge le débogage.
Sinon, l'accélérateur spécifié par la variable d'environnement CPPAMP_DEFAULT_ACCELERATOR, si elle est définie.
Sinon, un périphérique non émulé.
Sinon, le périphérique qui a la plus grande quantité de mémoire disponible.
Sinon, un périphérique qui n'est pas attaché à l'affichage.
En outre, le runtime spécifie un access_type de type access_type_auto pour l'accélérateur par défaut. Cela signifie que l'accélérateur par défaut utilise la mémoire partagée si elle est prise en charge et si ses caractéristiques de performance (bande passante et latence) sont connues pour être identiques à celles de la mémoire dédiée (non partagée).
Vous pouvez déterminer les propriétés de l'accélérateur par défaut lorsque vous construisez l'accélérateur par défaut et en examinant ses propriétés. L'exemple de code suivant imprime le chemin d'accès, la quantité de mémoire d'accélérateur, la prise en charge de la mémoire partagée, la prise en charge en double précision et la prise en charge en double précision limitée de l'accélérateur par défaut.
void default_properties() {
accelerator default_acc;
std::wcout << default_acc.device_path << "\n";
std::wcout << default_acc.dedicated_memory << "\n";
std::wcout << (accs[i].supports_cpu_shared_memory ?
"CPU shared memory: true" : "CPU shared memory: false") << "\n";
std::wcout << (accs[i].supports_double_precision ?
"double precision: true" : "double precision: false") << "\n";
std::wcout << (accs[i].supports_limited_double_precision ?
"limited double precision: true" : "limited double precision: false") << "\n";
}
Variable d'environnement de CPPAMP_DEFAULT_ACCELERATOR
Vous pouvez définir la variable d'environnement CPPAMP_DEFAULT_ACCELERATOR pour spécifier accelerator::device_path de l'accélérateur par défaut. Le chemin d'accès est dépendant du matériel. Le code suivant utilise la fonction accelerator::get_all pour récupérer une liste des accélérateurs disponibles puis pour afficher le chemin d'accès et les caractéristiques de chaque accélérateur.
void list_all_accelerators()
{
std::vector<accelerator> accs = accelerator::get_all();
for (int i = 0; i < accs.size(); i++) {
std::wcout << accs[i].device_path << "\n";
std::wcout << accs[i].dedicated_memory << "\n";
std::wcout << (accs[i].supports_cpu_shared_memory ?
"CPU shared memory: true" : "CPU shared memory: false") << "\n";
std::wcout << (accs[i].supports_double_precision ?
"double precision: true" : "double precision: false") << "\n";
std::wcout << (accs[i].supports_limited_double_precision ?
"limited double precision: true" : "limited double precision: false") << "\n";
}
}
Sélection d'un accélérateur
Pour sélectionner un accélérateur, utilisez la méthode accelerator::get_all pour récupérer une liste des accélérateurs disponibles, puis pour en sélectionner un en fonction de ses propriétés. Cet exemple montre comment choisir l'accélérateur ayant le plus de mémoire :
void pick_with_most_memory()
{
std::vector<accelerator> accs = accelerator::get_all();
accelerator acc_chosen = accs[0];
for (int i = 0; i < accs.size(); i++) {
if (accs[i].dedicated_memory > acc_chosen.dedicated_memory) {
acc_chosen = accs[i];
}
}
std::wcout << "The accelerator with the most memory is "
<< acc_chosen.device_path << "\n"
<< acc_chosen.dedicated_memory << ".\n";
}
Notes
L'un des accélérateurs retournés par accelerator::get_all est l'accélérateur d'UC.Vous ne pouvez pas exécuter du code sur l'accélérateur de l'UC.Pour filtrer l'accélérateur d'UC, comparez la valeur de la propriété device_path de l'accélérateur retourné par accelerator::get_all avec la valeur accelerator::cpu_accelerator.Pour plus d'informations, consultez la section « Accélérateurs spéciaux » dans cet article.
Mémoire partagée
La mémoire partagée est la mémoire accessible par l'UC et l'accélérateur. L'utilisation de la mémoire partagée élimine ou réduit considérablement la charge mémoire des données de copie entre l'UC et l'accélérateur. Bien que la mémoire soit partagée, elle n'est pas accessible simultanément le processeur et l'accélérateur, sans quoi un comportement aléatoire est à craindre. La propriété d'accélérateur supports_cpu_shared_memory retourne true si l'accélérateur prend en charge la mémoire partagée, et la propriété default_cpu_access_type obtient l'access_type par défaut pour la mémoire allouée sur l'accelerator, par exemple, les array associés aux objets accelerator ou array_view accessibles sur l'accelerator.
Le runtime C++ AMP choisit automatiquement le meilleur access_type par défaut pour chaque accelerator, mais les caractéristiques de performance (bande passante et latence) de la mémoire partagée peuvent être pires que celles de la mémoire d'accélérateur dédiée (non partagée) lors de la lecture à partir de l'UC, de l'écriture à partir de l'UC ou les deux. Si la mémoire partagée s'exécute ainsi que la mémoire dédiée pour la lecture et l'écriture à partir du processeur, le runtime prend la valeur par défaut access_type_read_write ; sinon, le runtime choisit un access_typepar défaut plus conservateur, et autorise l'application à le remplacer si les modèles d'accès à la mémoire de ses noyaux de calcul utilisent un autre access_type.
L'exemple de code suivant indique comment déterminer si l'accélérateur par défaut prend en charge la mémoire partagée, puis substitue son type d'accès par défaut et crée un accelerator_view à partir de celui-ci.
#include <amp.h>
#include <iostream>
using namespace Concurrency;
int main()
{
accelerator acc = accelerator(accelerator::default_accelerator);
// Early out if the default accelerator doesn’t support shared memory.
if(!acc.supports_cpu_shared_memory)
{
std::cout << "The default accelerator does not support shared memory" << std::endl;
return 1;
}
// Override the default CPU access type.
acc.set_default_cpu_access_type(access_type_read_write);
// Create an accelerator_view from the default accelerator. The
// accelerator_view reflects the default_cpu_access_type of the
// accelerator it’s associated with.
accelerator_view acc_v = acc.default_view;
}
Une accelerator_view reflète systématiquement le default_cpu_access_type de l'accelerator auquel il est associé, et il ne fournit aucune interface pour substituer ou modifier son access_type.
Modification de l'accélérateur par défaut
Vous pouvez modifier l'accélérateur par défaut en appelant la méthode accelerator::set_default. Vous pouvez seulement modifier l'accélérateur par défaut une fois par exécution de l'application et vous devez le modifier avant que tout code soit exécuté sur le GPU. Tous les appels de fonction suivants pour modifier l'accélérateur retourne false. Si vous souhaitez utiliser un accélérateur différent dans un appel à parallel_for_each, lisez la section relative à l'« utilisation de plusieurs accélérateurs » dans cet article. L'exemple de code suivant définit l'accélérateur par défaut à un non émulé, qui n'est pas connecté à un affichage, et qui prend en charge la double précision.
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;
}
Utilisation de plusieurs accélérateurs
Il existe deux façons d'utiliser plusieurs accélérateurs dans votre application :
Vous pouvez passer des objets accelerator_view à des appels à la méthode parallel_for_each.
Vous pouvez construire un objet array à l'aide d'un objet accelerator_view spécifique. Le runtime de C+AMP prendra l'objet accelerator_view de l'objet capturé array dans l'expression lambda.
Accélérateurs spéciaux
Les chemins d'accès de périphérique de trois accélérateurs spéciaux sont disponibles comme des propriétés de la classe accelerator :
accelerator::direct3d_ref, données membres : Cet accélérateur monothread utilise le logiciel sur le CPU pour émuler une carte graphique générique. Il est utilisé par défaut pour le débogage, mais il n'est pas utile dans la production car il est plus lent que les accélérateurs matériels. En outre, il est uniquement disponible dans le Kit de développement logiciel de DirectX et de Windows, et il est peu probable qu'il soit installé sur les ordinateurs de vos clients. Pour plus d'informations, consultez Débogage du code GPU.
accelerator::direct3d_warp, données membres : Cet accélérateur fournit une solution de secours pour exécuter le code C++ AMP sur les processeurs multicœurs qui utilisent des extensions Streaming SIMD (SSE).
accelerator::cpu_accelerator, données membres : Vous pouvez utiliser cet accélérateur pour installer des tableaux temporaires. Le code C++ AMP ne peut pas être exécuté. Pour plus d'informations, consultez Tableaux temporaires dans C++ AMP (page éventuellement en anglais) publié sur le blog Programmation parallèle dans le code natif (éventuellement en anglais).
Interopérabilité
Le runtime C++ AMP prend en charge l'interopérabilité entre la classe accelerator_view et l'interface ID3D11Device Direct3D. La méthode create_accelerator_view prend une interface IUnknown et retourne un objet accelerator_view. La méthode get_device prend un objet accelerator_view et retourne une interface IUknown.