CUDA性能优化系列——Kmeans算法调优(三)

本文探讨了四种不同的GPU计算调度方式:单流同步调用、单流异步调用、多流执行(通过OpenMP开启4个线程,每个线程持有一个流)以及多流执行但不添加事件同步。通过对KMeans算法的实现,比较了它们的执行时间和内存拷贝overlap效果。结果显示,多流执行并添加事件同步的方式能有效提高执行效率,但执行结果稳定性有待提升。

本篇对调度方式进行优化,实现内存拷贝和计算overlap。

单流同步调用

/*
单流同步
*/
void CallKmeansSync()
{
   
   
	//TODO:init host memory
	float* h_Src/*[Coords][SrcCount]*/,
		* h_Clusters/*[ClusterCount][Coords]*/;
	int* h_MemberShip/*[kSrcCount]*/,
		* h_MemberCount/*[kClusterCount]*/;
	h_Src = (float*)malloc(kSrcCount * kCoords * sizeof(float));
	h_Clusters = (float*)malloc(kClusterCount * kCoords * sizeof(float));/*[kClusterCount][kCoords]*/
	for (size_t i = 0; i < kClusterCount; i++)
	{
   
   
		for (int j = 0; j < kCoords; ++j)
		{
   
   
			h_Clusters[i * kCoords + j] = float(100 * i + 10);
		}
	}
	for (size_t i = 0; i < kClusterCount; i++)
	{
   
   
		for (size_t j = 0; j < (kSrcCount / kClusterCount); j++)
		{
   
   
			for (size_t iDim = 0; iDim < kCoords; iDim++)
			{
   
   
				h_Src[iDim * kSrcCount + i * (kSrcCount / kClusterCount) + j] = i * 100 + 0.5f;// -(float)((rand() % 100) / 100);
			}
		}
	}
	h_MemberShip = (int*)malloc(kSrcCount * sizeof(int));
	memset(h_MemberShip, 9, kSrcCount * sizeof(int));
	h_MemberCount = (int*)malloc(kClusterCount * sizeof(int));
	memset(h_MemberCount, 0, kClusterCount * sizeof(int));

	//TODO:init device mempry
	float* d_pSrc/*[Coords][SrcCount]*/,
		* d_pClusters/*[ClusterCount][Coords]*/;
	int* d_pChanged/*[1]*/,
		* d_pMemberShip/*[kSrcCount]*/,
		* d_pMemberCount/*[kClusterCount]*/;
	cudaMalloc(&d_pSrc, kSrcCount * kCoords * sizeof(float));
	cudaMemcpy(d_pSrc, h_Src, kSrcCount * kCoords * sizeof(float), cudaMemcpyHostToDevice);

	cudaMalloc(&d_pClusters, kClusterCount * kCoords * sizeof(float));


	cudaMalloc(&d_pMemberShip, kSrcCount * sizeof(int));
	cudaMemcpy(d_pMemberShip, h_MemberShip, kSrcCount * sizeof(int), cudaMemcpyHostToDevice);

	cudaMalloc(&d_pMemberCount, kClusterCount * sizeof(int));
	cudaMemcpy(d_pMemberCount, h_MemberCount, kClusterCount * sizeof(int), cudaMemcpyHostToDevice);

	cudaMalloc(&d_pChanged, sizeof(int));

	//TODO:find the points
	int itCount = 0;
	int iChanged = 0;
	cudaStream_t sMember, sNewCluster;
	cudaStreamCreate(&sMember);
	cudaStreamCreate(&sNewCluster);
	cudaEvent_t eMember, eNewCluster;
	cudaEventCreate(&eMember);
	cudaEventCreate(&eNewCluster);
	do
	{
   
   
		{
   
   
			const int UnrollScale = 8;
			cudaMemcpy(d_pClusters, h_Clusters, kClusterCount * kCoords * sizeof(float), cudaMemcpyHostToDevice);//s1
			cudaMemset(d_pMemberCount, 0, kClusterCount * sizeof(int));//s1
			kKmeansClassifyMembershipMath<kCoords, UnrollScale, kClusterCount, kClusterUnroll> << <kGridCount * 1, kBlockSize / UnrollScale, kClusterCount* kCoords * sizeof(float) >> >
				(kSrcCount, kCoords, kClusterCount, d_pSrc, d_pMemberShip, d_pClusters, d_pChanged);
			checkCUDARtn(cudaDeviceSynchronize());
			cudaMemcpy(&iChanged, d_pChanged, sizeof(int), cudaMemcpyDeviceToHost);//s1
			cudaMemset(d_pChanged, 0, sizeof(int));//s1
			cudaMemset(d_pClusters, 0, kClusterCount * kCoords * sizeof(float));//s2
			const int kUnrollAdvUNROLLSCALE = 16;
			kkKmeansUnrollAdv<kBlockSize / kExecuteScale, kUnrollAdvUNROLLSCALE, kCoords, kClusterCount> << <kGridCount * kExecuteScale / kUnrollAdvUNROLLSCALE, kBlockSize / kExecuteScale, (kBlockSize * kCoords + kBlockSize) * sizeof(float) / kExecuteScale >> > (kSrcCount, kCoords, kClusterCount, d_pSrc, d_pMemberShip, d_pClusters, d_pMemberCount);//s2
			checkCUDARtn(cudaDeviceSynchronize());
			cudaMemcpy(h_MemberCount, d_pMemberCount, kClusterCount * sizeof(int), cudaMemcpyDeviceToHost);//s2
			cudaMemcpy(h_Clusters, d_pClusters, kClusterCount * kCoords * sizeof(float), cudaMemcpyDeviceToHost);//s2
			for (size_t i = 0; i < kClusterCount; i++)//s2
			{
   
   
				for (int j = 0; j < kCoords; ++j)
				{
   
   
					h_Clusters[i * kCoords + j] = (h_MemberCount[i] == 0) ? 0 : (h_Clusters[i * kCoords + j] / h_MemberCount[i]);
				}
			}
		}
	} while (/*(0 != iChanged) &&*/ itCount++ < 8);
	std::cout << "it count " << itCount << std::endl;
	cudaEventDestroy(eMember);
	cudaEventDestroy(eNewCluster);
	cudaStreamDestroy(sMember);
	cudaStreamDestroy(sNewCluster);
	free(h_Src);
	free(h_Clusters);
	free(h_MemberShip);
	free(h_MemberCount);
	cudaFree(d_pSrc);
	cudaFree(d_pClusters);
	cudaFree(d_pChanged);
	cudaFree(d_pMemberShip);
	cudaFree(d_pMemberCount);
}

void CallFunckmeans(int FuncIndex)
{
   
   
	const int kSingleStm = 0;
	const int kMultStm = 1;
	const int kPrevCopy = 2;
	const int kNonSync = 3;
	const int kSync = 4;
	if (FuncIndex == kSingleStm)
	{
   
   
		CallKmeansSingleStm();
	}
	else if (FuncIndex == kMultStm)
	{
   
   
		CallKmeansMultOverlap();
	}
	else if (FuncIndex == kPrevCopy)
	{
   
   
		CallKmeansMultPrevCopy();
	}
	else if (FuncIndex == kNonSync)
	{
   
   
		CallKmeansMultNonSync();
	}
	else if (FuncIndex == kSync)
	{
   
   
		CallKmeansSync();
	}
	return;
}

调度情况
在这里插入图片描述
总耗时7ms,可以看到核函数之间由于同步没有连续执行

单流异步调用

在这里插入代码片/*
单流异步
*/
void CallKmeansSingleStm()
{
   
   

	//TODO:init host memory
	float* h_Src/*[Coords][SrcCount]*/,
		* h_Clusters/*[ClusterCount][Coords]*/;
	int* h_MemberShip/*[kSrcCount]*/,
		* h_MemberCount/*[kClusterCount]*/;
	//h_Src = (float*)malloc(kSrcCount * kCoords * sizeof(float));
	cudaMallocHost(&h_Src, kSrcCount * kCoords * sizeof(float));
	h_Clusters = (float*)malloc(kClusterCount * kCoords * sizeof(float));/*[kClusterCount][kCoords]*/
	for (size_t i = 0; i < kClusterCount; i++)
	{
   
   
		for (int j = 0; j < kCoords; ++j)
		{
   
   
			h_Clusters[i * kCoords + j] = float(100 * i + 10);
		}
	}
	for (size_t i = 0; i < kClusterCount; i++)
	{
   
   
		for (size_t j = 0; j < (kSrcCount / kClusterCount); j++)
		{
   
   
			for (size_t iDim = 0; iDim < kCoords; iDim++)
			{
   
   
				h_Src[iDim * kSrcCount + i * (kSrcCount / kClusterCount) + j] = i * 100 + 0.5f;// -(float)((rand() % 100) / 100);
			}
		}
	}
	//h_MemberShip = (int*)malloc(kSrcCount * sizeof(int));
	cudaMallocHost(&h_MemberShip, kSrcCount * sizeof(int));
	memset(h_MemberShip, 9, kSrcCount * sizeof(int));
	h_MemberCount = (int*)malloc(kClusterCount * sizeof(int));
	memset(h_MemberCount, 0, kClusterCount * sizeof(int));

	//TODO:init stream
	cudaStream_t stm;
	cudaStreamCreate(&stm);
	const int EventNum = 10;
	cudaEvent_t event[EventNum];
	for (size_t i = 0; i < EventNum; i++)
	{
   
   
		cudaEventCreate(&event[i]);
	}

	//TODO:init device mempry
	float* d_pSrc/*[Coords][SrcCount]*/,
		* d_pClusters/*[ClusterCount][Coords]*/;
	int* d_pChanged/*[1]*/,
		* d_pMemberShip/*[kSrcCount]*/,
		* d_pMemberCount/*[kClusterCount]*/;
	cudaMalloc(&d_pSrc, kSrcCount * kCoords * sizeof(float));
	cudaMalloc(&d_pClusters, kClusterCount * k
请你根据我发给你的pdf文件写一份实验报告,实验报告要求内容丰富完整。实验模板如下所示: 实验名称: 聚类算法的应用 实验时间: 2025 年 9 月 25 日 第 四 周 星期 四 一、实验预习 1、实验目的 1)理解聚类方面的基本概念和基础知识 2) 掌握数据聚类基本的方法 3) 熟练掌握利用 MATLAB 进行 Kmeans 聚类算法的步骤 4) 实现 kmeans 聚类算法 2、实验内容 1) 数据准备; 2) 对于未聚类数据集,首先从数据集D中随机选择 k样本作为初始的k 个质心; 3) 求出每个样本到中心点的距离,按照距离自身最近的中心点进行第一次聚类; 4) 依据上次聚类结果,求出新的中心点(新簇内点 x 和 y 的平均值); 5) 反复迭代,直到中心点的变化满足收敛条件(变化很小或几乎不变化), 最终得到聚类结果。 3、硬、软件环境 计算机要求:CPU为2.3GHz Intel Core i5及以上, 内存6G,操作系统为Windows 10;显卡性能尽量好, Python版本为3.7,cuda、PyTorch版本根据电脑配置自行选择相应的版本。 实验中用到的具体依赖包 • PyTorch • Tensorflow 1.10 • Python 3.7 4、实验预备工作 掌握Kmeans 聚类算法的基本方法和推导过程。 预习评价 教师签名 二、实验报告 1、实验步骤 输入是样本集 D={x1,x2 ,...xm }聚类的簇树 k,最大迭代次数 N 输出是簇划分 C={C1,C2 ,...Ck} 1) 从数据集 D 中随机选择 k 个样本作为初始的 k 个质心向量: { μ1, μ2 ,... μk } 2) 对于 n=1,2,...,N a) 将簇划分 C 初始化为Ct = ∅ t = 1,2,3…, k b) 对于 i=1,2...m,计算样本xi和各个质心向量μj (j=1,2,...k)的距离: ,将xi标记最小的为dij所对应的类别λiλi。此时更新 Cλi= Cλi ∪{xi} c) 对于 j=1,2,…,k,对Cj中所有的样本点重新计算新的质心 d) 如果所有的 k 个质心向量都没有发生变化,则转到步骤 3) 3) 输出簇划分C={C1,C2 ,...Ck} 一般来说,经典 k-means 算法有以下几个特点: 1) 需要提前确定 k 值; 2) 对初始质心点敏感; 3) 对异常数据敏感。 K-means 原生存在的一些问题: 1) k 均值算法的目标函数化过程是单非增的(每次的迭代至少不会让 结果更糟),但是 k 均值算法本身对达到收敛的迭代次数没有给出理论 保证。 2) 算法给出的 k 均值目标函数输出值和目标函数的最小可能值之差,没有 平凡下界,实际上,k 均值可能会收敛到局部最小值。为了提高 k 均值 的结果,通常使用不同的随机初始化中心点,将该程序运行多次,并选 取最的结果。除此之外,有一些无监督的算法可以作为 k 均值算法的 前置算法,用来选取初始化中心。 3) 在训练集上根据距离平方总和标准得到的“最佳聚类”,将不可避免地选择与数据点一样多的聚类。因为此时损失为 0,为了抑制这种倾向, 需要应用 MDL 准则进行模型结构复杂性惩罚,在模型复杂度和损失目 标最化之间寻求一个平衡。 2、实验结果(包括程序运行结果、实测数据结果、数据分析等) 程序代码 运行示例 3、实验结论 参考文献 成绩评定 教师签名
最新发布
10-03
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值