Пошаговое руководство. Отладка приложения C++ AMP

В этой статье показано, как отлаживать приложение, использующее ускоренный массовый параллелизм C++ (C++ AMP), чтобы воспользоваться преимуществами графической обработки (GPU). В нем используется программа параллельного сокращения, которая суммирует большой массив целых чисел. В этом пошаговом руководстве рассматриваются следующие задачи:

  • Запуск отладчика GPU.
  • Проверка потоков GPU в окне потоков GPU.
  • Использование окна Parallel Stacks для одновременного наблюдения за стеками вызовов нескольких потоков GPU.
  • Использование окна Parallel Watch для проверки значений одного выражения в нескольких потоках одновременно.
  • Пометка, замораживание, оттепель и группирование потоков GPU.
  • Выполнение всех потоков плитки в определенном расположении в коде.

Необходимые компоненты

Перед началом работы с этим пошаговом руководстве выполните следующие действия.

Примечание.

Заголовки C++ AMP устарели начиная с Visual Studio 2022 версии 17.0. Включение всех заголовков AMP приведет к возникновению ошибок сборки. Определите _SILENCE_AMP_DEPRECATION_WARNINGS перед включением всех заголовков AMP, чтобы замолчать предупреждения.

Примечание.

Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.

Создание примера проекта

Инструкции по созданию проекта зависят от используемой версии Visual Studio. Убедитесь, что на этой странице выбрана правильная версия документации над оглавлением.

Создание примера проекта в Visual Studio

  1. В строке меню выберите Файл>Создать>Проект, чтобы открыть диалоговое окно Создание проекта.

  2. В верхней части диалогового окна задайте для параметра Язык значение C++, для параметра Платформа значение Windows, а для Типа проекта — Консоль.

  3. В отфильтрованном списке типов проектов щелкните Консольное приложение, а затем нажмите кнопку Далее. На следующей странице введите AMPMapReduceполе "Имя ", чтобы указать имя проекта, и укажите расположение проекта, если требуется другой.

    Screenshot showing the Create a new project dialog with the Console App template selected.

  4. Нажмите кнопку Создать, чтобы создать клиентский проект.

Создание примера проекта в Visual Studio 2017 или Visual Studio 2015

  1. Запустите среду Visual Studio.

  2. В строке меню выберите Файл >Создать >Проект.

  3. В разделе "Установленные " в области шаблонов выберите Visual C++.

  4. Выберите консольное приложение Win32, введите AMPMapReduce в поле "Имя " и нажмите кнопку "ОК ".

  5. Нажмите кнопку Далее.

  6. Снимите поле проверка заголовка предварительной компиляции и нажмите кнопку "Готово".

  7. В Обозреватель решений удалите stdafx.h, targetver.h и stdafx.cpp из проекта.

Далее.

  1. Откройте AMPMapReduce.cpp и замените его содержимое следующим кодом.

    // AMPMapReduce.cpp defines the entry point for the program.
    // The program performs a parallel-sum reduction that computes the sum of an array of integers.
    
    #include <stdio.h>
    #include <tchar.h>
    #include <amp.h>
    
    const int BLOCK_DIM = 32;
    
    using namespace concurrency;
    
    void sum_kernel_tiled(tiled_index<BLOCK_DIM> t_idx, array<int, 1> &A, int stride_size) restrict(amp)
    {
        tile_static int localA[BLOCK_DIM];
    
        index<1> globalIdx = t_idx.global * stride_size;
        index<1> localIdx = t_idx.local;
    
        localA[localIdx[0]] =  A[globalIdx];
    
        t_idx.barrier.wait();
    
        // Aggregate all elements in one tile into the first element.
        for (int i = BLOCK_DIM / 2; i > 0; i /= 2)
        {
            if (localIdx[0] < i)
            {
    
                localA[localIdx[0]] += localA[localIdx[0] + i];
            }
    
            t_idx.barrier.wait();
        }
    
        if (localIdx[0] == 0)
        {
            A[globalIdx] = localA[0];
        }
    }
    
    int size_after_padding(int n)
    {
        // The extent might have to be slightly bigger than num_stride to
        // be evenly divisible by BLOCK_DIM. You can do this by padding with zeros.
        // The calculation to do this is BLOCK_DIM * ceil(n / BLOCK_DIM)
        return ((n - 1) / BLOCK_DIM + 1) * BLOCK_DIM;
    }
    
    int reduction_sum_gpu_kernel(array<int, 1> input)
    {
        int len = input.extent[0];
    
        //Tree-based reduction control that uses the CPU.
        for (int stride_size = 1; stride_size < len; stride_size *= BLOCK_DIM)
        {
            // Number of useful values in the array, given the current
            // stride size.
            int num_strides = len / stride_size;
    
            extent<1> e(size_after_padding(num_strides));
    
            // The sum kernel that uses the GPU.
            parallel_for_each(extent<1>(e).tile<BLOCK_DIM>(), [&input, stride_size] (tiled_index<BLOCK_DIM> idx) restrict(amp)
            {
                sum_kernel_tiled(idx, input, stride_size);
            });
        }
    
        array_view<int, 1> output = input.section(extent<1>(1));
        return output[0];
    }
    
    int cpu_sum(const std::vector<int> &arr) {
        int sum = 0;
        for (size_t i = 0; i < arr.size(); i++) {
            sum += arr[i];
        }
        return sum;
    }
    
    std::vector<int> rand_vector(unsigned int size) {
        srand(2011);
    
        std::vector<int> vec(size);
        for (size_t i = 0; i < size; i++) {
            vec[i] = rand();
        }
        return vec;
    }
    
    array<int, 1> vector_to_array(const std::vector<int> &vec) {
        array<int, 1> arr(vec.size());
        copy(vec.begin(), vec.end(), arr);
        return arr;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        std::vector<int> vec = rand_vector(10000);
        array<int, 1> arr = vector_to_array(vec);
    
        int expected = cpu_sum(vec);
        int actual = reduction_sum_gpu_kernel(arr);
    
        bool passed = (expected == actual);
        if (!passed) {
            printf("Actual (GPU): %d, Expected (CPU): %d", actual, expected);
        }
        printf("sum: %s\n", passed ? "Passed!" : "Failed!");
    
        getchar();
    
        return 0;
    }
    
  2. В строке меню выберите Файл>Сохранить все.

  3. В Обозреватель решений откройте контекстное меню для AMPMapReduce и выберите пункт "Свойства".

  4. В диалоговом окне "Страницы свойств" в разделе "Свойства конфигурации" выберите заголовки C/C++>Precompiled.

  5. Для свойства предварительно скомпилированного заголовка нажмите кнопку "Не использовать предварительно скомпилированные заголовки", а затем нажмите кнопку "ОК".

  6. В строке меню последовательно выберите Сборка>Собрать решение.

Отладка кода ЦП

В этой процедуре вы будете использовать локальный отладчик Windows, чтобы убедиться, что код ЦП в этом приложении правильный. Сегмент кода ЦП в этом приложении, который особенно интересен, является for циклом reduction_sum_gpu_kernel в функции. Он управляет параллельным сокращением на основе дерева, которое выполняется на GPU.

Отладка кода ЦП

  1. В Обозреватель решений откройте контекстное меню для AMPMapReduce и выберите пункт "Свойства".

  2. В диалоговом окне "Страницы свойств" в разделе "Свойства конфигурации" выберите "Отладка". Убедитесь, что локальный отладчик Windows выбран в отладчике для запуска списка.

  3. Вернитесь в редактор кода.

  4. Установите точки останова на строках кода, показанных на следующем рисунке (приблизительно строки 67 строк 70).

    CPU breakpoints marked next to lines of code in the editor.
    Точки останова ЦП

  5. В строке меню выберите Отладка>Начать отладку.

  6. В окне "Локальные" просмотрите значение stride_size до достижения точки останова в строке 70.

  7. В строке меню выберите Отладка>Остановить отладку.

Отладка кода GPU

В этом разделе показано, как выполнить отладку кода GPU, который является кодом, содержащимся в sum_kernel_tiled функции. Код GPU вычисляет сумму целых чисел для каждого блока параллельно.

Отладка кода GPU

  1. В Обозреватель решений откройте контекстное меню для AMPMapReduce и выберите пункт "Свойства".

  2. В диалоговом окне "Страницы свойств" в разделе "Свойства конфигурации" выберите "Отладка".

  3. В списке Загружаемый отладчик выберите Локальный отладчик Windows.

  4. В списке типов отладчика убедитесь, что выбран параметр "Авто ".

    Авто — это значение по умолчанию. В версиях до Windows 10 GPU только является обязательным значением вместо auto.

  5. Нажмите кнопку ОК.

  6. Задайте точку останова в строке 30, как показано на следующем рисунке.

    GPU breakpoints marked next to a line of code in the editor.
    Точка останова GPU

  7. В строке меню выберите Отладка>Начать отладку. Точки останова в коде ЦП в строках 67 и 70 не выполняются во время отладки GPU, так как эти строки кода выполняются на ЦП.

Использование окна потоков GPU

  1. Чтобы открыть окно потоков GPU, в строке меню выберите "Отладка>потоков GPU Windows".>

    Вы можете проверить состояние потоков GPU в окне потоков GPU, которое отображается.

  2. Закрепление окна потоков GPU в нижней части Visual Studio. Нажмите кнопку "Развернуть потоковый коммутатор", чтобы отобразить текстовые поля плитки и потока. В окне потоков GPU отображается общее количество активных и заблокированных потоков GPU, как показано на следующем рисунке.

    GPU Threads window with 4 active threads.
    Окно "Потоки GPU"

    313 плиток выделяются для этого вычисления. Каждая плитка содержит 32 потока. Так как локальная отладка GPU выполняется в эмуляторе программного обеспечения, существует четыре активных потока GPU. Четыре потока одновременно выполняют инструкции, а затем переходят вместе к следующей инструкции.

    В окне потоков GPU есть четыре потока GPU, активные и 28 потоков GPU заблокированы в инструкции tile_barrier::wait, определенной примерно в строке 21 (t_idx.barrier.wait();). Все 32 потока GPU принадлежат первой плитке. tile[0] Стрелка указывает на строку, содержащую текущий поток. Чтобы переключиться на другой поток, используйте один из следующих методов:

    • В строке для перехода на поток в окне потоков GPU откройте контекстное меню и нажмите кнопку "Переключиться на поток". Если строка представляет несколько потоков, переключитесь на первый поток в соответствии с координатами потока.

    • Введите значения плитки и потока потока в соответствующих текстовых полях, а затем нажмите кнопку "Переключить поток ".

    В окне "Стек вызовов" отображается стек вызовов текущего потока GPU.

Использование окна параллельных стеков

  1. Чтобы открыть окно Параллельных стеков, в строке меню выберите "Отладка>Параллельных стеков Windows".>

    Окно Параллельных стеков можно использовать для одновременной проверки кадров стека нескольких потоков GPU.

  2. Закрепление окна Параллельных стеков в нижней части Visual Studio.

  3. Убедитесь, что потоки выбраны в списке в левом верхнем углу. На следующем рисунке в окне Параллельных стеков отображается ориентированное представление потоков GPU, которые вы видели в окне потоков GPU.

    Parallel Stacks window with 4 active threads.
    Окно "Параллельные стеки"

    32 потока пошли из _kernel_stub лямбда-инструкции в parallel_for_each вызове функции, а затем в sum_kernel_tiled функцию, где происходит параллельное сокращение. 28 из 32 потоков прошли до tile_barrier::wait инструкции и остаются заблокированными в строке 22, а остальные четыре потока остаются активными в функции в sum_kernel_tiled строке 30.

    Вы можете проверить свойства потока GPU. Они доступны в окне потоков GPU в полнофункциональные подсказки данных окна Параллельных стеках . Чтобы увидеть их, наведите указатель на рамку sum_kernel_tiledстека. На следующем рисунке показана подсказка dataTip.

    DataTip for Parallel Stacks window.
    Подсказка данных потока GPU

    Дополнительные сведения о окне параллельных стеков см. в разделе "Использование окна параллельных стеков".

Использование окна параллельных контрольных значений

  1. Чтобы открыть окно "Параллельные часы", в строке меню выберите "Отладка>параллельных часов Windows>Parallel Watch>1".

    Для проверки значений выражения в нескольких потоках можно использовать окно Parallel Watch .

  2. Прикрепите окно Parallel Watch 1 к нижней части Visual Studio. В таблице окна Параллельных контрольных значений есть 32 строки. Каждый соответствует потоку GPU, который появился как в окне потоков GPU, так и в окне параллельных стеков . Теперь можно ввести выражения, значения которых необходимо проверить во всех 32 потоках GPU.

  3. Выберите заголовок столбца "Добавить контрольные значения ", введите localIdxи нажмите клавишу ВВОД .

  4. Снова выберите заголовок столбца "Добавить контрольные значения", введите globalIdxи нажмите клавишу ВВОД.

  5. Снова выберите заголовок столбца "Добавить контрольные значения", введите localA[localIdx[0]]и нажмите клавишу ВВОД.

    Вы можете сортировать по указанному выражению, выбрав соответствующий заголовок столбца.

    Выберите заголовок столбца localA[localIdx[0], чтобы отсортировать столбец. На следующем рисунке показаны результаты сортировки по localA[localIdx[0]].

    Parallel Watch window with sorted results.
    Результаты сортировки

    Вы можете экспортировать содержимое в окне Параллельных часов в Excel, нажав кнопку Excel и выбрав команду "Открыть в Excel". Если на компьютере разработки установлен Excel, кнопка открывает лист Excel, содержащий содержимое.

  6. В правом верхнем углу окна Parallel Watch есть элемент управления фильтра, который можно использовать для фильтрации содержимого с помощью логических выражений. Введите localA[localIdx[0]] > 20000 текстовое поле элемента управления фильтра и нажмите клавишу ВВОД .

    Теперь окно содержит только потоки, в которых localA[localIdx[0]] значение больше 20000. Содержимое по-прежнему отсортировано localA[localIdx[0]] по столбцу, который является действием сортировки, которое вы выбрали ранее.

Добавление тегов потоков GPU

Вы можете пометить определенные потоки GPU, пометив их в окне потоков GPU, окно параллельных часов или подсказку dataTip в окне параллельных стеков. Если строка в окне потоков GPU содержит несколько потоков, пометка этой строки помечает все потоки, содержащиеся в строке.

Флаг потоков GPU

  1. Выберите заголовок столбца [Thread] в окне Parallel Watch 1, чтобы отсортировать по индексу плитки и индексу потока.

  2. В строке меню выберите "Продолжить отладку>", что приводит к выполнению четырех потоков, которые были активны к следующему барьеру (определено в строке 32 AMPMapReduce.cpp).

  3. Выберите символ флага в левой части строки, содержащей четыре потока, которые теперь активны.

    На следующем рисунке показаны четыре активные помеченные потоки в окне потоков GPU.

    GPU Threads window with flagged threads.
    Активные потоки в окне "Потоки GPU"

    Окно параллельных контрольных значений и подсказка dataTip окна Параллельных стеках указывают на помеченные потоки.

  4. Если вы хотите сосредоточиться на четырех потоках, помеченных вами, можно отобразить только помеченные потоки. Он ограничивает то, что вы видите в окнах потоков GPU, параллельных часов и параллельных стеках.

    Нажмите кнопку "Показать помеченный только" на любом из окон или на панели инструментов "Расположение отладки". На следующем рисунке показана кнопка "Показать помеченный только только" на панели инструментов "Расположение отладки".

    Debug Location toolbar with Show Only Flagged icon.
    Кнопка "Показать помеченный только"

    Теперь окна потоков GPU, параллельных часов и параллельных стеках отображают только помеченные потоки.

Замораживание и оттаивание потоков GPU

Вы можете заморозить (приостановить) и отморозить (возобновить) потоки GPU из окна потоков GPU или окна параллельных часов. Вы можете заморозить и отморозить потоки ЦП таким же образом; Дополнительные сведения см. в разделе "Практическое руководство. Использование окна потоков".

Замораживание и оттепель потоков GPU

  1. Нажмите кнопку "Показать помеченный только" , чтобы отобразить все потоки.

  2. В строке меню выберите "Продолжить отладку>".

  3. Откройте контекстное меню для активной строки и нажмите кнопку "Закрепить".

    На следующем рисунке окна потоков GPU показано, что все четыре потока заморожены.

    GPU Threads windows showing frozen threads.
    Замороженные потоки в окне потоков GPU

    Аналогичным образом в окне "Параллельные часы" показано, что все четыре потока заморожены.

  4. В строке меню выберите "Продолжить отладку>", чтобы разрешить следующим четыре потокам GPU пройти мимо барьера в строке 22 и достичь точки останова в строке 30. В окне потоков GPU показано, что четыре ранее замороженных потока остаются замороженными и в активном состоянии.

  5. В строке меню выберите "Отладка", "Продолжить".

  6. В окне "Параллельные часы" можно также оттаить отдельные или несколько потоков GPU.

Группировка потоков GPU

  1. В контекстном меню для одного из потоков в окне потоков GPU выберите группировать по адресу.

    Потоки в окне потоков GPU группируются по адресу. Адрес соответствует инструкции в дизассемблированном расположении каждой группы потоков. 24 потока находятся в строке 22, где выполняется метод tile_barrier::wait. 12 потоков находятся в инструкции по барьеру в строке 32. Четыре из этих потоков помечены. Восемь потоков находятся в точке останова в строке 30. Четыре из этих потоков заморожены. На следующем рисунке показаны сгруппированные потоки в окне потоков GPU.

    GPU Threads window with threads grouped by Address.
    Сгруппированные потоки в окне потоков GPU

  2. Вы также можете выполнить операцию Group By , открыв контекстное меню для сетки данных окна параллельных часов . Выберите группу по, а затем выберите пункт меню, соответствующий тому, как вы хотите сгруппировать потоки.

Выполнение всех потоков в определенном расположении в коде

Все потоки в заданной плитке выполняются в строке, содержащей курсор с помощью запуска текущей плитки к курсору.

Выполнение всех потоков в расположении, помеченного курсором

  1. В контекстном меню для замороженных потоков выберите Thaw.

  2. В редакторе кода поместите курсор в строку 30.

  3. В контекстном меню редактора кода выберите "Запустить текущую плитку к курсору".

    24 потока, которые ранее были заблокированы на барьере в строке 21, прошли до линии 32. Он показан в окне потоков GPU.

См. также

Обзор C++ AMP
Отладка кода GPU
Практическое руководство. Использование окна потоков GPU
Практическое руководство. Использование окна "Контроль параллельных данных"
Анализ кода C++ AMP с помощью визуализатора параллелизма