Apply consistent code formatting across the repo. Add clang-format and pre-commit hooks.

This commit is contained in:
Rob Armstrong
2025-03-27 10:30:07 -07:00
parent 2cd58fbc9a
commit ceab6e8bcc
782 changed files with 107230 additions and 106548 deletions

View File

@@ -29,9 +29,9 @@
* This is a simple test showing performance and usability
* improvements with large kernel parameters introduced in CUDA 12.1
*/
#include <cassert>
#include <chrono>
#include <iostream>
#include <cassert>
// Utility includes
#include <helper_cuda.h>
@@ -40,134 +40,141 @@ using namespace std;
using namespace std::chrono;
#define TEST_ITERATIONS (1000)
#define TOTAL_PARAMS (8000) // ints
#define KERNEL_PARAM_LIMIT (1024) // ints
#define TOTAL_PARAMS (8000) // ints
#define KERNEL_PARAM_LIMIT (1024) // ints
#define CONST_COPIED_PARAMS (TOTAL_PARAMS - KERNEL_PARAM_LIMIT)
__constant__ int excess_params[CONST_COPIED_PARAMS];
typedef struct {
int param[KERNEL_PARAM_LIMIT];
typedef struct
{
int param[KERNEL_PARAM_LIMIT];
} param_t;
typedef struct {
int param[TOTAL_PARAMS];
typedef struct
{
int param[TOTAL_PARAMS];
} param_large_t;
// Kernel with 4KB kernel parameter limit
__global__ void kernelDefault(__grid_constant__ const param_t p, int *result) {
int tmp = 0;
__global__ void kernelDefault(__grid_constant__ const param_t p, int *result)
{
int tmp = 0;
// accumulate kernel parameters
for (int i = 0; i < KERNEL_PARAM_LIMIT; ++i) {
tmp += p.param[i];
}
// accumulate kernel parameters
for (int i = 0; i < KERNEL_PARAM_LIMIT; ++i) {
tmp += p.param[i];
}
// accumulate excess values passed via const memory
for (int i = 0; i < CONST_COPIED_PARAMS; ++i) {
tmp += excess_params[i];
}
// accumulate excess values passed via const memory
for (int i = 0; i < CONST_COPIED_PARAMS; ++i) {
tmp += excess_params[i];
}
*result = tmp;
*result = tmp;
}
// Kernel with 32,764 byte kernel parameter limit
__global__ void kernelLargeParam(__grid_constant__ const param_large_t p, int *result) {
int tmp = 0;
__global__ void kernelLargeParam(__grid_constant__ const param_large_t p, int *result)
{
int tmp = 0;
// accumulate kernel parameters
for (int i = 0; i < TOTAL_PARAMS; ++i) {
tmp += p.param[i];
}
// accumulate kernel parameters
for (int i = 0; i < TOTAL_PARAMS; ++i) {
tmp += p.param[i];
}
*result = tmp;
*result = tmp;
}
static void report_time(std::chrono::time_point<std::chrono::steady_clock> start,
std::chrono::time_point<std::chrono::steady_clock> end,
int iters) {
auto usecs = duration_cast<duration<float,
microseconds::period>>(end - start);
cout << usecs.count() / iters << endl;
int iters)
{
auto usecs = duration_cast<duration<float, microseconds::period>>(end - start);
cout << usecs.count() / iters << endl;
}
int main() {
int rc;
cudaFree(0);
int main()
{
int rc;
cudaFree(0);
param_t p;
param_large_t p_large;
param_t p;
param_large_t p_large;
// pageable host memory that holds excess constants passed via constant memory
int *copied_params = (int *)malloc(CONST_COPIED_PARAMS * sizeof(int));
assert(copied_params);
// pageable host memory that holds excess constants passed via constant memory
int *copied_params = (int *)malloc(CONST_COPIED_PARAMS * sizeof(int));
assert(copied_params);
// storage for computed result
int *d_result;
int h_result;
checkCudaErrors(cudaMalloc(&d_result, sizeof(int)));
// storage for computed result
int *d_result;
int h_result;
checkCudaErrors(cudaMalloc(&d_result, sizeof(int)));
int expected_result = 0;
int expected_result = 0;
// fill in data for validation
for (int i = 0; i < KERNEL_PARAM_LIMIT; ++i) {
p.param[i] = (i & 0xFF);
}
for (int i = KERNEL_PARAM_LIMIT; i < TOTAL_PARAMS; ++i) {
copied_params[i - KERNEL_PARAM_LIMIT] = (i & 0xFF);
}
for (int i = 0; i < TOTAL_PARAMS; ++i) {
p_large.param[i] = (i & 0xFF);
expected_result += (i & 0xFF);
}
// warmup, verify correctness
checkCudaErrors(cudaMemcpyToSymbol(excess_params, copied_params, CONST_COPIED_PARAMS * sizeof(int), 0, cudaMemcpyHostToDevice));
kernelDefault<<<1,1>>>(p, d_result);
checkCudaErrors(cudaMemcpy(&h_result, d_result, sizeof(int), cudaMemcpyDeviceToHost));
checkCudaErrors(cudaDeviceSynchronize());
if(h_result != expected_result) {
std::cout << "Test failed" << std::endl;
rc=-1;
goto Exit;
}
kernelLargeParam<<<1,1>>>(p_large, d_result);
checkCudaErrors(cudaMemcpy(&h_result, d_result, sizeof(int), cudaMemcpyDeviceToHost));
checkCudaErrors(cudaDeviceSynchronize());
if(h_result != expected_result) {
std::cout << "Test failed" << std::endl;
rc=-1;
goto Exit;
}
// benchmark default kernel parameter limit
{
auto start = steady_clock::now();
for (int i = 0; i < TEST_ITERATIONS; ++i) {
checkCudaErrors(cudaMemcpyToSymbol(excess_params, copied_params, CONST_COPIED_PARAMS * sizeof(int), 0, cudaMemcpyHostToDevice));
kernelDefault<<<1, 1>>>(p, d_result);
// fill in data for validation
for (int i = 0; i < KERNEL_PARAM_LIMIT; ++i) {
p.param[i] = (i & 0xFF);
}
for (int i = KERNEL_PARAM_LIMIT; i < TOTAL_PARAMS; ++i) {
copied_params[i - KERNEL_PARAM_LIMIT] = (i & 0xFF);
}
for (int i = 0; i < TOTAL_PARAMS; ++i) {
p_large.param[i] = (i & 0xFF);
expected_result += (i & 0xFF);
}
checkCudaErrors(cudaDeviceSynchronize());
auto end = steady_clock::now();
std::cout << "Kernel 4KB parameter limit - time (us):";
report_time(start, end, TEST_ITERATIONS);
// benchmark large kernel parameter limit
start = steady_clock::now();
for (int i = 0; i < TEST_ITERATIONS; ++i) {
kernelLargeParam<<<1, 1>>>(p_large, d_result);
}
// warmup, verify correctness
checkCudaErrors(
cudaMemcpyToSymbol(excess_params, copied_params, CONST_COPIED_PARAMS * sizeof(int), 0, cudaMemcpyHostToDevice));
kernelDefault<<<1, 1>>>(p, d_result);
checkCudaErrors(cudaMemcpy(&h_result, d_result, sizeof(int), cudaMemcpyDeviceToHost));
checkCudaErrors(cudaDeviceSynchronize());
end = steady_clock::now();
std::cout << "Kernel 32,764 byte parameter limit - time (us):";
report_time(start, end, TEST_ITERATIONS);
}
std::cout << "Test passed!" << std::endl;
rc=0;
if (h_result != expected_result) {
std::cout << "Test failed" << std::endl;
rc = -1;
goto Exit;
}
kernelLargeParam<<<1, 1>>>(p_large, d_result);
checkCudaErrors(cudaMemcpy(&h_result, d_result, sizeof(int), cudaMemcpyDeviceToHost));
checkCudaErrors(cudaDeviceSynchronize());
if (h_result != expected_result) {
std::cout << "Test failed" << std::endl;
rc = -1;
goto Exit;
}
// benchmark default kernel parameter limit
{
auto start = steady_clock::now();
for (int i = 0; i < TEST_ITERATIONS; ++i) {
checkCudaErrors(cudaMemcpyToSymbol(
excess_params, copied_params, CONST_COPIED_PARAMS * sizeof(int), 0, cudaMemcpyHostToDevice));
kernelDefault<<<1, 1>>>(p, d_result);
}
checkCudaErrors(cudaDeviceSynchronize());
auto end = steady_clock::now();
std::cout << "Kernel 4KB parameter limit - time (us):";
report_time(start, end, TEST_ITERATIONS);
// benchmark large kernel parameter limit
start = steady_clock::now();
for (int i = 0; i < TEST_ITERATIONS; ++i) {
kernelLargeParam<<<1, 1>>>(p_large, d_result);
}
checkCudaErrors(cudaDeviceSynchronize());
end = steady_clock::now();
std::cout << "Kernel 32,764 byte parameter limit - time (us):";
report_time(start, end, TEST_ITERATIONS);
}
std::cout << "Test passed!" << std::endl;
rc = 0;
Exit:
// cleanup
cudaFree(d_result);
free(copied_params);
return rc;
// cleanup
cudaFree(d_result);
free(copied_params);
return rc;
}

View File

@@ -33,33 +33,28 @@
#define ONE_MB (ONE_KB * ONE_KB)
extern size_t maxSampleSizeInMb;
extern int numKernelRuns;
extern int verboseResults;
extern int numKernelRuns;
extern int verboseResults;
extern unsigned int findNumSizesToTest(unsigned int minSize,
unsigned int maxSize,
unsigned int multiplier);
extern unsigned int findNumSizesToTest(unsigned int minSize, unsigned int maxSize, unsigned int multiplier);
// For Tracking the different memory allocation types
typedef enum memAllocType_enum {
MEMALLOC_TYPE_START,
USE_MANAGED_MEMORY_WITH_HINTS = MEMALLOC_TYPE_START,
USE_MANAGED_MEMORY_WITH_HINTS_ASYNC,
USE_MANAGED_MEMORY,
USE_ZERO_COPY,
USE_HOST_PAGEABLE_AND_DEVICE_MEMORY,
USE_HOST_PAGEABLE_AND_DEVICE_MEMORY_ASYNC,
USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY,
USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY_ASYNC,
MEMALLOC_TYPE_END = USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY_ASYNC,
MEMALLOC_TYPE_INVALID,
MEMALLOC_TYPE_COUNT = MEMALLOC_TYPE_INVALID
MEMALLOC_TYPE_START,
USE_MANAGED_MEMORY_WITH_HINTS = MEMALLOC_TYPE_START,
USE_MANAGED_MEMORY_WITH_HINTS_ASYNC,
USE_MANAGED_MEMORY,
USE_ZERO_COPY,
USE_HOST_PAGEABLE_AND_DEVICE_MEMORY,
USE_HOST_PAGEABLE_AND_DEVICE_MEMORY_ASYNC,
USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY,
USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY_ASYNC,
MEMALLOC_TYPE_END = USE_HOST_PAGELOCKED_AND_DEVICE_MEMORY_ASYNC,
MEMALLOC_TYPE_INVALID,
MEMALLOC_TYPE_COUNT = MEMALLOC_TYPE_INVALID
} MemAllocType;
typedef enum bandwidthType_enum {
READ_BANDWIDTH,
WRITE_BANDWIDTH
} BandwidthType;
typedef enum bandwidthType_enum { READ_BANDWIDTH, WRITE_BANDWIDTH } BandwidthType;
extern const char *memAllocTypeStr[];
extern const char *memAllocTypeShortStr[];
@@ -67,22 +62,20 @@ extern const char *memAllocTypeShortStr[];
struct resultsData;
struct testResults;
void createAndInitTestResults(struct testResults **results,
const char *testName,
unsigned int numMeasurements,
unsigned int numSizesToTest);
void createAndInitTestResults(struct testResults **results,
const char *testName,
unsigned int numMeasurements,
unsigned int numSizesToTest);
unsigned long *getPtrSizesToTest(struct testResults *results);
void freeTestResultsAndAllResultsData(struct testResults *results);
void createResultDataAndAddToTestResults(struct resultsData **ptrData,
struct testResults *results,
const char *resultsName,
bool printOnlyInVerbose,
bool reportAsBandwidth);
double *getPtrRunTimesInMs(struct resultsData *data, int allocType,
int sizeIndex);
void createResultDataAndAddToTestResults(struct resultsData **ptrData,
struct testResults *results,
const char *resultsName,
bool printOnlyInVerbose,
bool reportAsBandwidth);
double *getPtrRunTimesInMs(struct resultsData *data, int allocType, int sizeIndex);
void printResults(struct testResults *results,
bool print_launch_transfer_results, bool print_std_deviation);
void printResults(struct testResults *results, bool print_launch_transfer_results, bool print_std_deviation);
#endif

View File

@@ -27,7 +27,8 @@
#include "commonKernels.hpp"
__global__ void spinWhileLessThanOne(volatile unsigned int *latch) {
while (latch[0] < 1)
;
__global__ void spinWhileLessThanOne(volatile unsigned int *latch)
{
while (latch[0] < 1)
;
}

View File

@@ -27,277 +27,267 @@
#include <stdio.h>
#include <string.h>
#include "commonDefs.hpp"
#define CU_INIT_UUID
#include <cmath>
#define UNITS_Time "ms"
#define UNITS_BW "MB/s"
#define KB_str "KB"
#define MB_str "MB"
#define UNITS_BW "MB/s"
#define KB_str "KB"
#define MB_str "MB"
struct resultsData {
char resultsName[64];
struct testResults *results;
// this has MEMALLOC_TYPE_COUNT * results->numSizesToTest *
// results->numMeasurements elements
double **runTimesInMs[MEMALLOC_TYPE_COUNT];
double *averageRunTimesInMs[MEMALLOC_TYPE_COUNT];
double *stdDevRunTimesInMs[MEMALLOC_TYPE_COUNT];
double *stdDevBandwidthInMBps[MEMALLOC_TYPE_COUNT];
bool printOnlyInVerbose;
bool reportAsBandwidth;
struct resultsData *next;
struct resultsData
{
char resultsName[64];
struct testResults *results;
// this has MEMALLOC_TYPE_COUNT * results->numSizesToTest *
// results->numMeasurements elements
double **runTimesInMs[MEMALLOC_TYPE_COUNT];
double *averageRunTimesInMs[MEMALLOC_TYPE_COUNT];
double *stdDevRunTimesInMs[MEMALLOC_TYPE_COUNT];
double *stdDevBandwidthInMBps[MEMALLOC_TYPE_COUNT];
bool printOnlyInVerbose;
bool reportAsBandwidth;
struct resultsData *next;
};
struct testResults {
char testName[64];
unsigned int numMeasurements;
unsigned long *sizesToTest;
unsigned int numSizesToTest;
struct resultsData *resultsDataHead;
struct resultsData *resultsDataTail;
struct testResults
{
char testName[64];
unsigned int numMeasurements;
unsigned long *sizesToTest;
unsigned int numSizesToTest;
struct resultsData *resultsDataHead;
struct resultsData *resultsDataTail;
};
unsigned int findNumSizesToTest(unsigned int minSize, unsigned int maxSize,
unsigned int multiplier) {
unsigned int numSizesToTest = 0;
while (minSize <= maxSize) {
numSizesToTest++;
minSize *= multiplier;
}
return numSizesToTest;
unsigned int findNumSizesToTest(unsigned int minSize, unsigned int maxSize, unsigned int multiplier)
{
unsigned int numSizesToTest = 0;
while (minSize <= maxSize) {
numSizesToTest++;
minSize *= multiplier;
}
return numSizesToTest;
}
int compareDoubles(const void *ptr1, const void *ptr2) {
return (*(double *)ptr1 > *(double *)ptr2) ? 1 : -1;
}
int compareDoubles(const void *ptr1, const void *ptr2) { return (*(double *)ptr1 > *(double *)ptr2) ? 1 : -1; }
static inline double getTimeOrBandwidth(double runTimeInMs, unsigned long size,
bool getBandwidth) {
return (getBandwidth) ? (1000 * (size / runTimeInMs)) / ONE_MB : runTimeInMs;
static inline double getTimeOrBandwidth(double runTimeInMs, unsigned long size, bool getBandwidth)
{
return (getBandwidth) ? (1000 * (size / runTimeInMs)) / ONE_MB : runTimeInMs;
}
void createAndInitTestResults(struct testResults **ptrResults,
const char *testName,
unsigned int numMeasurements,
unsigned int numSizesToTest) {
unsigned int i;
struct testResults *results;
results = (struct testResults *)malloc(sizeof(struct testResults));
memset(results, 0, sizeof(struct testResults));
strcpy(results->testName, testName);
results->numMeasurements = numMeasurements;
results->numSizesToTest = numSizesToTest;
results->sizesToTest =
(unsigned long *)malloc(numSizesToTest * sizeof(unsigned long));
results->resultsDataHead = NULL;
results->resultsDataTail = NULL;
const char *testName,
unsigned int numMeasurements,
unsigned int numSizesToTest)
{
unsigned int i;
struct testResults *results;
results = (struct testResults *)malloc(sizeof(struct testResults));
memset(results, 0, sizeof(struct testResults));
strcpy(results->testName, testName);
results->numMeasurements = numMeasurements;
results->numSizesToTest = numSizesToTest;
results->sizesToTest = (unsigned long *)malloc(numSizesToTest * sizeof(unsigned long));
results->resultsDataHead = NULL;
results->resultsDataTail = NULL;
*ptrResults = results;
*ptrResults = results;
}
unsigned long *getPtrSizesToTest(struct testResults *results) {
return results->sizesToTest;
}
unsigned long *getPtrSizesToTest(struct testResults *results) { return results->sizesToTest; }
void createResultDataAndAddToTestResults(struct resultsData **ptrData,
struct testResults *results,
const char *resultsName,
bool printOnlyInVerbose,
bool reportAsBandwidth) {
unsigned int i, j;
struct resultsData *data;
data = (struct resultsData *)malloc(sizeof(struct resultsData));
memset(data, 0, sizeof(struct resultsData));
strcpy(data->resultsName, resultsName);
data->results = results;
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
data->runTimesInMs[i] =
(double **)malloc(results->numSizesToTest * sizeof(double *));
for (j = 0; j < results->numSizesToTest; j++) {
data->runTimesInMs[i][j] =
(double *)malloc(results->numMeasurements * sizeof(double));
}
data->averageRunTimesInMs[i] =
(double *)malloc(results->numSizesToTest * sizeof(double));
data->stdDevRunTimesInMs[i] =
(double *)malloc(results->numSizesToTest * sizeof(double));
data->stdDevBandwidthInMBps[i] =
(double *)malloc(results->numSizesToTest * sizeof(double));
}
data->printOnlyInVerbose = printOnlyInVerbose;
data->reportAsBandwidth = reportAsBandwidth;
data->next = NULL;
*ptrData = data;
if (results->resultsDataHead == NULL) {
results->resultsDataHead = data;
results->resultsDataTail = data;
} else {
results->resultsDataTail->next = data;
results->resultsDataTail = data;
}
}
double *getPtrRunTimesInMs(struct resultsData *data, int allocType,
int sizeIndex) {
return data->runTimesInMs[allocType][sizeIndex];
}
void freeTestResultsAndAllResultsData(struct testResults *results) {
struct resultsData *data, *dataToFree;
unsigned int i, j;
for (data = results->resultsDataHead; data != NULL;) {
struct testResults *results,
const char *resultsName,
bool printOnlyInVerbose,
bool reportAsBandwidth)
{
unsigned int i, j;
struct resultsData *data;
data = (struct resultsData *)malloc(sizeof(struct resultsData));
memset(data, 0, sizeof(struct resultsData));
strcpy(data->resultsName, resultsName);
data->results = results;
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
for (j = 0; j < results->numSizesToTest; j++) {
free(data->runTimesInMs[i][j]);
}
free(data->runTimesInMs[i]);
free(data->averageRunTimesInMs[i]);
free(data->stdDevRunTimesInMs[i]);
free(data->stdDevBandwidthInMBps[i]);
}
dataToFree = data;
data = data->next;
free(dataToFree);
}
free(results->sizesToTest);
free(results);
}
void calculateAverageAndStdDev(double *pAverage, double *pStdDev,
double *allResults, unsigned int count) {
unsigned int i;
double average = 0.0;
double stdDev = 0.0;
for (i = 0; i < count; i++) {
average += allResults[i];
}
average /= count;
for (i = 0; i < count; i++) {
stdDev += (allResults[i] - average) * (allResults[i] - average);
}
stdDev /= count;
stdDev = sqrt(stdDev);
*pAverage = average;
*pStdDev = (average == 0.0) ? 0.0 : ((100.0 * stdDev) / average);
}
void calculateStdDevBandwidth(double *pStdDev, double *allResults,
unsigned int count, unsigned long size) {
unsigned int i;
double bandwidth;
double average = 0.0;
double stdDev = 0.0;
for (i = 0; i < count; i++) {
bandwidth = (1000 * (size / allResults[i])) / ONE_MB;
average += bandwidth;
}
average /= count;
for (i = 0; i < count; i++) {
bandwidth = (1000 * (size / allResults[i])) / ONE_MB;
stdDev += (bandwidth - average) * (bandwidth - average);
}
stdDev /= count;
stdDev = sqrt(stdDev);
*pStdDev = (average == 0.0) ? 0.0 : ((100.0 * stdDev) / average);
}
void printTimesInTableFormat(struct testResults *results,
struct resultsData *data, bool printAverage,
bool printStdDev) {
unsigned int i, j;
bool printStdDevBandwidth = printStdDev && data->reportAsBandwidth;
printf("Size_KB");
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf("\t%7s", memAllocTypeShortStr[i]);
}
printf("\n");
for (j = 0; j < results->numSizesToTest; j++) {
printf("%lu", results->sizesToTest[j] / ONE_KB);
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf(data->reportAsBandwidth ? "\t%7.2lf" : "\t%7.3lf",
printStdDevBandwidth
? data->stdDevBandwidthInMBps[i][j]
: getTimeOrBandwidth(
printAverage ? data->averageRunTimesInMs[i][j]
: data->stdDevRunTimesInMs[i][j],
results->sizesToTest[j], data->reportAsBandwidth));
}
printf("\n");
}
}
void printAllResultsInVerboseMode(struct testResults *results,
struct resultsData *data) {
unsigned int i, j, k;
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf("Verbose mode, printing all results for %s\n", memAllocTypeStr[i]);
printf("Instance");
for (j = 0; j < results->numSizesToTest; j++) {
printf("\t%lu", results->sizesToTest[j] / ONE_KB);
}
printf("\n");
for (k = 0; k < results->numMeasurements; k++) {
printf("%u", k);
for (j = 0; j < results->numSizesToTest; j++) {
printf(data->reportAsBandwidth ? "\t%7.2lf" : "\t%7.3lf",
getTimeOrBandwidth(data->runTimesInMs[i][j][k],
results->sizesToTest[j],
data->reportAsBandwidth));
}
printf("\n");
}
}
}
void printResults(struct testResults *results,
bool print_launch_transfer_results,
bool print_std_deviation) {
char vulcanPrint[256];
char resultNameNoSpaces[64];
unsigned int i, j, k;
struct resultsData *resultsIter;
bool sizeGreaterThan1MB;
for (resultsIter = results->resultsDataHead; resultsIter != NULL;
resultsIter = resultsIter->next) {
if (!verboseResults && resultsIter->printOnlyInVerbose) {
continue;
}
if (!print_launch_transfer_results) {
if (!(strcmp(resultsIter->resultsName, "Overall Time") == 0)) {
continue;
}
}
// regular print
printf("\n%s For %s ", resultsIter->resultsName, results->testName);
printf("\n");
for (j = 0; j < results->numSizesToTest; j++) {
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
calculateAverageAndStdDev(&resultsIter->averageRunTimesInMs[i][j],
&resultsIter->stdDevRunTimesInMs[i][j],
resultsIter->runTimesInMs[i][j],
results->numMeasurements);
if (resultsIter->reportAsBandwidth) {
calculateStdDevBandwidth(&resultsIter->stdDevBandwidthInMBps[i][j],
resultsIter->runTimesInMs[i][j],
results->numMeasurements,
results->sizesToTest[j]);
data->runTimesInMs[i] = (double **)malloc(results->numSizesToTest * sizeof(double *));
for (j = 0; j < results->numSizesToTest; j++) {
data->runTimesInMs[i][j] = (double *)malloc(results->numMeasurements * sizeof(double));
}
}
data->averageRunTimesInMs[i] = (double *)malloc(results->numSizesToTest * sizeof(double));
data->stdDevRunTimesInMs[i] = (double *)malloc(results->numSizesToTest * sizeof(double));
data->stdDevBandwidthInMBps[i] = (double *)malloc(results->numSizesToTest * sizeof(double));
}
printf("\nPrinting Average of %u measurements in (%s)\n",
results->numMeasurements,
resultsIter->reportAsBandwidth ? UNITS_BW : UNITS_Time);
printTimesInTableFormat(results, resultsIter, true, false);
if (print_std_deviation) {
printf(
"\nPrinting Standard Deviation as %% of average of %u measurements\n",
results->numMeasurements);
printTimesInTableFormat(results, resultsIter, false, true);
data->printOnlyInVerbose = printOnlyInVerbose;
data->reportAsBandwidth = reportAsBandwidth;
data->next = NULL;
*ptrData = data;
if (results->resultsDataHead == NULL) {
results->resultsDataHead = data;
results->resultsDataTail = data;
}
if (verboseResults) {
printAllResultsInVerboseMode(results, resultsIter);
else {
results->resultsDataTail->next = data;
results->resultsDataTail = data;
}
}
double *getPtrRunTimesInMs(struct resultsData *data, int allocType, int sizeIndex)
{
return data->runTimesInMs[allocType][sizeIndex];
}
void freeTestResultsAndAllResultsData(struct testResults *results)
{
struct resultsData *data, *dataToFree;
unsigned int i, j;
for (data = results->resultsDataHead; data != NULL;) {
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
for (j = 0; j < results->numSizesToTest; j++) {
free(data->runTimesInMs[i][j]);
}
free(data->runTimesInMs[i]);
free(data->averageRunTimesInMs[i]);
free(data->stdDevRunTimesInMs[i]);
free(data->stdDevBandwidthInMBps[i]);
}
dataToFree = data;
data = data->next;
free(dataToFree);
}
free(results->sizesToTest);
free(results);
}
void calculateAverageAndStdDev(double *pAverage, double *pStdDev, double *allResults, unsigned int count)
{
unsigned int i;
double average = 0.0;
double stdDev = 0.0;
for (i = 0; i < count; i++) {
average += allResults[i];
}
average /= count;
for (i = 0; i < count; i++) {
stdDev += (allResults[i] - average) * (allResults[i] - average);
}
stdDev /= count;
stdDev = sqrt(stdDev);
*pAverage = average;
*pStdDev = (average == 0.0) ? 0.0 : ((100.0 * stdDev) / average);
}
void calculateStdDevBandwidth(double *pStdDev, double *allResults, unsigned int count, unsigned long size)
{
unsigned int i;
double bandwidth;
double average = 0.0;
double stdDev = 0.0;
for (i = 0; i < count; i++) {
bandwidth = (1000 * (size / allResults[i])) / ONE_MB;
average += bandwidth;
}
average /= count;
for (i = 0; i < count; i++) {
bandwidth = (1000 * (size / allResults[i])) / ONE_MB;
stdDev += (bandwidth - average) * (bandwidth - average);
}
stdDev /= count;
stdDev = sqrt(stdDev);
*pStdDev = (average == 0.0) ? 0.0 : ((100.0 * stdDev) / average);
}
void printTimesInTableFormat(struct testResults *results, struct resultsData *data, bool printAverage, bool printStdDev)
{
unsigned int i, j;
bool printStdDevBandwidth = printStdDev && data->reportAsBandwidth;
printf("Size_KB");
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf("\t%7s", memAllocTypeShortStr[i]);
}
printf("\n");
for (j = 0; j < results->numSizesToTest; j++) {
printf("%lu", results->sizesToTest[j] / ONE_KB);
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf(data->reportAsBandwidth ? "\t%7.2lf" : "\t%7.3lf",
printStdDevBandwidth ? data->stdDevBandwidthInMBps[i][j]
: getTimeOrBandwidth(printAverage ? data->averageRunTimesInMs[i][j]
: data->stdDevRunTimesInMs[i][j],
results->sizesToTest[j],
data->reportAsBandwidth));
}
printf("\n");
}
}
void printAllResultsInVerboseMode(struct testResults *results, struct resultsData *data)
{
unsigned int i, j, k;
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
printf("Verbose mode, printing all results for %s\n", memAllocTypeStr[i]);
printf("Instance");
for (j = 0; j < results->numSizesToTest; j++) {
printf("\t%lu", results->sizesToTest[j] / ONE_KB);
}
printf("\n");
for (k = 0; k < results->numMeasurements; k++) {
printf("%u", k);
for (j = 0; j < results->numSizesToTest; j++) {
printf(
data->reportAsBandwidth ? "\t%7.2lf" : "\t%7.3lf",
getTimeOrBandwidth(data->runTimesInMs[i][j][k], results->sizesToTest[j], data->reportAsBandwidth));
}
printf("\n");
}
}
}
void printResults(struct testResults *results, bool print_launch_transfer_results, bool print_std_deviation)
{
char vulcanPrint[256];
char resultNameNoSpaces[64];
unsigned int i, j, k;
struct resultsData *resultsIter;
bool sizeGreaterThan1MB;
for (resultsIter = results->resultsDataHead; resultsIter != NULL; resultsIter = resultsIter->next) {
if (!verboseResults && resultsIter->printOnlyInVerbose) {
continue;
}
if (!print_launch_transfer_results) {
if (!(strcmp(resultsIter->resultsName, "Overall Time") == 0)) {
continue;
}
}
// regular print
printf("\n%s For %s ", resultsIter->resultsName, results->testName);
printf("\n");
for (j = 0; j < results->numSizesToTest; j++) {
for (i = 0; i < MEMALLOC_TYPE_COUNT; i++) {
calculateAverageAndStdDev(&resultsIter->averageRunTimesInMs[i][j],
&resultsIter->stdDevRunTimesInMs[i][j],
resultsIter->runTimesInMs[i][j],
results->numMeasurements);
if (resultsIter->reportAsBandwidth) {
calculateStdDevBandwidth(&resultsIter->stdDevBandwidthInMBps[i][j],
resultsIter->runTimesInMs[i][j],
results->numMeasurements,
results->sizesToTest[j]);
}
}
}
printf("\nPrinting Average of %u measurements in (%s)\n",
results->numMeasurements,
resultsIter->reportAsBandwidth ? UNITS_BW : UNITS_Time);
printTimesInTableFormat(results, resultsIter, true, false);
if (print_std_deviation) {
printf("\nPrinting Standard Deviation as %% of average of %u measurements\n", results->numMeasurements);
printTimesInTableFormat(results, resultsIter, false, true);
}
if (verboseResults) {
printAllResultsInVerboseMode(results, resultsIter);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -41,8 +41,8 @@
#include <string.h>
// includes, project
#include <helper_cuda.h> // helper functions for CUDA error checking and initialization
#include <helper_functions.h> // helper utility functions
#include <helper_cuda.h> // helper functions for CUDA error checking and initialization
#include <helper_functions.h> // helper utility functions
////////////////////////////////////////////////////////////////////////////////
// Misaligned types
@@ -51,46 +51,50 @@ typedef unsigned char uint8;
typedef unsigned short int uint16;
typedef struct {
unsigned char r, g, b, a;
typedef struct
{
unsigned char r, g, b, a;
} RGBA8_misaligned;
typedef struct {
unsigned int l, a;
typedef struct
{
unsigned int l, a;
} LA32_misaligned;
typedef struct {
unsigned int r, g, b;
typedef struct
{
unsigned int r, g, b;
} RGB32_misaligned;
typedef struct {
unsigned int r, g, b, a;
typedef struct
{
unsigned int r, g, b, a;
} RGBA32_misaligned;
////////////////////////////////////////////////////////////////////////////////
// Aligned types
////////////////////////////////////////////////////////////////////////////////
typedef struct __align__(4) {
unsigned char r, g, b, a;
}
RGBA8;
typedef struct __align__(4)
{
unsigned char r, g, b, a;
} RGBA8;
typedef unsigned int I32;
typedef struct __align__(8) {
unsigned int l, a;
}
LA32;
typedef struct __align__(8)
{
unsigned int l, a;
} LA32;
typedef struct __align__(16) {
unsigned int r, g, b;
}
RGB32;
typedef struct __align__(16)
{
unsigned int r, g, b;
} RGB32;
typedef struct __align__(16) {
unsigned int r, g, b, a;
}
RGBA32;
typedef struct __align__(16)
{
unsigned int r, g, b, a;
} RGBA32;
////////////////////////////////////////////////////////////////////////////////
// Because G80 class hardware natively supports global memory operations
@@ -101,10 +105,10 @@ RGBA32;
// "Structure of arrays" storage strategy offers best performance
// in general case. See section 5.1.2 of the Programming Guide.
////////////////////////////////////////////////////////////////////////////////
typedef struct __align__(16) {
RGBA32 c1, c2;
}
RGBA32_2;
typedef struct __align__(16)
{
RGBA32 c1, c2;
} RGBA32_2;
////////////////////////////////////////////////////////////////////////////////
// Common host and device functions
@@ -126,14 +130,14 @@ int iAlignDown(int a, int b) { return a - a % b; }
// Copy is carried out on per-element basis,
// so it's not per-byte in case of padded structures.
////////////////////////////////////////////////////////////////////////////////
template <class TData>
__global__ void testKernel(TData *d_odata, TData *d_idata, int numElements) {
const int tid = blockDim.x * blockIdx.x + threadIdx.x;
const int numThreads = blockDim.x * gridDim.x;
template <class TData> __global__ void testKernel(TData *d_odata, TData *d_idata, int numElements)
{
const int tid = blockDim.x * blockIdx.x + threadIdx.x;
const int numThreads = blockDim.x * gridDim.x;
for (int pos = tid; pos < numElements; pos += numThreads) {
d_odata[pos] = d_idata[pos];
}
for (int pos = tid; pos < numElements; pos += numThreads) {
d_odata[pos] = d_idata[pos];
}
}
////////////////////////////////////////////////////////////////////////////////
@@ -144,171 +148,166 @@ __global__ void testKernel(TData *d_odata, TData *d_idata, int numElements) {
// is undefined, since padding is merely a placeholder
// and doesn't contain any user data.
////////////////////////////////////////////////////////////////////////////////
template <class TData>
int testCPU(TData *h_odata, TData *h_idata, int numElements,
int packedElementSize) {
for (int pos = 0; pos < numElements; pos++) {
TData src = h_idata[pos];
TData dst = h_odata[pos];
template <class TData> int testCPU(TData *h_odata, TData *h_idata, int numElements, int packedElementSize)
{
for (int pos = 0; pos < numElements; pos++) {
TData src = h_idata[pos];
TData dst = h_odata[pos];
for (int i = 0; i < packedElementSize; i++)
if (((char *)&src)[i] != ((char *)&dst)[i]) {
return 0;
}
}
for (int i = 0; i < packedElementSize; i++)
if (((char *)&src)[i] != ((char *)&dst)[i]) {
return 0;
}
}
return 1;
return 1;
}
////////////////////////////////////////////////////////////////////////////////
// Data configuration
////////////////////////////////////////////////////////////////////////////////
// Memory chunk size in bytes. Reused for test
const int MEM_SIZE = 50000000;
const int MEM_SIZE = 50000000;
const int NUM_ITERATIONS = 32;
// GPU input and output data
unsigned char *d_idata, *d_odata;
// CPU input data and instance of GPU output data
unsigned char *h_idataCPU, *h_odataGPU;
unsigned char *h_idataCPU, *h_odataGPU;
StopWatchInterface *hTimer = NULL;
template <class TData>
int runTest(int packedElementSize, int memory_size) {
const int totalMemSizeAligned = iAlignDown(memory_size, sizeof(TData));
const int numElements = iDivDown(memory_size, sizeof(TData));
template <class TData> int runTest(int packedElementSize, int memory_size)
{
const int totalMemSizeAligned = iAlignDown(memory_size, sizeof(TData));
const int numElements = iDivDown(memory_size, sizeof(TData));
// Clean output buffer before current test
checkCudaErrors(cudaMemset(d_odata, 0, memory_size));
// Run test
checkCudaErrors(cudaDeviceSynchronize());
sdkResetTimer(&hTimer);
sdkStartTimer(&hTimer);
// Clean output buffer before current test
checkCudaErrors(cudaMemset(d_odata, 0, memory_size));
// Run test
checkCudaErrors(cudaDeviceSynchronize());
sdkResetTimer(&hTimer);
sdkStartTimer(&hTimer);
for (int i = 0; i < NUM_ITERATIONS; i++) {
testKernel<TData>
<<<64, 256>>>((TData *)d_odata, (TData *)d_idata, numElements);
getLastCudaError("testKernel() execution failed\n");
}
for (int i = 0; i < NUM_ITERATIONS; i++) {
testKernel<TData><<<64, 256>>>((TData *)d_odata, (TData *)d_idata, numElements);
getLastCudaError("testKernel() execution failed\n");
}
checkCudaErrors(cudaDeviceSynchronize());
sdkStopTimer(&hTimer);
double gpuTime = sdkGetTimerValue(&hTimer) / NUM_ITERATIONS;
printf("Avg. time: %f ms / Copy throughput: %f GB/s.\n", gpuTime,
(double)totalMemSizeAligned / (gpuTime * 0.001 * 1073741824.0));
checkCudaErrors(cudaDeviceSynchronize());
sdkStopTimer(&hTimer);
double gpuTime = sdkGetTimerValue(&hTimer) / NUM_ITERATIONS;
printf("Avg. time: %f ms / Copy throughput: %f GB/s.\n",
gpuTime,
(double)totalMemSizeAligned / (gpuTime * 0.001 * 1073741824.0));
// Read back GPU results and run validation
checkCudaErrors(
cudaMemcpy(h_odataGPU, d_odata, memory_size, cudaMemcpyDeviceToHost));
int flag = testCPU((TData *)h_odataGPU, (TData *)h_idataCPU, numElements,
packedElementSize);
// Read back GPU results and run validation
checkCudaErrors(cudaMemcpy(h_odataGPU, d_odata, memory_size, cudaMemcpyDeviceToHost));
int flag = testCPU((TData *)h_odataGPU, (TData *)h_idataCPU, numElements, packedElementSize);
printf(flag ? "\tTEST OK\n" : "\tTEST FAILURE\n");
printf(flag ? "\tTEST OK\n" : "\tTEST FAILURE\n");
return !flag;
return !flag;
}
int main(int argc, char **argv) {
int i, nTotalFailures = 0;
int main(int argc, char **argv)
{
int i, nTotalFailures = 0;
int devID;
cudaDeviceProp deviceProp;
printf("[%s] - Starting...\n", argv[0]);
int devID;
cudaDeviceProp deviceProp;
printf("[%s] - Starting...\n", argv[0]);
// find first CUDA device
devID = findCudaDevice(argc, (const char **)argv);
// find first CUDA device
devID = findCudaDevice(argc, (const char **)argv);
// get number of SMs on this GPU
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
printf("[%s] has %d MP(s) x %d (Cores/MP) = %d (Cores)\n", deviceProp.name,
deviceProp.multiProcessorCount,
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor),
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) *
deviceProp.multiProcessorCount);
// get number of SMs on this GPU
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
printf("[%s] has %d MP(s) x %d (Cores/MP) = %d (Cores)\n",
deviceProp.name,
deviceProp.multiProcessorCount,
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor),
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) * deviceProp.multiProcessorCount);
// Anything that is less than 192 Cores will have a scaled down workload
float scale_factor =
max((192.0f / (_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) *
(float)deviceProp.multiProcessorCount)),
1.0f);
// Anything that is less than 192 Cores will have a scaled down workload
float scale_factor = max(
(192.0f / (_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) * (float)deviceProp.multiProcessorCount)),
1.0f);
int MemorySize = (int)(MEM_SIZE / scale_factor) &
0xffffff00; // force multiple of 256 bytes
int MemorySize = (int)(MEM_SIZE / scale_factor) & 0xffffff00; // force multiple of 256 bytes
printf("> Compute scaling value = %4.2f\n", scale_factor);
printf("> Memory Size = %d\n", MemorySize);
printf("> Compute scaling value = %4.2f\n", scale_factor);
printf("> Memory Size = %d\n", MemorySize);
sdkCreateTimer(&hTimer);
sdkCreateTimer(&hTimer);
printf("Allocating memory...\n");
h_idataCPU = (unsigned char *)malloc(MemorySize);
h_odataGPU = (unsigned char *)malloc(MemorySize);
checkCudaErrors(cudaMalloc((void **)&d_idata, MemorySize));
checkCudaErrors(cudaMalloc((void **)&d_odata, MemorySize));
printf("Allocating memory...\n");
h_idataCPU = (unsigned char *)malloc(MemorySize);
h_odataGPU = (unsigned char *)malloc(MemorySize);
checkCudaErrors(cudaMalloc((void **)&d_idata, MemorySize));
checkCudaErrors(cudaMalloc((void **)&d_odata, MemorySize));
printf("Generating host input data array...\n");
printf("Generating host input data array...\n");
for (i = 0; i < MemorySize; i++) {
h_idataCPU[i] = (i & 0xFF) + 1;
}
for (i = 0; i < MemorySize; i++) {
h_idataCPU[i] = (i & 0xFF) + 1;
}
printf("Uploading input data to GPU memory...\n");
checkCudaErrors(
cudaMemcpy(d_idata, h_idataCPU, MemorySize, cudaMemcpyHostToDevice));
printf("Uploading input data to GPU memory...\n");
checkCudaErrors(cudaMemcpy(d_idata, h_idataCPU, MemorySize, cudaMemcpyHostToDevice));
printf("Testing misaligned types...\n");
printf("uint8...\n");
nTotalFailures += runTest<uint8>(1, MemorySize);
printf("Testing misaligned types...\n");
printf("uint8...\n");
nTotalFailures += runTest<uint8>(1, MemorySize);
printf("uint16...\n");
nTotalFailures += runTest<uint16>(2, MemorySize);
printf("uint16...\n");
nTotalFailures += runTest<uint16>(2, MemorySize);
printf("RGBA8_misaligned...\n");
nTotalFailures += runTest<RGBA8_misaligned>(4, MemorySize);
printf("RGBA8_misaligned...\n");
nTotalFailures += runTest<RGBA8_misaligned>(4, MemorySize);
printf("LA32_misaligned...\n");
nTotalFailures += runTest<LA32_misaligned>(8, MemorySize);
printf("LA32_misaligned...\n");
nTotalFailures += runTest<LA32_misaligned>(8, MemorySize);
printf("RGB32_misaligned...\n");
nTotalFailures += runTest<RGB32_misaligned>(12, MemorySize);
printf("RGB32_misaligned...\n");
nTotalFailures += runTest<RGB32_misaligned>(12, MemorySize);
printf("RGBA32_misaligned...\n");
nTotalFailures += runTest<RGBA32_misaligned>(16, MemorySize);
printf("RGBA32_misaligned...\n");
nTotalFailures += runTest<RGBA32_misaligned>(16, MemorySize);
printf("Testing aligned types...\n");
printf("RGBA8...\n");
nTotalFailures += runTest<RGBA8>(4, MemorySize);
printf("Testing aligned types...\n");
printf("RGBA8...\n");
nTotalFailures += runTest<RGBA8>(4, MemorySize);
printf("I32...\n");
nTotalFailures += runTest<I32>(4, MemorySize);
printf("I32...\n");
nTotalFailures += runTest<I32>(4, MemorySize);
printf("LA32...\n");
nTotalFailures += runTest<LA32>(8, MemorySize);
printf("LA32...\n");
nTotalFailures += runTest<LA32>(8, MemorySize);
printf("RGB32...\n");
nTotalFailures += runTest<RGB32>(12, MemorySize);
printf("RGB32...\n");
nTotalFailures += runTest<RGB32>(12, MemorySize);
printf("RGBA32...\n");
nTotalFailures += runTest<RGBA32>(16, MemorySize);
printf("RGBA32...\n");
nTotalFailures += runTest<RGBA32>(16, MemorySize);
printf("RGBA32_2...\n");
nTotalFailures += runTest<RGBA32_2>(32, MemorySize);
printf("RGBA32_2...\n");
nTotalFailures += runTest<RGBA32_2>(32, MemorySize);
printf("\n[alignedTypes] -> Test Results: %d Failures\n", nTotalFailures);
printf("\n[alignedTypes] -> Test Results: %d Failures\n", nTotalFailures);
printf("Shutting down...\n");
checkCudaErrors(cudaFree(d_idata));
checkCudaErrors(cudaFree(d_odata));
free(h_odataGPU);
free(h_idataCPU);
printf("Shutting down...\n");
checkCudaErrors(cudaFree(d_idata));
checkCudaErrors(cudaFree(d_odata));
free(h_odataGPU);
free(h_idataCPU);
sdkDeleteTimer(&hTimer);
sdkDeleteTimer(&hTimer);
if (nTotalFailures != 0) {
printf("Test failed!\n");
exit(EXIT_FAILURE);
}
if (nTotalFailures != 0) {
printf("Test failed!\n");
exit(EXIT_FAILURE);
}
printf("Test passed\n");
exit(EXIT_SUCCESS);
printf("Test passed\n");
exit(EXIT_SUCCESS);
}

View File

@@ -6,5 +6,5 @@ typedef struct{
float b;
} testStructure;
Without alignment specification the compiler will not automatically use a single 64-bit global memory load/store instruction, but will emit two 32-bit load instructions instead.
This significantly impacts aggregate load/store bandwidth, since the latter breaks coalescing rules because of incontiguous memory access pattern. Refer to section 5.1.2.1 of the Programming Guide.
Without alignment specification the compiler will not automatically use a single 64-bit global memory load/store instruction, but will emit two 32-bit load instructions instead.
This significantly impacts aggregate load/store bandwidth, since the latter breaks coalescing rules because of incontiguous memory access pattern. Refer to section 5.1.2.1 of the Programming Guide.

View File

@@ -30,4 +30,3 @@ cudaStreamBeginCapture, cudaGraphInstantiate, cudaGraphLaunch, cudaGraphUpload
Download and install the [CUDA Toolkit 12.5](https://developer.nvidia.com/cuda-downloads) for your corresponding platform.
## References (for more details)

View File

@@ -31,10 +31,10 @@
#define USE_NVTX
#include <chrono>
#include <cstdio>
#include <cuda_runtime.h>
#include <vector>
#include <chrono>
typedef volatile int LatchType;
@@ -43,75 +43,72 @@ std::chrono::time_point<std::chrono::high_resolution_clock> getCpuTime()
return std::chrono::high_resolution_clock::now();
}
template <typename T>
float getMicroSecondDuration(T start, T end)
template <typename T> float getMicroSecondDuration(T start, T end)
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(end-start).count() *.001f;
return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() * .001f;
}
float getAsyncMicroSecondDuration(cudaEvent_t start, cudaEvent_t end)
{
float ms;
cudaEventElapsedTime(&ms, start, end);
return ms*1000;
return ms * 1000;
}
#ifdef USE_NVTX
#include <nvtx3/nvToolsExt.h>
class Tracer {
class Tracer
{
public:
Tracer(const char* name) {
nvtxRangePushA(name);
}
~Tracer() {
nvtxRangePop();
}
Tracer(const char *name) { nvtxRangePushA(name); }
~Tracer() { nvtxRangePop(); }
};
#define RANGE(name) Tracer uniq_name_using_macros(name);
#define RANGE(name) Tracer uniq_name_using_macros(name);
#define RANGE_PUSH(name) nvtxRangePushA(name)
#define RANGE_POP() nvtxRangePop();
#define RANGE_POP() nvtxRangePop();
#else
#define RANGE(name)
#endif
std::vector<cudaStream_t> stream;
cudaEvent_t event[1];
cudaEvent_t timingEvent[2];
cudaEvent_t event[1];
cudaEvent_t timingEvent[2];
struct hostData {
struct hostData
{
long long timeElapsed;
bool timeoutDetected;
bool timeoutDetected;
long long timeElapsed2;
bool timeoutDetected2;
bool timeoutDetected2;
LatchType latch;
LatchType latch2;
};
struct hostData *hostData;
__global__ void empty()
{
}
__global__ void empty() {}
// Function to read the GPU nanosecond timer in a kernel
__device__ __forceinline__ unsigned long long __globaltimer() {
unsigned long long globaltimer;
asm volatile ("mov.u64 %0, %globaltimer;" : "=l"(globaltimer));
return globaltimer;
__device__ __forceinline__ unsigned long long __globaltimer()
{
unsigned long long globaltimer;
asm volatile("mov.u64 %0, %globaltimer;" : "=l"(globaltimer));
return globaltimer;
}
__global__ void delay(long long ticks)
{
long long endTime = clock64() + ticks;
while (clock64() < endTime);
while (clock64() < endTime)
;
}
__global__ void waitWithTimeout(long long nanoseconds, bool* timeoutDetected, long long *timeElapsed, LatchType* latch)
__global__ void waitWithTimeout(long long nanoseconds, bool *timeoutDetected, long long *timeElapsed, LatchType *latch)
{
long long startTime = __globaltimer();
long long endTime = startTime + nanoseconds;
long long time = 0;
long long endTime = startTime + nanoseconds;
long long time = 0;
do {
time = __globaltimer();
} while (time < endTime && (latch == NULL || *latch == 0));
@@ -124,13 +121,9 @@ __global__ void waitWithTimeout(long long nanoseconds, bool* timeoutDetected, lo
}
}
__global__ void preUploadAnnotation()
{
}
__global__ void preUploadAnnotation() {}
__global__ void postUploadAnnotation()
{
}
__global__ void postUploadAnnotation() {}
cudaGraph_t createParallelChain(int length, int width, bool singleEntry = false)
{
@@ -138,9 +131,9 @@ cudaGraph_t createParallelChain(int length, int width, bool singleEntry = false)
RANGE("capture");
cudaGraph_t graph;
cudaStreamBeginCapture(stream[0], cudaStreamCaptureModeGlobal);
int streamIdx = 0;
int streamIdx = 0;
if (singleEntry) {
empty<<<1,1,0,stream[streamIdx]>>>();
empty<<<1, 1, 0, stream[streamIdx]>>>();
}
cudaEventRecord(event[0], stream[0]);
@@ -151,7 +144,7 @@ cudaGraph_t createParallelChain(int length, int width, bool singleEntry = false)
for (int i = 0; i < width; i++) {
streamIdx = i;
for (int j = 0; j < length; j++) {
empty<<<1,1,0,stream[streamIdx]>>>();
empty<<<1, 1, 0, stream[streamIdx]>>>();
}
}
@@ -164,10 +157,10 @@ cudaGraph_t createParallelChain(int length, int width, bool singleEntry = false)
return graph;
}
std::vector<const char*> metricName;
std::vector<float> metricValue;
std::vector<const char *> metricName;
std::vector<float> metricValue;
int counter2 = 0;
int counter2 = 0;
void runDemo(cudaGraph_t graph, int length, int width)
{
cudaGraphExec_t graphExec;
@@ -203,12 +196,13 @@ void runDemo(cudaGraph_t graph, int length, int width)
metricValue.push_back(getMicroSecondDuration(start, streamSync));
}
{
// re-instantiating the exec to simulate first launch into a busy stream.
// re-instantiating the exec to simulate first launch into a busy stream.
cudaGraphExecDestroy(graphExec);
cudaGraphInstantiateWithFlags(&graphExec, graph, 0);
long long maxTimeoutNanoSeconds = 4000 + 500*length*width;
waitWithTimeout<<<1,1,0,stream[0]>>>(maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
long long maxTimeoutNanoSeconds = 4000 + 500 * length * width;
waitWithTimeout<<<1, 1, 0, stream[0]>>>(
maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
RANGE("launch including upload in busy stream");
cudaEventRecord(timingEvent[0], stream[0]);
@@ -222,13 +216,14 @@ void runDemo(cudaGraph_t graph, int length, int width)
metricValue.push_back(getAsyncMicroSecondDuration(timingEvent[0], timingEvent[1]));
metricName.push_back("blockingKernelTimeoutDetected");
metricValue.push_back(hostData->timeoutDetected);
hostData->latch = 0;
hostData->latch = 0;
hostData->timeoutDetected = 0;
}
{
RANGE("repeat lauch in busy stream");
long long maxTimeoutNanoSeconds = 4000 + 500*length*width;
waitWithTimeout<<<1,1,0,stream[0]>>>(maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
long long maxTimeoutNanoSeconds = 4000 + 500 * length * width;
waitWithTimeout<<<1, 1, 0, stream[0]>>>(
maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
cudaEventRecord(timingEvent[0], stream[0]);
cudaGraphLaunch(graphExec, stream[0]);
cudaEventRecord(timingEvent[1], stream[0]);
@@ -240,34 +235,37 @@ void runDemo(cudaGraph_t graph, int length, int width)
metricValue.push_back(getAsyncMicroSecondDuration(timingEvent[0], timingEvent[1]));
metricName.push_back("blockingKernelTimeoutDetected");
metricValue.push_back(hostData->timeoutDetected);
hostData->latch = 0;
hostData->latch = 0;
hostData->timeoutDetected = 0;
}
{
// re-instantiating the exec to provide upload with work to do.
cudaGraphExecDestroy(graphExec);
cudaGraphInstantiateWithFlags(&graphExec, graph, 0);
long long maxTimeoutNanoSeconds = 4000 + 1000*length*width;
waitWithTimeout<<<1,1,0,stream[0]>>>(maxTimeoutNanoSeconds, &hostData->timeoutDetected2, &hostData->timeElapsed2, &hostData->latch2);
maxTimeoutNanoSeconds = 2000 + 500*length*width;
waitWithTimeout<<<1,1,0,stream[1]>>>(maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
long long maxTimeoutNanoSeconds = 4000 + 1000 * length * width;
waitWithTimeout<<<1, 1, 0, stream[0]>>>(
maxTimeoutNanoSeconds, &hostData->timeoutDetected2, &hostData->timeElapsed2, &hostData->latch2);
maxTimeoutNanoSeconds = 2000 + 500 * length * width;
waitWithTimeout<<<1, 1, 0, stream[1]>>>(
maxTimeoutNanoSeconds, &hostData->timeoutDetected, &hostData->timeElapsed, &hostData->latch);
RANGE("uploading a graph off of the critical path");
preUploadAnnotation<<<1,1,0,stream[1]>>>();
preUploadAnnotation<<<1, 1, 0, stream[1]>>>();
cudaEventRecord(timingEvent[0], stream[0]);
auto start = getCpuTime();
cudaGraphUpload(graphExec, stream[1]);
auto apiReturn = getCpuTime();
cudaEventRecord(event[0],stream[1]);
cudaEventRecord(event[0], stream[1]);
cudaEventRecord(timingEvent[1], stream[0]);
postUploadAnnotation<<<1,1,0,stream[1]>>>();
postUploadAnnotation<<<1, 1, 0, stream[1]>>>();
hostData->latch = 1; // release the blocking kernel for the upload
cudaStreamWaitEvent(stream[0],event[0]);
cudaStreamWaitEvent(stream[0], event[0]);
cudaGraphLaunch(graphExec, stream[0]);
cudaEventSynchronize(event[0]); // upload done, similuate critical path being ready for the graph to run by the release of the second latch
cudaEventSynchronize(event[0]); // upload done, similuate critical path being ready for the graph to run by the
// release of the second latch
hostData->latch2 = 1; // release the work
hostData->latch2 = 1; // release the work
cudaStreamSynchronize(stream[0]);
metricName.push_back("upload_api_time");
@@ -277,9 +275,9 @@ void runDemo(cudaGraph_t graph, int length, int width)
metricName.push_back("blockingKernelTimeoutDetected");
metricValue.push_back(hostData->timeoutDetected);
hostData->latch = 0;
hostData->latch2 = 0;
hostData->timeoutDetected = 0;
hostData->latch = 0;
hostData->latch2 = 0;
hostData->timeoutDetected = 0;
hostData->timeoutDetected2 = 0;
}
cudaGraphExecDestroy(graphExec);
@@ -287,7 +285,8 @@ void runDemo(cudaGraph_t graph, int length, int width)
RANGE_POP();
}
void usage() {
void usage()
{
printf("programName [outputFmt] [numTrials] [length] [width] [pattern] [stride] [maxLength] \n");
printf("\toutputFmt - program output, default=3 (see below)\n");
printf("\tnumTrials (per length)\n");
@@ -312,34 +311,36 @@ void usage() {
int main(int argc, char **argv)
{
if(argc < 1) {
if (argc < 1) {
usage();
return 0;
}
int numTrials=1, length=20, width=1, outputFmt=3, pattern=0, stride = 1;
if(argc > 1) outputFmt = atoi(argv[1]);
if(argc > 2) numTrials = atoi(argv[2]);
if(argc > 3) length= atoi(argv[3]);
if(argc > 4) width= atoi(argv[4]);
if(argc > 5) pattern = atoi(argv[5]);
if(argc > 6) stride = atoi(argv[6]);
int numTrials = 1, length = 20, width = 1, outputFmt = 3, pattern = 0, stride = 1;
if (argc > 1)
outputFmt = atoi(argv[1]);
if (argc > 2)
numTrials = atoi(argv[2]);
if (argc > 3)
length = atoi(argv[3]);
if (argc > 4)
width = atoi(argv[4]);
if (argc > 5)
pattern = atoi(argv[5]);
if (argc > 6)
stride = atoi(argv[6]);
int maxLength = length;
if(argc > 7) maxLength = atoi(argv[7]);
if (argc > 7)
maxLength = atoi(argv[7]);
if (maxLength < length) {
maxLength = length;
}
if((outputFmt & 4) && (outputFmt & 2)) {
if ((outputFmt & 4) && (outputFmt & 2)) {
printf("printing average and all samples doesn't make sense\n");
}
if(length == 0 ||
width == 0 ||
outputFmt == 0 ||
outputFmt > 5 ||
pattern > 1)
{
if (length == 0 || width == 0 || outputFmt == 0 || outputFmt > 5 || pattern > 1) {
usage();
return 0;
}
@@ -351,10 +352,10 @@ int main(int argc, char **argv)
cudaFree(0);
cudaMallocHost(&hostData, sizeof(*hostData));
int numStreams = width;
if (numStreams == 1) numStreams = 2; // demo needs two streams even if capture only needs 1.
if (numStreams == 1)
numStreams = 2; // demo needs two streams even if capture only needs 1.
stream.resize(numStreams);
for (int i = 0; i < numStreams; i++)
{
for (int i = 0; i < numStreams; i++) {
cudaStreamCreate(&stream[i]);
}
@@ -364,15 +365,14 @@ int main(int argc, char **argv)
{
RANGE("warmup");
for (int i = 0; i < width; i++)
{
empty<<<1,1,0,stream[i]>>>();
for (int i = 0; i < width; i++) {
empty<<<1, 1, 0, stream[i]>>>();
}
cudaStreamSynchronize(stream[0]);
auto start = getCpuTime();
graph = createParallelChain(length, width, singleEntry);
auto end = getCpuTime();
graph = createParallelChain(length, width, singleEntry);
auto end = getCpuTime();
metricValue.push_back(getMicroSecondDuration(start, end));
metricName.push_back("capture");
runDemo(graph, length, width);
@@ -382,7 +382,7 @@ int main(int argc, char **argv)
printf("length, width, pattern, ");
for (int i = 0; i < metricName.size(); i++) {
printf("%s, ", metricName[i]);
}
}
printf("\r\n");
}
@@ -390,7 +390,7 @@ int main(int argc, char **argv)
printf("skipping trials since no output is expected\n");
return 1;
}
std::vector<double> metricTotal;
metricTotal.resize(metricValue.size());
@@ -399,32 +399,32 @@ int main(int argc, char **argv)
metricName.clear();
metricValue.clear();
auto start = getCpuTime();
graph = createParallelChain(length, width, singleEntry);
auto end = getCpuTime();
graph = createParallelChain(length, width, singleEntry);
auto end = getCpuTime();
metricValue.push_back(getMicroSecondDuration(start, end));
runDemo(graph, length, width);
if (outputFmt & 2) {
printf("%d, %d, %d, ",length, width, pattern);
printf("%d, %d, %d, ", length, width, pattern);
for (int i = 0; i < metricValue.size(); i++) {
printf("%0.3f, ", metricValue[i]);
}
}
printf("\r\n");
}
if (outputFmt & 4) {
for (int i = 0; i < metricTotal.size(); i++) {
metricTotal[i] += metricValue[i];
}
}
}
}
if (outputFmt & 4) {
printf("%d, %d, %d, ",length, width, pattern);
printf("%d, %d, %d, ", length, width, pattern);
for (int i = 0; i < metricTotal.size(); i++) {
printf("%0.3f, ", metricTotal[i]/numTrials);
printf("%0.3f, ", metricTotal[i] / numTrials);
metricTotal[i] = 0;
}
}
printf("\r\n");
}

View File

@@ -42,9 +42,9 @@
namespace cg = cooperative_groups;
// Utilities and system includes
#include <helper_string.h> // helper for string parsing
#include <helper_image.h> // helper for image and data comparison
#include <helper_cuda.h> // helper for cuda error checking functions
#include <helper_cuda.h> // helper for cuda error checking functions
#include <helper_image.h> // helper for image and data comparison
#include <helper_string.h> // helper for string parsing
const char *sSDKsample = "Transpose";
@@ -53,20 +53,19 @@ const char *sSDKsample = "Transpose";
// TILE_DIM/BLOCK_ROWS elements. TILE_DIM must be an integral multiple of
// BLOCK_ROWS
#define TILE_DIM 32
#define TILE_DIM 32
#define BLOCK_ROWS 16
// This sample assumes that MATRIX_SIZE_X = MATRIX_SIZE_Y
int MATRIX_SIZE_X = 1024;
int MATRIX_SIZE_Y = 1024;
int MUL_FACTOR = TILE_DIM;
int MUL_FACTOR = TILE_DIM;
#define FLOOR(a, b) (a - (a % b))
// Compute the tile size necessary to illustrate performance cases for SM20+
// hardware
int MAX_TILES = (FLOOR(MATRIX_SIZE_X, 512) * FLOOR(MATRIX_SIZE_Y, 512)) /
(TILE_DIM * TILE_DIM);
int MAX_TILES = (FLOOR(MATRIX_SIZE_X, 512) * FLOOR(MATRIX_SIZE_Y, 512)) / (TILE_DIM * TILE_DIM);
// Number of repetitions used for timing. Two sets of repetitions are
// performed: 1) over kernel launches and 2) inside the kernel over just the
@@ -79,41 +78,42 @@ int MAX_TILES = (FLOOR(MATRIX_SIZE_X, 512) * FLOOR(MATRIX_SIZE_Y, 512)) /
// width and height must be integral multiples of TILE_DIM
// -------------------------------------------------------
__global__ void copy(float *odata, float *idata, int width, int height) {
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
__global__ void copy(float *odata, float *idata, int width, int height)
{
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index = xIndex + width * yIndex;
int index = xIndex + width * yIndex;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index + i * width] = idata[index + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index + i * width] = idata[index + i * width];
}
}
__global__ void copySharedMem(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM];
__global__ void copySharedMem(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM];
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index = xIndex + width * yIndex;
int index = xIndex + width * yIndex;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
if (xIndex < width && yIndex < height) {
tile[threadIdx.y][threadIdx.x] = idata[index];
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
if (xIndex < width && yIndex < height) {
tile[threadIdx.y][threadIdx.x] = idata[index];
}
}
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
if (xIndex < height && yIndex < width) {
odata[index] = tile[threadIdx.y][threadIdx.x];
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
if (xIndex < height && yIndex < width) {
odata[index] = tile[threadIdx.y][threadIdx.x];
}
}
}
}
// -------------------------------------------------------
@@ -121,71 +121,71 @@ __global__ void copySharedMem(float *odata, float *idata, int width,
// width and height must be integral multiples of TILE_DIM
// -------------------------------------------------------
__global__ void transposeNaive(float *odata, float *idata, int width,
int height) {
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
__global__ void transposeNaive(float *odata, float *idata, int width, int height)
{
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + width * yIndex;
int index_out = yIndex + height * xIndex;
int index_in = xIndex + width * yIndex;
int index_out = yIndex + height * xIndex;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i] = idata[index_in + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i] = idata[index_in + i * width];
}
}
// coalesced transpose (with bank conflicts)
__global__ void transposeCoalesced(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM];
__global__ void transposeCoalesced(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM];
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
}
// Coalesced transpose with no bank conflicts
__global__ void transposeNoBankConflicts(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM + 1];
__global__ void transposeNoBankConflicts(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM + 1];
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
}
// Transpose that effectively reorders execution of thread blocks along
@@ -200,44 +200,45 @@ __global__ void transposeNoBankConflicts(float *odata, float *idata, int width,
// blockIdx_y and replacement of blockIdx.x and bloclIdx.y with the subscripted
// versions in the remaining code
__global__ void transposeDiagonal(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM + 1];
__global__ void transposeDiagonal(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float tile[TILE_DIM][TILE_DIM + 1];
int blockIdx_x, blockIdx_y;
int blockIdx_x, blockIdx_y;
// do diagonal reordering
if (width == height) {
blockIdx_y = blockIdx.x;
blockIdx_x = (blockIdx.x + blockIdx.y) % gridDim.x;
} else {
int bid = blockIdx.x + gridDim.x * blockIdx.y;
blockIdx_y = bid % gridDim.y;
blockIdx_x = ((bid / gridDim.y) + blockIdx_y) % gridDim.x;
}
// do diagonal reordering
if (width == height) {
blockIdx_y = blockIdx.x;
blockIdx_x = (blockIdx.x + blockIdx.y) % gridDim.x;
}
else {
int bid = blockIdx.x + gridDim.x * blockIdx.y;
blockIdx_y = bid % gridDim.y;
blockIdx_x = ((bid / gridDim.y) + blockIdx_y) % gridDim.x;
}
// from here on the code is same as previous kernel except blockIdx_x replaces
// blockIdx.x and similarly for y
// from here on the code is same as previous kernel except blockIdx_x replaces
// blockIdx.x and similarly for y
int xIndex = blockIdx_x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx_y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
int xIndex = blockIdx_x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx_y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
xIndex = blockIdx_y * TILE_DIM + threadIdx.x;
yIndex = blockIdx_x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
xIndex = blockIdx_y * TILE_DIM + threadIdx.x;
yIndex = blockIdx_x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
tile[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = tile[threadIdx.x][threadIdx.y + i];
}
}
// --------------------------------------------------------------------
@@ -249,365 +250,369 @@ __global__ void transposeDiagonal(float *odata, float *idata, int width,
// components of a full transpose
// --------------------------------------------------------------------
__global__ void transposeFineGrained(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float block[TILE_DIM][TILE_DIM + 1];
__global__ void transposeFineGrained(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float block[TILE_DIM][TILE_DIM + 1];
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index = xIndex + (yIndex)*width;
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index = xIndex + (yIndex)*width;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
block[threadIdx.y + i][threadIdx.x] = idata[index + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
block[threadIdx.y + i][threadIdx.x] = idata[index + i * width];
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index + i * height] = block[threadIdx.x][threadIdx.y + i];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index + i * height] = block[threadIdx.x][threadIdx.y + i];
}
}
__global__ void transposeCoarseGrained(float *odata, float *idata, int width,
int height) {
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float block[TILE_DIM][TILE_DIM + 1];
__global__ void transposeCoarseGrained(float *odata, float *idata, int width, int height)
{
// Handle to thread block group
cg::thread_block cta = cg::this_thread_block();
__shared__ float block[TILE_DIM][TILE_DIM + 1];
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
int xIndex = blockIdx.x * TILE_DIM + threadIdx.x;
int yIndex = blockIdx.y * TILE_DIM + threadIdx.y;
int index_in = xIndex + (yIndex)*width;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
xIndex = blockIdx.y * TILE_DIM + threadIdx.x;
yIndex = blockIdx.x * TILE_DIM + threadIdx.y;
int index_out = xIndex + (yIndex)*height;
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
block[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
block[threadIdx.y + i][threadIdx.x] = idata[index_in + i * width];
}
cg::sync(cta);
cg::sync(cta);
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = block[threadIdx.y + i][threadIdx.x];
}
for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) {
odata[index_out + i * height] = block[threadIdx.y + i][threadIdx.x];
}
}
// ---------------------
// host utility routines
// ---------------------
void computeTransposeGold(float *gold, float *idata, const int size_x,
const int size_y) {
for (int y = 0; y < size_y; ++y) {
for (int x = 0; x < size_x; ++x) {
gold[(x * size_y) + y] = idata[(y * size_x) + x];
void computeTransposeGold(float *gold, float *idata, const int size_x, const int size_y)
{
for (int y = 0; y < size_y; ++y) {
for (int x = 0; x < size_x; ++x) {
gold[(x * size_y) + y] = idata[(y * size_x) + x];
}
}
}
}
void getParams(int argc, char **argv, cudaDeviceProp &deviceProp, int &size_x,
int &size_y, int max_tile_dim) {
// set matrix size (if (x,y) dim of matrix is not square, then this will have
// to be modified
if (checkCmdLineFlag(argc, (const char **)argv, "dimX")) {
size_x = getCmdLineArgumentInt(argc, (const char **)argv, "dimX");
void getParams(int argc, char **argv, cudaDeviceProp &deviceProp, int &size_x, int &size_y, int max_tile_dim)
{
// set matrix size (if (x,y) dim of matrix is not square, then this will have
// to be modified
if (checkCmdLineFlag(argc, (const char **)argv, "dimX")) {
size_x = getCmdLineArgumentInt(argc, (const char **)argv, "dimX");
if (size_x > max_tile_dim) {
printf("> MatrixSize X = %d is greater than the recommended size = %d\n",
size_x, max_tile_dim);
} else {
printf("> MatrixSize X = %d\n", size_x);
if (size_x > max_tile_dim) {
printf("> MatrixSize X = %d is greater than the recommended size = %d\n", size_x, max_tile_dim);
}
else {
printf("> MatrixSize X = %d\n", size_x);
}
}
} else {
size_x = max_tile_dim;
size_x = FLOOR(size_x, 512);
}
if (checkCmdLineFlag(argc, (const char **)argv, "dimY")) {
size_y = getCmdLineArgumentInt(argc, (const char **)argv, "dimY");
if (size_y > max_tile_dim) {
printf("> MatrixSize Y = %d is greater than the recommended size = %d\n",
size_y, max_tile_dim);
} else {
printf("> MatrixSize Y = %d\n", size_y);
else {
size_x = max_tile_dim;
size_x = FLOOR(size_x, 512);
}
if (checkCmdLineFlag(argc, (const char **)argv, "dimY")) {
size_y = getCmdLineArgumentInt(argc, (const char **)argv, "dimY");
if (size_y > max_tile_dim) {
printf("> MatrixSize Y = %d is greater than the recommended size = %d\n", size_y, max_tile_dim);
}
else {
printf("> MatrixSize Y = %d\n", size_y);
}
}
else {
size_y = max_tile_dim;
size_y = FLOOR(size_y, 512);
}
} else {
size_y = max_tile_dim;
size_y = FLOOR(size_y, 512);
}
}
void showHelp() {
printf("\n%s : Command line options\n", sSDKsample);
printf("\t-device=n (where n=0,1,2.... for the GPU device)\n\n");
printf("> The default matrix size can be overridden with these parameters\n");
printf("\t-dimX=row_dim_size (matrix row dimensions)\n");
printf("\t-dimY=col_dim_size (matrix column dimensions)\n");
void showHelp()
{
printf("\n%s : Command line options\n", sSDKsample);
printf("\t-device=n (where n=0,1,2.... for the GPU device)\n\n");
printf("> The default matrix size can be overridden with these parameters\n");
printf("\t-dimX=row_dim_size (matrix row dimensions)\n");
printf("\t-dimY=col_dim_size (matrix column dimensions)\n");
}
// ----
// main
// ----
int main(int argc, char **argv) {
// Start logs
printf("%s Starting...\n\n", sSDKsample);
int main(int argc, char **argv)
{
// Start logs
printf("%s Starting...\n\n", sSDKsample);
if (checkCmdLineFlag(argc, (const char **)argv, "help")) {
showHelp();
return 0;
}
int devID = findCudaDevice(argc, (const char **)argv);
cudaDeviceProp deviceProp;
// get number of SMs on this GPU
checkCudaErrors(cudaGetDevice(&devID));
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
// compute the scaling factor (for GPUs with fewer MPs)
float scale_factor, total_tiles;
scale_factor =
max((192.0f / (_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) *
(float)deviceProp.multiProcessorCount)),
1.0f);
printf("> Device %d: \"%s\"\n", devID, deviceProp.name);
printf("> SM Capability %d.%d detected:\n", deviceProp.major,
deviceProp.minor);
// Calculate number of tiles we will run for the Matrix Transpose performance
// tests
int size_x, size_y, max_matrix_dim, matrix_size_test;
matrix_size_test = 512; // we round down max_matrix_dim for this perf test
total_tiles = (float)MAX_TILES / scale_factor;
max_matrix_dim =
FLOOR((int)(floor(sqrt(total_tiles)) * TILE_DIM), matrix_size_test);
// This is the minimum size allowed
if (max_matrix_dim == 0) {
max_matrix_dim = matrix_size_test;
}
printf("> [%s] has %d MP(s) x %d (Cores/MP) = %d (Cores)\n", deviceProp.name,
deviceProp.multiProcessorCount,
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor),
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) *
deviceProp.multiProcessorCount);
printf("> Compute performance scaling factor = %4.2f\n", scale_factor);
// Extract parameters if there are any, command line -dimx and -dimy can
// override any of these settings
getParams(argc, argv, deviceProp, size_x, size_y, max_matrix_dim);
if (size_x != size_y) {
printf(
"\n[%s] does not support non-square matrices (row_dim_size(%d) != "
"col_dim_size(%d))\nExiting...\n\n",
sSDKsample, size_x, size_y);
exit(EXIT_FAILURE);
}
if (size_x % TILE_DIM != 0 || size_y % TILE_DIM != 0) {
printf(
"[%s] Matrix size must be integral multiple of tile "
"size\nExiting...\n\n",
sSDKsample);
exit(EXIT_FAILURE);
}
// kernel pointer and descriptor
void (*kernel)(float *, float *, int, int);
const char *kernelName;
// execution configuration parameters
dim3 grid(size_x / TILE_DIM, size_y / TILE_DIM),
threads(TILE_DIM, BLOCK_ROWS);
if (grid.x < 1 || grid.y < 1) {
printf("[%s] grid size computation incorrect in test \nExiting...\n\n",
sSDKsample);
exit(EXIT_FAILURE);
}
// CUDA events
cudaEvent_t start, stop;
// size of memory required to store the matrix
size_t mem_size = static_cast<size_t>(sizeof(float) * size_x * size_y);
if (2 * mem_size > deviceProp.totalGlobalMem) {
printf("Input matrix size is larger than the available device memory!\n");
printf("Please choose a smaller size matrix\n");
exit(EXIT_FAILURE);
}
// allocate host memory
float *h_idata = (float *)malloc(mem_size);
float *h_odata = (float *)malloc(mem_size);
float *transposeGold = (float *)malloc(mem_size);
float *gold;
// allocate device memory
float *d_idata, *d_odata;
checkCudaErrors(cudaMalloc((void **)&d_idata, mem_size));
checkCudaErrors(cudaMalloc((void **)&d_odata, mem_size));
// initialize host data
for (int i = 0; i < (size_x * size_y); ++i) {
h_idata[i] = (float)i;
}
// copy host data to device
checkCudaErrors(
cudaMemcpy(d_idata, h_idata, mem_size, cudaMemcpyHostToDevice));
// Compute reference transpose solution
computeTransposeGold(transposeGold, h_idata, size_x, size_y);
// print out common data for all kernels
printf(
"\nMatrix size: %dx%d (%dx%d tiles), tile size: %dx%d, block size: "
"%dx%d\n\n",
size_x, size_y, size_x / TILE_DIM, size_y / TILE_DIM, TILE_DIM, TILE_DIM,
TILE_DIM, BLOCK_ROWS);
// initialize events
checkCudaErrors(cudaEventCreate(&start));
checkCudaErrors(cudaEventCreate(&stop));
//
// loop over different kernels
//
bool success = true;
for (int k = 0; k < 8; k++) {
// set kernel pointer
switch (k) {
case 0:
kernel = &copy;
kernelName = "simple copy ";
break;
case 1:
kernel = &copySharedMem;
kernelName = "shared memory copy";
break;
case 2:
kernel = &transposeNaive;
kernelName = "naive ";
break;
case 3:
kernel = &transposeCoalesced;
kernelName = "coalesced ";
break;
case 4:
kernel = &transposeNoBankConflicts;
kernelName = "optimized ";
break;
case 5:
kernel = &transposeCoarseGrained;
kernelName = "coarse-grained ";
break;
case 6:
kernel = &transposeFineGrained;
kernelName = "fine-grained ";
break;
case 7:
kernel = &transposeDiagonal;
kernelName = "diagonal ";
break;
if (checkCmdLineFlag(argc, (const char **)argv, "help")) {
showHelp();
return 0;
}
// set reference solution
if (kernel == &copy || kernel == &copySharedMem) {
gold = h_idata;
} else if (kernel == &transposeCoarseGrained ||
kernel == &transposeFineGrained) {
gold = h_odata; // fine- and coarse-grained kernels are not full
// transposes, so bypass check
} else {
gold = transposeGold;
int devID = findCudaDevice(argc, (const char **)argv);
cudaDeviceProp deviceProp;
// get number of SMs on this GPU
checkCudaErrors(cudaGetDevice(&devID));
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
// compute the scaling factor (for GPUs with fewer MPs)
float scale_factor, total_tiles;
scale_factor = max(
(192.0f / (_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) * (float)deviceProp.multiProcessorCount)),
1.0f);
printf("> Device %d: \"%s\"\n", devID, deviceProp.name);
printf("> SM Capability %d.%d detected:\n", deviceProp.major, deviceProp.minor);
// Calculate number of tiles we will run for the Matrix Transpose performance
// tests
int size_x, size_y, max_matrix_dim, matrix_size_test;
matrix_size_test = 512; // we round down max_matrix_dim for this perf test
total_tiles = (float)MAX_TILES / scale_factor;
max_matrix_dim = FLOOR((int)(floor(sqrt(total_tiles)) * TILE_DIM), matrix_size_test);
// This is the minimum size allowed
if (max_matrix_dim == 0) {
max_matrix_dim = matrix_size_test;
}
// Clear error status
checkCudaErrors(cudaGetLastError());
printf("> [%s] has %d MP(s) x %d (Cores/MP) = %d (Cores)\n",
deviceProp.name,
deviceProp.multiProcessorCount,
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor),
_ConvertSMVer2Cores(deviceProp.major, deviceProp.minor) * deviceProp.multiProcessorCount);
// warmup to avoid timing startup
kernel<<<grid, threads>>>(d_odata, d_idata, size_x, size_y);
printf("> Compute performance scaling factor = %4.2f\n", scale_factor);
// take measurements for loop over kernel launches
checkCudaErrors(cudaEventRecord(start, 0));
// Extract parameters if there are any, command line -dimx and -dimy can
// override any of these settings
getParams(argc, argv, deviceProp, size_x, size_y, max_matrix_dim);
for (int i = 0; i < NUM_REPS; i++) {
kernel<<<grid, threads>>>(d_odata, d_idata, size_x, size_y);
// Ensure no launch failure
checkCudaErrors(cudaGetLastError());
if (size_x != size_y) {
printf("\n[%s] does not support non-square matrices (row_dim_size(%d) != "
"col_dim_size(%d))\nExiting...\n\n",
sSDKsample,
size_x,
size_y);
exit(EXIT_FAILURE);
}
checkCudaErrors(cudaEventRecord(stop, 0));
checkCudaErrors(cudaEventSynchronize(stop));
float kernelTime;
checkCudaErrors(cudaEventElapsedTime(&kernelTime, start, stop));
checkCudaErrors(
cudaMemcpy(h_odata, d_odata, mem_size, cudaMemcpyDeviceToHost));
bool res = compareData(gold, h_odata, size_x * size_y, 0.01f, 0.0f);
if (res == false) {
printf("*** %s kernel FAILED ***\n", kernelName);
success = false;
if (size_x % TILE_DIM != 0 || size_y % TILE_DIM != 0) {
printf("[%s] Matrix size must be integral multiple of tile "
"size\nExiting...\n\n",
sSDKsample);
exit(EXIT_FAILURE);
}
// take measurements for loop inside kernel
checkCudaErrors(
cudaMemcpy(h_odata, d_odata, mem_size, cudaMemcpyDeviceToHost));
res = compareData(gold, h_odata, size_x * size_y, 0.01f, 0.0f);
// kernel pointer and descriptor
void (*kernel)(float *, float *, int, int);
const char *kernelName;
if (res == false) {
printf("*** %s kernel FAILED ***\n", kernelName);
success = false;
// execution configuration parameters
dim3 grid(size_x / TILE_DIM, size_y / TILE_DIM), threads(TILE_DIM, BLOCK_ROWS);
if (grid.x < 1 || grid.y < 1) {
printf("[%s] grid size computation incorrect in test \nExiting...\n\n", sSDKsample);
exit(EXIT_FAILURE);
}
// report effective bandwidths
float kernelBandwidth = 2.0f * 1000.0f * mem_size / (1024 * 1024 * 1024) /
(kernelTime / NUM_REPS);
printf(
"transpose %s, Throughput = %.4f GB/s, Time = %.5f ms, Size = %u fp32 "
"elements, NumDevsUsed = %u, Workgroup = %u\n",
kernelName, kernelBandwidth, kernelTime / NUM_REPS, (size_x * size_y),
1, TILE_DIM * BLOCK_ROWS);
}
// CUDA events
cudaEvent_t start, stop;
// cleanup
free(h_idata);
free(h_odata);
free(transposeGold);
cudaFree(d_idata);
cudaFree(d_odata);
// size of memory required to store the matrix
size_t mem_size = static_cast<size_t>(sizeof(float) * size_x * size_y);
checkCudaErrors(cudaEventDestroy(start));
checkCudaErrors(cudaEventDestroy(stop));
if (2 * mem_size > deviceProp.totalGlobalMem) {
printf("Input matrix size is larger than the available device memory!\n");
printf("Please choose a smaller size matrix\n");
exit(EXIT_FAILURE);
}
if (!success) {
printf("Test failed!\n");
exit(EXIT_FAILURE);
}
// allocate host memory
float *h_idata = (float *)malloc(mem_size);
float *h_odata = (float *)malloc(mem_size);
float *transposeGold = (float *)malloc(mem_size);
float *gold;
printf("Test passed\n");
exit(EXIT_SUCCESS);
// allocate device memory
float *d_idata, *d_odata;
checkCudaErrors(cudaMalloc((void **)&d_idata, mem_size));
checkCudaErrors(cudaMalloc((void **)&d_odata, mem_size));
// initialize host data
for (int i = 0; i < (size_x * size_y); ++i) {
h_idata[i] = (float)i;
}
// copy host data to device
checkCudaErrors(cudaMemcpy(d_idata, h_idata, mem_size, cudaMemcpyHostToDevice));
// Compute reference transpose solution
computeTransposeGold(transposeGold, h_idata, size_x, size_y);
// print out common data for all kernels
printf("\nMatrix size: %dx%d (%dx%d tiles), tile size: %dx%d, block size: "
"%dx%d\n\n",
size_x,
size_y,
size_x / TILE_DIM,
size_y / TILE_DIM,
TILE_DIM,
TILE_DIM,
TILE_DIM,
BLOCK_ROWS);
// initialize events
checkCudaErrors(cudaEventCreate(&start));
checkCudaErrors(cudaEventCreate(&stop));
//
// loop over different kernels
//
bool success = true;
for (int k = 0; k < 8; k++) {
// set kernel pointer
switch (k) {
case 0:
kernel = &copy;
kernelName = "simple copy ";
break;
case 1:
kernel = &copySharedMem;
kernelName = "shared memory copy";
break;
case 2:
kernel = &transposeNaive;
kernelName = "naive ";
break;
case 3:
kernel = &transposeCoalesced;
kernelName = "coalesced ";
break;
case 4:
kernel = &transposeNoBankConflicts;
kernelName = "optimized ";
break;
case 5:
kernel = &transposeCoarseGrained;
kernelName = "coarse-grained ";
break;
case 6:
kernel = &transposeFineGrained;
kernelName = "fine-grained ";
break;
case 7:
kernel = &transposeDiagonal;
kernelName = "diagonal ";
break;
}
// set reference solution
if (kernel == &copy || kernel == &copySharedMem) {
gold = h_idata;
}
else if (kernel == &transposeCoarseGrained || kernel == &transposeFineGrained) {
gold = h_odata; // fine- and coarse-grained kernels are not full
// transposes, so bypass check
}
else {
gold = transposeGold;
}
// Clear error status
checkCudaErrors(cudaGetLastError());
// warmup to avoid timing startup
kernel<<<grid, threads>>>(d_odata, d_idata, size_x, size_y);
// take measurements for loop over kernel launches
checkCudaErrors(cudaEventRecord(start, 0));
for (int i = 0; i < NUM_REPS; i++) {
kernel<<<grid, threads>>>(d_odata, d_idata, size_x, size_y);
// Ensure no launch failure
checkCudaErrors(cudaGetLastError());
}
checkCudaErrors(cudaEventRecord(stop, 0));
checkCudaErrors(cudaEventSynchronize(stop));
float kernelTime;
checkCudaErrors(cudaEventElapsedTime(&kernelTime, start, stop));
checkCudaErrors(cudaMemcpy(h_odata, d_odata, mem_size, cudaMemcpyDeviceToHost));
bool res = compareData(gold, h_odata, size_x * size_y, 0.01f, 0.0f);
if (res == false) {
printf("*** %s kernel FAILED ***\n", kernelName);
success = false;
}
// take measurements for loop inside kernel
checkCudaErrors(cudaMemcpy(h_odata, d_odata, mem_size, cudaMemcpyDeviceToHost));
res = compareData(gold, h_odata, size_x * size_y, 0.01f, 0.0f);
if (res == false) {
printf("*** %s kernel FAILED ***\n", kernelName);
success = false;
}
// report effective bandwidths
float kernelBandwidth = 2.0f * 1000.0f * mem_size / (1024 * 1024 * 1024) / (kernelTime / NUM_REPS);
printf("transpose %s, Throughput = %.4f GB/s, Time = %.5f ms, Size = %u fp32 "
"elements, NumDevsUsed = %u, Workgroup = %u\n",
kernelName,
kernelBandwidth,
kernelTime / NUM_REPS,
(size_x * size_y),
1,
TILE_DIM * BLOCK_ROWS);
}
// cleanup
free(h_idata);
free(h_odata);
free(transposeGold);
cudaFree(d_idata);
cudaFree(d_odata);
checkCudaErrors(cudaEventDestroy(start));
checkCudaErrors(cudaEventDestroy(stop));
if (!success) {
printf("Test failed!\n");
exit(EXIT_FAILURE);
}
printf("Test passed\n");
exit(EXIT_SUCCESS);
}