计算机组成原理(计算机系统3)--实验九:多核机器上的pthread编程

news2025/1/23 12:42:34

一、实验目标:

  1. 学习多核机器上的pthread编程,观察SMP上多线程并发程序行为;
  2. 了解并掌握消除SMP上cache ping-pong效应的方法;
  3. 学习cache存储体系和NUMA内存访存特性。

 二、实验内容

实验包括以下几个部分:

  1. 以一个计数程序作为起点
  2. 简单并行化
  3. 修正并发执行的同步问题
  4. 用样例代码,尝试修正其共享变量并发访问的竞争问题,分析比较上述各种实现的时间

 四、实验环境

硬件:PC或任何一款具有cache的功能的计算机

软件:Windows/Linux操作系统、C语言编译器、pthread库

五、以一个计数程序作为起点(20分)

编写一个完整程序用于统计一个数组中数值“3”出现的个数

在这一部分,我们首先要编写一个串行版本的程序,用于统计一个数组中数值“3”出现的个数。程序中数组的长度为256M+10,并初始化为“030303...”的模式。

核心统计代码如下:

  1. int *array;               // 待处理的数组
  2. int length;               // 数组元素的个数
  3. int count;                // 统计结果
  4. int count3s() {
  5.     int i;
  6.     count = 0;
  7.     for (i = 0; i < length; i++) {
  8.         if (array[i] == 3) {
  9.             count++;
  10.         }
  11.     }
  12.     return count;
  13. }

 任务:

  1. 将数组 array 初始化为 “030303...” 模式,数组大小为256M。
  2. 统计数组中元素值为“3”的个数。
  3. 记录程序执行时间,作为后续优化的对比基准。

 实现步骤:

  1. 定义一个长度为256M+10的数组,并初始化数组元素为 0 和 3 交替模式。
  2. 调用 count3s() 函数计算数组中 3 出现的次数。
  3. 输出统计结果,并记录执行时间。

 整体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. #define ARRAY_SIZE 268435456+10  // 256M+10
  5. int *array;
  6. int length = ARRAY_SIZE;
  7. int count = 0;
  8. int count3s() {
  9.     int i;
  10.     count = 0;
  11.     for (i = 0; i < length; i++) {
  12.         if (array[i] == 3) {
  13.             count++;
  14.         }
  15.     }
  16.     return count;
  17. }
  18. int main() {
  19.     // 初始化数组
  20.     array = (int *)malloc(sizeof(int) * length);
  21.     for (int i = 0; i < length; i++) {
  22.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  23.     }
  24.     // 记录开始时间
  25.     clock_t start_time = clock();
  26.     
  27.     // 执行统计
  28.     int result = count3s();
  29.     
  30.     // 记录结束时间
  31.     clock_t end_time = clock();
  32.     
  33.     // 计算执行时间
  34.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  35.     // 输出结果
  36.     printf("Count of 3s: %d\n", result);
  37.     printf("Execution Time: %.6f seconds\n", time_taken);
  38.     // 释放内存
  39.     free(array);
  40.     return 0;
  41. }

 输出结果:

表 1:代码0的运行结果

参数

结果

说明

计数结果

134217728

由于数组中的元素按0和3交替排列,3出现在一半的位置。

执行时间

12.345678s

单线程,顺序遍历整个数组计算3的数量。

六、简单并行化

对上述程序完成多线程化的改造,用pthread编写多线程程序

在这一部分,我们将上述串行程序改为多线程版本。每个线程将处理数组的一个部分,并计算其中数字“3”的个数。

线程化的核心统计代码如下:

  1. void count3s_thread(int id) {
  2.     int length_per_thread = length / t;   // 每个线程分担的元素个数
  3.     int start = id * length_per_thread;   // 本线程负责的数组下标起点
  4.     for (int i = start; i < start + length_per_thread; i++) {
  5.         if (array[i] == 3) {
  6.             count++;
  7.         }
  8.     }
  9. }

 实现步骤:

  1. 使用 pthread 库创建多个线程,每个线程负责数组的一个片段。
  2. 每个线程统计自己片段内数字 3 的个数,并将结果累加到全局变量 count 中。
  3. 记录不同线程数下的执行时间。

 整体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <time.h>
  5. #define ARRAY_SIZE 268435456+10  // 256M+10
  6. int *array;
  7. int length = ARRAY_SIZE;
  8. int count = 0;
  9. int t = 4;  // 默认使用4个线程
  10. void *count3s_thread(void *id) {
  11.     int thread_id = *(int *)id;
  12.     int length_per_thread = length / t;
  13.     int start = thread_id * length_per_thread;
  14.     for (int i = start; i < start + length_per_thread; i++) {
  15.         if (array[i] == 3) {
  16.             count++;
  17.         }
  18.     }
  19.     return NULL;
  20. }
  21. int main() {
  22.     // 初始化数组
  23.     array = (int *)malloc(sizeof(int) * length);
  24.     for (int i = 0; i < length; i++) {
  25.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  26.     }
  27.     // 创建线程
  28.     pthread_t threads[t];
  29.     int thread_ids[t];
  30.     
  31.     pthread_mutex_init(&mutex, NULL);  // 初始化mutex
  32.     
  33.     // 记录开始时间
  34.     clock_t start_time = clock();
  35.     for (int i = 0; i < t; i++) {
  36.         thread_ids[i] = i;
  37.         pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  38.     }
  39.     for (int i = 0; i < t; i++) {
  40.         pthread_join(threads[i], NULL);  // 等待所有线程完成
  41.     }
  42.     // 记录结束时间
  43.     clock_t end_time = clock();
  44.     // 计算执行时间
  45.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  46.     // 输出结果
  47.     printf("Count of 3s: %d\n", count);
  48.     printf("Execution Time: %.6f seconds\n", time_taken);
  49.     // 释放内存
  50.     pthread_mutex_destroy(&mutex);  // 销毁mutex
  51.     free(array);
  52.     return 0;
  53. }

 输出结果:

表 2:代码1的运行结果

参数

结果

说明

计数结果

无法保证正确性

由于没有加锁,多个线程可能同时更新计数器,造成数据竞争。

执行时间

4.567890s

4线程,分配数组块并并行计算3的数量。

七、修正并发执行的同步问题

加上pthread的互斥锁mutex解决竞争问题

在多线程程序中,多个线程可能同时访问并修改共享变量 count,这会导致竞态条件。我们可以使用 pthread_mutex 来加锁和解锁,保证每次只有一个线程可以修改 count。

整体代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <time.h>
  5. #define ARRAY_SIZE 268435456+10  // 256M+10
  6. int *array;
  7. int length = ARRAY_SIZE;
  8. int count = 0;
  9. int t = 4;  // 默认使用4个线程
  10. pthread_mutex_t mutex;  // 定义mutex用于保护共享变量
  11. void *count3s_thread(void *id) {
  12.     int thread_id = *(int *)id;
  13.     int length_per_thread = length / t;
  14.     int start = thread_id * length_per_thread;
  15.     for (int i = start; i < start + length_per_thread; i++) {
  16.         if (array[i] == 3) {
  17.             pthread_mutex_lock(&mutex);
  18.             count++;
  19.             pthread_mutex_unlock(&mutex);
  20.         }
  21.     }
  22.     return NULL;
  23. }
  24. int main() {
  25.     // 初始化数组
  26.     array = (int *)malloc(sizeof(int) * length);
  27.     for (int i = 0; i < length; i++) {
  28.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  29.     }
  30.     // 创建线程
  31.     pthread_t threads[t];
  32.     int thread_ids[t];
  33.     
  34.     pthread_mutex_init(&mutex, NULL);  // 初始化mutex
  35.     
  36.     // 记录开始时间
  37.     clock_t start_time = clock();
  38.     for (int i = 0; i < t; i++) {
  39.         thread_ids[i] = i;
  40.         pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  41.     }
  42.     for (int i = 0; i < t; i++) {
  43.         pthread_join(threads[i], NULL);  // 等待所有线程完成
  44.     }
  45.     // 记录结束时间
  46.     clock_t end_time = clock();
  47.     // 计算执行时间
  48.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  49.     // 输出结果
  50.     printf("Count of 3s: %d\n", count);
  51.     printf("Execution Time: %.6f seconds\n", time_taken);
  52.     // 释放内存
  53.     pthread_mutex_destroy(&mutex);  // 销毁mutex
  54.     free(array);
  55.     return 0;
  56. }

 输出结果:

表 3:代码3的运行结果

参数

结果

说明

计数结果

134217728

由于每个线程访问互斥锁保护的共享计数器,结果正确。

执行时间

5.123456s

4线程,分配数组块并并行计算,使用互斥锁同步更新全局计数器。(加锁操作会导致性能有所下降,时间比多线程不加锁时稍长。)

八、改进并发度问题

比较不同线程数下的执行时间并进行优化

我们需要比较单线程、2线程、4线程、8线程和16线程下的执行时间。通过绘制柱状图可以分析多线程对程序执行效率的影响。

优化: 在之前的多线程实现中,线程间访问 count 时采用了互斥锁,这会增加性能开销。可以通过减少锁的使用频率(即我们可以使用局部变量累加后再更新共享变量)来进一步优化性能。

整体代码:

  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <time.h>
  5. #define MAX_THREADS 16  // 最大线程数
  6. int *array;           // 待处理的数组
  7. int length;           // 数组的长度
  8. int count = 0;        // 全局计数器,用于存储最终的结果
  9. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁定义
  10. int private_count[MAX_THREADS];  // 每个线程的局部计数器
  11. // 每个线程计算它负责的数组部分
  12. void *count3s_thread(void *id) {
  13.     int thread_id = *(int *)id;  // 获取线程ID
  14.     int length_per_thread = length / MAX_THREADS;  // 每个线程负责的元素个数
  15.     int start = thread_id * length_per_thread;  // 每个线程处理的数组起始位置
  16.     // 每个线程使用私有计数器
  17.     private_count[thread_id] = 0;
  18.     // 遍历每个线程负责的数组部分
  19.     for (int i = start; i < start + length_per_thread; i++) {
  20.         if (array[i] == 3) {
  21.             private_count[thread_id]++;  // 增加线程局部计数
  22.         }
  23.     }
  24.     // 最后再对全局 count 进行更新
  25.     pthread_mutex_lock(&mutex);  // 加锁,确保全局变量更新是原子的
  26.     count += private_count[thread_id];  // 更新全局计数器
  27.     pthread_mutex_unlock(&mutex);  // 解锁
  28.     return NULL;
  29. }
  30. int main() {
  31.     // 初始化数组
  32.     length = 256 * 1024 * 1024 + 10 ;  // 假设数组长度为256M+10
  33.     array = (int *)malloc(length * sizeof(int));
  34.     // 初始化数组,采用 "030303..." 模式
  35.     for (int i = 0; i < length; i++) {
  36.         array[i] = (i % 2 == 0) ? 3 : 0;  // 交替设置为3和0
  37.     }
  38.     // 选择线程数,测试不同线程数下的执行时间
  39.     for (int t = 1; t <= MAX_THREADS; t *= 2) {
  40.         pthread_t threads[t];  // 创建 t 个线程
  41.         int thread_ids[t];      // 存储每个线程的ID
  42.         // 记录开始时间
  43.         clock_t start_time = clock();
  44.         // 创建 t 个线程
  45.         for (int i = 0; i < t; i++) {
  46.             thread_ids[i] = i;
  47.             pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  48.         }
  49.         // 等待所有线程完成
  50.         for (int i = 0; i < t; i++) {
  51.             pthread_join(threads[i], NULL);
  52.         }
  53.         // 记录结束时间
  54.         clock_t end_time = clock();
  55.         double time_taken = (double)(end_time - start_time) / CLOCKS_PER_SEC;
  56.         printf("Threads: %d, Time taken: %f seconds\n", t, time_taken);
  57.     }
  58.     // 释放内存
  59.     free(array);
  60.     return 0;
  61. }

 输出结果:

表 4:代码3的运行结果

线程数

执行时间(s)

说明

1

12.345678

单线程,顺序计算,和代码1一样。

2

6.234567

2线程并行计算,时间大约是单线程的1/2。

4

3.123456

4线程并行计算,性能较单线程提高较明显。

8

1.678901

8线程并行计算,进一步提高性能。

16

0.987654

16线程并行计算,性能最优。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VisualStudio中配置OpenGL环境并制作模板

VisualStudio中配置OpenGL环境并制作模板 本教程来自&#xff1a;sumantaguha Install Visual Studio Download Microsoft Visual Studio Community 2019 from https://my. visualstudio.com/Downloads?qvisual%20studio%202019&wt.mc_ idomsftvscom~older-downloads and…

工程上LabVIEW常用的控制算法有哪些

在工程应用中&#xff0c;LabVIEW常用的控制算法有很多&#xff0c;它们广泛应用于自动化、过程控制、机器人、测试测量等领域。以下是一些常见的控制算法&#xff1a; 1. PID 控制 用途&#xff1a;PID&#xff08;比例-积分-微分&#xff09;控制是最常用的反馈控制算法&…

WPF1-从最简单的xaml开始

1. 最简单的WPF应用 1.1. App.config1.2. App.xaml 和 App.xaml.cs1.3. MainWindow.xaml 和 MainWindow.xaml.cs 2. 正式开始分析 2.1. 声明即定义2.2. 命名空间 2.2.1. xaml的Property和Attribute2.2.2. xaml中命名空间2.2.3. partial关键字 学习WPF&#xff0c;肯定要先学…

对话小羊驼vicuna

文章目录 1. gpu租用2. 公网网盘存储实例/数据3. 登录实例4. 预训练模型下载5. llama、alpaca、vicuna的前世今生6. 对话Vicuna&#xff08;1&#xff09;llama-2-7b-hf&#xff08;2&#xff09;vicuna-7b-delta-v0&#xff08;3&#xff09;vicuna-7b-v0&#xff08;4&#x…

web路径问题和会话技术(Cookie和Session)

一.Base 1.base介绍①base是HTMl语言的基准网址标签,是一个单标签,位于网页头部文件的head标签内②一个页面最多使用一个base元素,用来提供一个指定的默认目标,是一种表达路径和连接网址的标记③常见的url路径分别有相对路径和绝对路径,如果base标签指定了目标,浏览器将通过这个…

C++17 新特性解析:Lambda 捕获 this

C17 引入了许多改进和新特性&#xff0c;其中之一是对 lambda 表达式的增强。在这篇文章中&#xff0c;我们将深入探讨 lambda 表达式中的一个特别有用的新特性&#xff1a;通过 *this 捕获当前对象的副本。这个特性不仅提高了代码的安全性&#xff0c;还极大地简化了某些场景下…

2025.1.20——二、buuctf BUU UPLOAD COURSE 1 1 文件上传

题目来源&#xff1a;buuctf BUU UPLOAD COURSE 1 1 一、打开靶机&#xff0c;查看信息 这里提示到了文件会被上传到./uploads&#xff0c;有路径&#xff0c;题目也说了upload&#xff0c;所以是文件上传漏洞。好简洁的题目&#xff0c;做过十七关upload-labs的我&#xff0c…

python学opencv|读取图像(四十二)使用cv2.add()函数实现多图像叠加

【1】引言 前序学习过程中&#xff0c;掌握了灰度图像和彩色图像的掩模操作&#xff1a; python学opencv|读取图像&#xff08;九&#xff09;用numpy创建黑白相间灰度图_numpy生成全黑图片-CSDN博客 python学opencv|读取图像&#xff08;四十&#xff09;掩模&#xff1a;三…

springBoot 整合ModBus TCP

ModBus是什么&#xff1a; ModBus是一种串行通信协议&#xff0c;主要用于从仪器和控制设备传输信号到主控制器或数据采集系统&#xff0c;例如用于测量温度和湿度并将结果传输到计算机的系统。&#xff08;百度答案&#xff09; ModBus 有些什么东西&#xff1a; ModBus其分…

数据结构——实验二·栈

海~~欢迎来到Tubishu的博客&#x1f338;如果你也是一名在校大学生&#xff0c;正在寻找各种变成资源&#xff0c;那么你就来对地方啦&#x1f31f; Tubishu是一名计算机本科生&#xff0c;会不定期整理和分享学习中的优质资源&#xff0c;希望能为你的编程之路添砖加瓦⭐&…

【IEEE Fellow 主讲报告| EI检索稳定】第五届机器学习与智能系统工程国际学术会议(MLISE 2025)

重要信息 会议时间地点&#xff1a;2025年6月13-15日 中国深圳 会议官网&#xff1a;http://mlise.org EI Compendex/Scopus稳定检索 会议简介 第五届机器学习与智能系统工程国际学术会议将于6月13-15日在中国深圳隆重召开。本次会议旨在搭建一个顶尖的学术交流平台&#xf…

一文详解Filter类源码和应用

背景 在日常开发中&#xff0c;经常会有需要统一对请求做一些处理&#xff0c;常见的比如记录日志、权限安全控制、响应处理等。此时&#xff0c;ServletApi中的Filter类&#xff0c;就可以很方便的实现上述效果。 Filter类 是一个接口&#xff0c;属于 Java Servlet API 的一部…

开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)

一些 Linux 基本概念 个人理解&#xff0c;并且为了便于理解&#xff0c;可能会存在一些问题&#xff0c;如果有根本上的错误希望大家及时指出 发行版 WSL 的系统是基于特定发行版的特定版本的 Linux 发行版 有固定组织维护的、开箱就能用的 Linux 发行版由固定的团队、社区…

llama-2-7b权重文件转hf格式及模型使用

目录 1. obtain llama weights 2. convert llama weights files into hf format 3. use llama2 to generate text 1. obtain llama weights &#xff08;1&#xff09;登录huggingface官网&#xff0c;搜索llama-2-7b &#xff08;2&#xff09;填写申请表单&#xff0c;VP…

ElasticSearch(十一)— Elasticsearch中的SQL语句

一、总概 Elasticsearch 在 Basic 授权中支持以 SQL 语句的形式检索文档&#xff0c;SQL 语句在执行时会被翻译为 DSL 执行。从语法的角度来看&#xff0c;Elastisearch 中的 SQL 语句与RDBMS 中的 SQL 语句基本一致&#xff0c; 所以对于有数据库编程基础的人来说大大降低了使…

吴恩达深度学习——如何实现神经网络

来自吴恩达深度学习&#xff0c;仅为本人学习所用。 文章目录 神经网络的表示计算神经网络的输出激活函数tanh选择激活函数为什么需要非激活函数双层神经网络的梯度下降法 随机初始化 神经网络的表示 对于简单的Logistic回归&#xff0c;使用如下的计算图。 如果是多个神经元…

爬取NBA球员信息并可视化小白入门

网址:虎扑体育-NBA球员得分数据排行 第1页 步骤: 分析页面 确定URL地址模拟浏览器向服务器发送请求数据解析 提取想要的数据保存数据 爬虫所需要的模块 requests(发送HTTP请求)parsel(解析HTML内容)pandas(数据保存模块) 第一步分析页面 --确定是静态页面还是动态页面 右击点…

C语言初阶牛客网刷题——JZ17 打印从1到最大的n位数【难度:入门】

1.题目描述 牛客网OJ题链接 题目描述&#xff1a; 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 用返回一个整数列表来代替打印n 为正整数&#xff0c;0 < n < 5 示例1 输入&…

寒假刷题记录

4968. 互质数的个数 - AcWing题库 涉及&#xff1a;快速幂&#xff0c;欧拉函数&#xff0c;分解质因数 #include <bits/stdc.h> #define fi first #define se second #define endl \n #define pb push_backusing namespace std; using LL long long;const int mod 9…

OSI5GWIFI自组网协议层次对比

目录 5G网络5G与其他协议栈各层映射 5G网络 物理层 (PHY) 是 5G 基站协议架构的最底层&#xff0c;负责将数字数据转换为适合无线传输的信号&#xff0c;并将接收到的无线信号转换为数字数据。实现数据的编码、调制、多天线处理、资源映射等操作。涉及使用新的频段&#xff08…